From 0e0a4691b626bb2537f03afb337a164aa8669261 Mon Sep 17 00:00:00 2001 From: Lauris BH Date: Mon, 4 Dec 2023 14:50:48 +0000 Subject: [PATCH] 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 Reviewed-by: appleboy Co-authored-by: Lauris BH Co-committed-by: Lauris BH --- gitea/client.go | 72 +++++++++++++++++++++++++++++++++++++++----- gitea/client_test.go | 35 +++++++++++++++++++++ gitea/release.go | 2 +- gitea/repo_file.go | 3 +- 4 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 gitea/client_test.go diff --git a/gitea/client.go b/gitea/client.go index fc5829e..adbadc7 100644 --- a/gitea/client.go +++ b/gitea/client.go @@ -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 } diff --git a/gitea/client_test.go b/gitea/client_test.go new file mode 100644 index 0000000..9470690 --- /dev/null +++ b/gitea/client_test.go @@ -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{ + `; rel="next"`, + `; rel="last"`, + `; rel="first"`, + `; 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) +} diff --git a/gitea/release.go b/gitea/release.go index 3200f20..3a83417 100644 --- a/gitea/release.go +++ b/gitea/release.go @@ -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 { diff --git a/gitea/repo_file.go b/gitea/repo_file.go index bcba705..79bbeb1 100644 --- a/gitea/repo_file.go +++ b/gitea/repo_file.go @@ -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 }