Add Link header parsing in response (#638)
Helps if need to get all data that can not be requested in single page Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/638 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-by: appleboy <appleboy.tw@gmail.com> Co-authored-by: Lauris BH <lauris@nix.lv> Co-committed-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
parent
e23e8aa300
commit
0e0a4691b6
4 changed files with 102 additions and 10 deletions
|
@ -12,9 +12,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -50,6 +50,11 @@ type Client struct {
|
||||||
// Response represents the gitea response
|
// Response represents the gitea response
|
||||||
type Response struct {
|
type Response struct {
|
||||||
*http.Response
|
*http.Response
|
||||||
|
|
||||||
|
FirstPage int
|
||||||
|
PrevPage int
|
||||||
|
NextPage int
|
||||||
|
LastPage int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientOption are functions used to init a new client
|
// ClientOption are functions used to init a new client
|
||||||
|
@ -241,6 +246,57 @@ func SetDebugMode() ClientOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newResponse(r *http.Response) *Response {
|
||||||
|
response := &Response{Response: r}
|
||||||
|
response.parseLinkHeader()
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Response) parseLinkHeader() {
|
||||||
|
link := r.Header.Get("Link")
|
||||||
|
if link == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
links := strings.Split(link, ",")
|
||||||
|
for _, l := range links {
|
||||||
|
u, param, ok := strings.Cut(l, ";")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
u = strings.Trim(u, " <>")
|
||||||
|
|
||||||
|
key, value, ok := strings.Cut(strings.TrimSpace(param), "=")
|
||||||
|
if !ok || key != "rel" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value = strings.Trim(value, "\"")
|
||||||
|
|
||||||
|
parsed, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
page := parsed.Query().Get("page")
|
||||||
|
if page == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value {
|
||||||
|
case "first":
|
||||||
|
r.FirstPage, _ = strconv.Atoi(page)
|
||||||
|
case "prev":
|
||||||
|
r.PrevPage, _ = strconv.Atoi(page)
|
||||||
|
case "next":
|
||||||
|
r.NextPage, _ = strconv.Atoi(page)
|
||||||
|
case "last":
|
||||||
|
r.LastPage, _ = strconv.Atoi(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) {
|
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) {
|
||||||
c.mutex.RLock()
|
c.mutex.RLock()
|
||||||
debug := c.debug
|
debug := c.debug
|
||||||
|
@ -262,11 +318,12 @@ func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *R
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if debug {
|
if debug {
|
||||||
fmt.Printf("Response: %v\n\n", resp)
|
fmt.Printf("Response: %v\n\n", resp)
|
||||||
}
|
}
|
||||||
return data, &Response{resp}, err
|
|
||||||
|
return data, newResponse(resp), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
|
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
|
||||||
|
@ -275,7 +332,7 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
|
||||||
if debug {
|
if debug {
|
||||||
var bodyStr string
|
var bodyStr string
|
||||||
if body != nil {
|
if body != nil {
|
||||||
bs, _ := ioutil.ReadAll(body)
|
bs, _ := io.ReadAll(body)
|
||||||
body = bytes.NewReader(bs)
|
body = bytes.NewReader(bs)
|
||||||
bodyStr = string(bs)
|
bodyStr = string(bs)
|
||||||
}
|
}
|
||||||
|
@ -323,7 +380,8 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
|
||||||
if debug {
|
if debug {
|
||||||
fmt.Printf("Response: %v\n\n", resp)
|
fmt.Printf("Response: %v\n\n", resp)
|
||||||
}
|
}
|
||||||
return &Response{resp}, nil
|
|
||||||
|
return newResponse(resp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a response for a HTTP status code indicating an error condition
|
// Converts a response for a HTTP status code indicating an error condition
|
||||||
|
@ -340,7 +398,7 @@ func statusCodeToErr(resp *Response) (body []byte, err error) {
|
||||||
// error: body will be read for details
|
// error: body will be read for details
|
||||||
//
|
//
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("body read on HTTP error %d: %v", resp.StatusCode, err)
|
return nil, fmt.Errorf("body read on HTTP error %d: %v", resp.StatusCode, err)
|
||||||
}
|
}
|
||||||
|
@ -393,7 +451,7 @@ func (c *Client) getResponse(method, path string, header http.Header, body io.Re
|
||||||
}
|
}
|
||||||
|
|
||||||
// success (2XX), read body
|
// success (2XX), read body
|
||||||
data, err = ioutil.ReadAll(resp.Body)
|
data, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, resp, err
|
return nil, resp, err
|
||||||
}
|
}
|
||||||
|
|
35
gitea/client_test.go
Normal file
35
gitea/client_test.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParsedPaging(t *testing.T) {
|
||||||
|
resp := newResponse(&http.Response{
|
||||||
|
Header: http.Header{
|
||||||
|
"Link": []string{
|
||||||
|
strings.Join(
|
||||||
|
[]string{
|
||||||
|
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=3>; rel="next"`,
|
||||||
|
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=4>; rel="last"`,
|
||||||
|
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=1>; rel="first"`,
|
||||||
|
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=1>; rel="prev"`,
|
||||||
|
}, ",",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, 1, resp.FirstPage)
|
||||||
|
assert.Equal(t, 1, resp.PrevPage)
|
||||||
|
assert.Equal(t, 3, resp.NextPage)
|
||||||
|
assert.Equal(t, 4, resp.LastPage)
|
||||||
|
}
|
|
@ -190,7 +190,7 @@ func (c *Client) fallbackGetReleaseByTag(owner, repo, tag string) (*Release, *Re
|
||||||
}
|
}
|
||||||
if len(rl) == 0 {
|
if len(rl) == 0 {
|
||||||
return nil,
|
return nil,
|
||||||
&Response{&http.Response{StatusCode: 404}},
|
newResponse(&http.Response{StatusCode: 404}),
|
||||||
fmt.Errorf("release with tag '%s' not found", tag)
|
fmt.Errorf("release with tag '%s' not found", tag)
|
||||||
}
|
}
|
||||||
for _, r := range rl {
|
for _, r := range rl {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -128,7 +127,7 @@ func (c *Client) GetFile(owner, repo, ref, filepath string, resolveLFS ...bool)
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
data, err2 := ioutil.ReadAll(reader)
|
data, err2 := io.ReadAll(reader)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return nil, resp, err2
|
return nil, resp, err2
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue