diff --git a/go.mod b/go.mod index d31af759..4c6617e8 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( github.com/ory/herodot v0.9.13 github.com/ory/jsonschema/v3 v3.0.7 github.com/pelletier/go-toml v1.9.5 + github.com/peterhellberg/link v1.2.0 github.com/pkg/errors v0.9.1 github.com/pkg/profile v1.7.0 github.com/prometheus/client_golang v1.13.0 diff --git a/go.sum b/go.sum index 6c964ec1..c60eaa8c 100644 --- a/go.sum +++ b/go.sum @@ -752,6 +752,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= +github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= diff --git a/pagination/keysetpagination/parse_header.go b/pagination/keysetpagination/parse_header.go new file mode 100644 index 00000000..8be68b03 --- /dev/null +++ b/pagination/keysetpagination/parse_header.go @@ -0,0 +1,44 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package keysetpagination + +import ( + "net/http" + "net/url" + + "github.com/peterhellberg/link" +) + +// PaginationResult represents a parsed result of the link HTTP header. +type PaginationResult struct { + // NextToken is the next page token. If it's empty, there is no next page. + NextToken string + + // FirstToken is the first page token. + FirstToken string +} + +// ParseHeader parses the response header's Link. +func ParseHeader(r *http.Response) *PaginationResult { + links := link.ParseResponse(r) + return &PaginationResult{ + NextToken: findRel(links, "next"), + FirstToken: findRel(links, "first"), + } +} + +func findRel(links link.Group, rel string) string { + for idx, l := range links { + if idx == rel { + parsed, err := url.Parse(l.URI) + if err != nil { + continue + } + + return parsed.Query().Get("page_token") + } + } + + return "" +} diff --git a/pagination/keysetpagination/parse_header_test.go b/pagination/keysetpagination/parse_header_test.go new file mode 100644 index 00000000..99ade8ae --- /dev/null +++ b/pagination/keysetpagination/parse_header_test.go @@ -0,0 +1,49 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package keysetpagination + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseHeader(t *testing.T) { + u, err := url.Parse("https://www.ory.sh/") + require.NoError(t, err) + + t.Run("has next page", func(t *testing.T) { + p := &Paginator{ + defaultToken: StringPageToken("default"), + token: StringPageToken("next"), + size: 2, + } + + r := httptest.NewRecorder() + Header(r, u, p) + + result := ParseHeader(&http.Response{Header: r.Header()}) + assert.Equal(t, "next", result.NextToken, r.Header()) + assert.Equal(t, "default", result.FirstToken, r.Header()) + }) + + t.Run("is last page", func(t *testing.T) { + p := &Paginator{ + defaultToken: StringPageToken("default"), + size: 1, + isLast: true, + } + + r := httptest.NewRecorder() + Header(r, u, p) + + result := ParseHeader(&http.Response{Header: r.Header()}) + assert.Equal(t, "", result.NextToken, r.Header()) + assert.Equal(t, "default", result.FirstToken, r.Header()) + }) +}