From b633ec3da6ccca196cd9d78c3c43d9797bd8d982 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Wed, 10 Nov 2021 11:30:49 +0100 Subject: [PATCH] feat: add x-total-count to paginated pages --- go.mod | 3 +- go.sum | 4 +- identity/handler.go | 3 +- x/pagination.go | 106 +++------------------------------- x/pagination_test.go | 133 ------------------------------------------- 5 files changed, 14 insertions(+), 235 deletions(-) delete mode 100644 x/pagination_test.go diff --git a/go.mod b/go.mod index 9f954dc10273..8d3bcd966107 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,7 @@ require ( github.com/ory/kratos-client-go v0.6.3-alpha.1 github.com/ory/mail/v3 v3.0.0 github.com/ory/nosurf v1.2.6 - github.com/ory/x v0.0.300 + github.com/ory/x v0.0.310 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.3.0 @@ -91,7 +91,6 @@ require ( github.com/tidwall/sjson v1.2.2 github.com/urfave/negroni v1.0.0 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 - golang.org/x/mod v0.5.1 // indirect golang.org/x/net v0.0.0-20211020060615-d418f374d309 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c diff --git a/go.sum b/go.sum index 10b21e2d6a90..483523e8391f 100644 --- a/go.sum +++ b/go.sum @@ -1586,8 +1586,8 @@ github.com/ory/x v0.0.205/go.mod h1:A1s4iwmFIppRXZLF3J9GGWeY/HpREVm0Dk5z/787iek= github.com/ory/x v0.0.250/go.mod h1:jUJaVptu+geeqlb9SyQCogTKj5ztSDIF6APkhbKtwLc= github.com/ory/x v0.0.272/go.mod h1:1TTPgJGQutrhI2OnwdrTIHE9ITSf4MpzXFzA/ncTGRc= github.com/ory/x v0.0.288/go.mod h1:APpShLyJcVzKw1kTgrHI+j/L9YM+8BRjHlcYObc7C1U= -github.com/ory/x v0.0.300 h1:uFkjn6+pB55tnWg1KGaeVewBRULL/cQTrpSQGbz3rbg= -github.com/ory/x v0.0.300/go.mod h1:CDs8F4TtG/A+F9FfBR40G7u/gA3Rg1+NcvkLlxN+H+Q= +github.com/ory/x v0.0.310 h1:Lk4g7PKm3PWfV29A+WEjn8y3MNG35ucG0D9mclSIQOo= +github.com/ory/x v0.0.310/go.mod h1:CDs8F4TtG/A+F9FfBR40G7u/gA3Rg1+NcvkLlxN+H+Q= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= diff --git a/identity/handler.go b/identity/handler.go index e82e0f9b404a..7c28d2614712 100644 --- a/identity/handler.go +++ b/identity/handler.go @@ -6,6 +6,8 @@ import ( "net/http" "time" + "github.com/ory/kratos/x" + "github.com/ory/kratos/cipher" "github.com/ory/herodot" @@ -18,7 +20,6 @@ import ( "github.com/ory/x/urlx" "github.com/ory/kratos/driver/config" - "github.com/ory/kratos/x" ) const RouteCollection = "/identities" diff --git a/x/pagination.go b/x/pagination.go index 85fa4ffa9e4e..f1f2e39f4a2e 100644 --- a/x/pagination.go +++ b/x/pagination.go @@ -1,113 +1,25 @@ package x import ( - "fmt" "net/http" "net/url" - "strconv" - "strings" + + "github.com/ory/x/pagination/pagepagination" ) const paginationMaxItems = 1000 const paginationDefaultItems = 250 -// ParsePagination parses limit and page from *http.Request with given limits and defaults. -func ParsePagination(r *http.Request) (page, itemsPerPage int) { - if offsetParam := r.URL.Query().Get("page"); offsetParam == "" { - page = 0 - } else { - if offset, err := strconv.ParseInt(offsetParam, 10, 0); err != nil { - page = 0 - } else { - page = int(offset) - } - } - - if limitParam := r.URL.Query().Get("per_page"); limitParam == "" { - itemsPerPage = paginationDefaultItems - } else { - if limit, err := strconv.ParseInt(limitParam, 10, 0); err != nil { - itemsPerPage = paginationDefaultItems - } else { - itemsPerPage = int(limit) - } - } - - if itemsPerPage > paginationMaxItems { - itemsPerPage = paginationMaxItems - } - - if itemsPerPage < 1 { - itemsPerPage = 1 - } - - if page < 0 { - page = 0 - } - - return +var paginator = &pagepagination.PagePaginator{ + MaxItems: paginationMaxItems, + DefaultItems: paginationDefaultItems, } -func header(u *url.URL, rel string, limit, page int64) string { - q := u.Query() - q.Set("per_page", fmt.Sprintf("%d", limit)) - q.Set("page", fmt.Sprintf("%d", page/limit)) - u.RawQuery = q.Encode() - return fmt.Sprintf("<%s>; rel=\"%s\"", u.String(), rel) +// ParsePagination parses limit and page from *http.Request with given limits and defaults. +func ParsePagination(r *http.Request) (page, itemsPerPage int) { + return paginator.ParsePagination(r) } func PaginationHeader(w http.ResponseWriter, u *url.URL, total int64, page, itemsPerPage int) { - if itemsPerPage <= 0 { - itemsPerPage = 1 - } - - itemsPerPage64 := int64(itemsPerPage) - offset := int64(page) * itemsPerPage64 - - // lastOffset will either equal the offset required to contain the remainder, - // or the limit. - var lastOffset int64 - if total%itemsPerPage64 == 0 { - lastOffset = total - itemsPerPage64 - } else { - lastOffset = (total / itemsPerPage64) * itemsPerPage64 - } - - // Check for last page - if offset >= lastOffset { - if total == 0 { - w.Header().Set("Link", strings.Join([]string{ - header(u, "first", itemsPerPage64, 0), - header(u, "next", itemsPerPage64, ((offset/itemsPerPage64)+1)*itemsPerPage64), - header(u, "prev", itemsPerPage64, ((offset/itemsPerPage64)-1)*itemsPerPage64), - }, ",")) - return - } - - if total < itemsPerPage64 { - w.Header().Set("link", header(u, "first", total, 0)) - return - } - - w.Header().Set("Link", strings.Join([]string{ - header(u, "first", itemsPerPage64, 0), - header(u, "prev", itemsPerPage64, lastOffset-itemsPerPage64), - }, ",")) - return - } - - if offset < itemsPerPage64 { - w.Header().Set("Link", strings.Join([]string{ - header(u, "next", itemsPerPage64, itemsPerPage64), - header(u, "last", itemsPerPage64, lastOffset), - }, ",")) - return - } - - w.Header().Set("Link", strings.Join([]string{ - header(u, "first", itemsPerPage64, 0), - header(u, "next", itemsPerPage64, ((offset/itemsPerPage64)+1)*itemsPerPage64), - header(u, "prev", itemsPerPage64, ((offset/itemsPerPage64)-1)*itemsPerPage64), - header(u, "last", itemsPerPage64, lastOffset), - }, ",")) + pagepagination.PaginationHeader(w, u, total, page, itemsPerPage) } diff --git a/x/pagination_test.go b/x/pagination_test.go deleted file mode 100644 index 335dbad36be3..000000000000 --- a/x/pagination_test.go +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright © 2017-2018 Aeneas Rekkas - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @author Aeneas Rekkas - * @copyright 2017-2018 Aeneas Rekkas - * @license Apache-2.0 - */ -package x - -import ( - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/ory/x/urlx" -) - -func TestPaginationHeader(t *testing.T) { - u := urlx.ParseOrPanic("http://example.com") - - t.Run("Create previous and first but not next or last if at the end", func(t *testing.T) { - r := httptest.NewRecorder() - PaginationHeader(r, u, 120, 2, 50) - - expect := strings.Join([]string{ - "; rel=\"first\"", - "; rel=\"prev\"", - }, ",") - - assert.EqualValues(t, expect, r.Result().Header.Get("Link")) - }) - - t.Run("Create next and last, but not previous or first if at the beginning", func(t *testing.T) { - r := httptest.NewRecorder() - PaginationHeader(r, u, 120, 0, 50) - - expect := strings.Join([]string{ - "; rel=\"next\"", - "; rel=\"last\"", - }, ",") - - assert.EqualValues(t, expect, r.Result().Header.Get("Link")) - }) - - t.Run("Create previous, next, first, and last if in the middle", func(t *testing.T) { - r := httptest.NewRecorder() - PaginationHeader(r, u, 300, 3, 50) - - expect := strings.Join([]string{ - "; rel=\"first\"", - "; rel=\"next\"", - "; rel=\"prev\"", - "; rel=\"last\"", - }, ",") - - assert.EqualValues(t, expect, r.Result().Header.Get("Link")) - }) - - t.Run("Header should default limit to 1 no limit was provided", func(t *testing.T) { - r := httptest.NewRecorder() - PaginationHeader(r, u, 100, 20, 0) - - expect := strings.Join([]string{ - "; rel=\"first\"", - "; rel=\"next\"", - "; rel=\"prev\"", - "; rel=\"last\"", - }, ",") - - assert.EqualValues(t, expect, r.Result().Header.Get("Link")) - }) - - t.Run("Create previous, next, first, but not last if in the middle and no total was provided", func(t *testing.T) { - r := httptest.NewRecorder() - PaginationHeader(r, u, 0, 3, 50) - - expect := strings.Join([]string{ - "; rel=\"first\"", - "; rel=\"next\"", - "; rel=\"prev\"", - }, ",") - - assert.EqualValues(t, expect, r.Result().Header.Get("Link")) - }) - - t.Run("Create only first if the limits provided exceeds the number of clients found", func(t *testing.T) { - r := httptest.NewRecorder() - PaginationHeader(r, u, 5, 0, 50) - - expect := "; rel=\"first\"" - - assert.EqualValues(t, expect, r.Result().Header.Get("Link")) - }) -} - -func TestParsePagination(t *testing.T) { - for _, tc := range []struct { - d string - url string - expectedItemsPerPage int - expectedPage int - }{ - {"normal", "http://localhost/foo?per_page=10&page=10", 10, 10}, - {"defaults", "http://localhost/foo", paginationDefaultItems, 0}, - {"limits", "http://localhost/foo?per_page=2000", paginationMaxItems, 0}, - {"negatives", "http://localhost/foo?per_page=-1&page=-1", 1, 0}, - {"invalid_params", "http://localhost/foo?per_page=a&page=b", paginationDefaultItems, 0}, - } { - t.Run(fmt.Sprintf("case=%s", tc.d), func(t *testing.T) { - u, _ := url.Parse(tc.url) - page, perPage := ParsePagination(&http.Request{URL: u}) - assert.EqualValues(t, perPage, tc.expectedItemsPerPage, "per_page") - assert.EqualValues(t, page, tc.expectedPage, "page") - }) - } -}