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"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -50,6 +50,11 @@ type Client struct {
|
|||
// Response represents the gitea response
|
||||
type Response struct {
|
||||
*http.Response
|
||||
|
||||
FirstPage int
|
||||
PrevPage int
|
||||
NextPage int
|
||||
LastPage int
|
||||
}
|
||||
|
||||
// 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) {
|
||||
c.mutex.RLock()
|
||||
debug := c.debug
|
||||
|
@ -262,11 +318,12 @@ func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *R
|
|||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if debug {
|
||||
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) {
|
||||
|
@ -275,7 +332,7 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
|
|||
if debug {
|
||||
var bodyStr string
|
||||
if body != nil {
|
||||
bs, _ := ioutil.ReadAll(body)
|
||||
bs, _ := io.ReadAll(body)
|
||||
body = bytes.NewReader(bs)
|
||||
bodyStr = string(bs)
|
||||
}
|
||||
|
@ -323,7 +380,8 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
|
|||
if debug {
|
||||
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
|
||||
|
@ -340,7 +398,7 @@ func statusCodeToErr(resp *Response) (body []byte, err error) {
|
|||
// error: body will be read for details
|
||||
//
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
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
|
||||
data, err = ioutil.ReadAll(resp.Body)
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil,
|
||||
&Response{&http.Response{StatusCode: 404}},
|
||||
newResponse(&http.Response{StatusCode: 404}),
|
||||
fmt.Errorf("release with tag '%s' not found", tag)
|
||||
}
|
||||
for _, r := range rl {
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
@ -128,7 +127,7 @@ func (c *Client) GetFile(owner, repo, ref, filepath string, resolveLFS ...bool)
|
|||
}
|
||||
defer reader.Close()
|
||||
|
||||
data, err2 := ioutil.ReadAll(reader)
|
||||
data, err2 := io.ReadAll(reader)
|
||||
if err2 != nil {
|
||||
return nil, resp, err2
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue