From bc34e3beeffcc79064a0c1f6dadccafce8f8c3d8 Mon Sep 17 00:00:00 2001 From: Krithika Sundararajan Date: Wed, 8 May 2024 14:01:12 +0800 Subject: [PATCH 1/3] Add package pagination --- api/pkg/pagination/pagination.go | 65 +++++++++++++ api/pkg/pagination/pagination_test.go | 131 ++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 api/pkg/pagination/pagination.go create mode 100644 api/pkg/pagination/pagination_test.go diff --git a/api/pkg/pagination/pagination.go b/api/pkg/pagination/pagination.go new file mode 100644 index 00000000..08be312a --- /dev/null +++ b/api/pkg/pagination/pagination.go @@ -0,0 +1,65 @@ +package pagination + +import ( + "fmt" + "math" +) + +type Paging struct { + Page int32 + Pages int32 + Total int32 +} + +type PaginationOptions struct { + Page *int32 `json:"page,omitempty"` + PageSize *int32 `json:"page_size,omitempty"` +} + +type Paginator struct { + DefaultPage int32 + DefaultPageSize int32 + MaxPageSize int32 +} + +func NewPaginator(defaultPage int32, defaultPageSize int32, maxPageSize int32) Paginator { + return Paginator{ + DefaultPage: defaultPage, + DefaultPageSize: defaultPageSize, + MaxPageSize: maxPageSize, + } +} + +func (p Paginator) NewPaginationOptions(page *int32, pageSize *int32) PaginationOptions { + if page == nil { + page = &p.DefaultPage + } + if pageSize == nil { + pageSize = &p.DefaultPageSize + } + + return PaginationOptions{ + Page: page, + PageSize: pageSize, + } +} + +func (p Paginator) ValidatePaginationParams(page *int32, pageSize *int32) error { + if pageSize != nil && (*pageSize <= 0 || *pageSize > p.MaxPageSize) { + return fmt.Errorf("Page size must be within range (0 < page_size <= %d) or unset.", + p.MaxPageSize) + } + if page != nil && *page <= 0 { + return fmt.Errorf("Page must be > 0 or unset.") + } + + return nil +} + +func ToPaging(opts PaginationOptions, count int) *Paging { + return &Paging{ + Page: *opts.Page, + Pages: int32(math.Ceil(float64(count) / float64(*opts.PageSize))), + Total: int32(count), + } +} diff --git a/api/pkg/pagination/pagination_test.go b/api/pkg/pagination/pagination_test.go new file mode 100644 index 00000000..af39ef11 --- /dev/null +++ b/api/pkg/pagination/pagination_test.go @@ -0,0 +1,131 @@ +package pagination + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewPaginationOptions(t *testing.T) { + one, two, three, ten := int32(1), int32(2), int32(3), int32(10) + paginator := NewPaginator(1, 10, 50) + + tests := map[string]struct { + page *int32 + pageSize *int32 + expected PaginationOptions + }{ + "defaults": { + expected: PaginationOptions{ + Page: &one, + PageSize: &ten, + }, + }, + "missing page": { + pageSize: &three, + expected: PaginationOptions{ + Page: &one, + PageSize: &three, + }, + }, + "missing page size": { + page: &two, + expected: PaginationOptions{ + Page: &two, + PageSize: &ten, + }, + }, + "new values": { + page: &two, + pageSize: &three, + expected: PaginationOptions{ + Page: &two, + PageSize: &three, + }, + }, + } + + for name, data := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, data.expected, paginator.NewPaginationOptions(data.page, data.pageSize)) + }) + } +} + +func TestValidatePaginationParams(t *testing.T) { + zero, one, two, hundred := int32(0), int32(1), int32(2), int32(100) + paginator := NewPaginator(1, 10, 50) + + tests := map[string]struct { + page *int32 + pageSize *int32 + expectedError string + }{ + "zero page size": { + pageSize: &zero, + expectedError: "Page size must be within range (0 < page_size <= 50) or unset.", + }, + "large page size": { + pageSize: &hundred, + expectedError: "Page size must be within range (0 < page_size <= 50) or unset.", + }, + "zero page": { + page: &zero, + expectedError: "Page must be > 0 or unset.", + }, + "success - all values unset": {}, + "success - all values set": { + page: &one, + pageSize: &two, + }, + } + + for name, data := range tests { + t.Run(name, func(t *testing.T) { + err := paginator.ValidatePaginationParams(data.page, data.pageSize) + if data.expectedError == "" { + assert.Nil(t, err) + } else { + assert.EqualError(t, err, data.expectedError) + } + }) + } +} + +func TestPaging(t *testing.T) { + tests := []struct { + page int32 + pageSize int32 + count int + expected Paging + }{ + { + page: int32(1), + pageSize: int32(10), + count: 5, + expected: Paging{ + Page: 1, + Pages: 1, + Total: 5, + }, + }, + { + page: int32(2), + pageSize: int32(3), + count: 7, + expected: Paging{ + Page: 2, + Pages: 3, + Total: 7, + }, + }, + } + + for idx, data := range tests { + t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) { + actual := ToPaging(PaginationOptions{Page: &data.page, PageSize: &data.pageSize}, data.count) + assert.Equal(t, data.expected, *actual) + }) + } +} From 9493ee2b1d8eaffb52147a13f4d170cee8b22a3b Mon Sep 17 00:00:00 2001 From: Krithika Sundararajan Date: Wed, 8 May 2024 16:12:23 +0800 Subject: [PATCH 2/3] Fix linter error --- api/pkg/pagination/pagination.go | 7 +++++-- api/pkg/pagination/pagination_test.go | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/api/pkg/pagination/pagination.go b/api/pkg/pagination/pagination.go index 08be312a..7600717d 100644 --- a/api/pkg/pagination/pagination.go +++ b/api/pkg/pagination/pagination.go @@ -5,17 +5,20 @@ import ( "math" ) +// Paging can be used to capture paging information in API responses. type Paging struct { Page int32 Pages int32 Total int32 } +// PaginationOptions can be used to supply pagination filter options to APIs. type PaginationOptions struct { Page *int32 `json:"page,omitempty"` PageSize *int32 `json:"page_size,omitempty"` } +// Paginator handles common pagination workflows. type Paginator struct { DefaultPage int32 DefaultPageSize int32 @@ -46,11 +49,11 @@ func (p Paginator) NewPaginationOptions(page *int32, pageSize *int32) Pagination func (p Paginator) ValidatePaginationParams(page *int32, pageSize *int32) error { if pageSize != nil && (*pageSize <= 0 || *pageSize > p.MaxPageSize) { - return fmt.Errorf("Page size must be within range (0 < page_size <= %d) or unset.", + return fmt.Errorf("page size must be within range (0 < page_size <= %d) or unset", p.MaxPageSize) } if page != nil && *page <= 0 { - return fmt.Errorf("Page must be > 0 or unset.") + return fmt.Errorf("page must be > 0 or unset") } return nil diff --git a/api/pkg/pagination/pagination_test.go b/api/pkg/pagination/pagination_test.go index af39ef11..97d2fdb8 100644 --- a/api/pkg/pagination/pagination_test.go +++ b/api/pkg/pagination/pagination_test.go @@ -64,15 +64,15 @@ func TestValidatePaginationParams(t *testing.T) { }{ "zero page size": { pageSize: &zero, - expectedError: "Page size must be within range (0 < page_size <= 50) or unset.", + expectedError: "page size must be within range (0 < page_size <= 50) or unset", }, "large page size": { pageSize: &hundred, - expectedError: "Page size must be within range (0 < page_size <= 50) or unset.", + expectedError: "page size must be within range (0 < page_size <= 50) or unset", }, "zero page": { page: &zero, - expectedError: "Page must be > 0 or unset.", + expectedError: "page must be > 0 or unset", }, "success - all values unset": {}, "success - all values set": { From 5bd5a0786bcf4dbdd9b10442cde2cddfc34aa02e Mon Sep 17 00:00:00 2001 From: Krithika Sundararajan Date: Wed, 8 May 2024 16:26:53 +0800 Subject: [PATCH 3/3] Rename pagination options struct --- api/pkg/pagination/pagination.go | 10 +++++----- api/pkg/pagination/pagination_test.go | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/pkg/pagination/pagination.go b/api/pkg/pagination/pagination.go index 7600717d..697a0faf 100644 --- a/api/pkg/pagination/pagination.go +++ b/api/pkg/pagination/pagination.go @@ -12,8 +12,8 @@ type Paging struct { Total int32 } -// PaginationOptions can be used to supply pagination filter options to APIs. -type PaginationOptions struct { +// Options can be used to supply pagination filter options to APIs. +type Options struct { Page *int32 `json:"page,omitempty"` PageSize *int32 `json:"page_size,omitempty"` } @@ -33,7 +33,7 @@ func NewPaginator(defaultPage int32, defaultPageSize int32, maxPageSize int32) P } } -func (p Paginator) NewPaginationOptions(page *int32, pageSize *int32) PaginationOptions { +func (p Paginator) NewPaginationOptions(page *int32, pageSize *int32) Options { if page == nil { page = &p.DefaultPage } @@ -41,7 +41,7 @@ func (p Paginator) NewPaginationOptions(page *int32, pageSize *int32) Pagination pageSize = &p.DefaultPageSize } - return PaginationOptions{ + return Options{ Page: page, PageSize: pageSize, } @@ -59,7 +59,7 @@ func (p Paginator) ValidatePaginationParams(page *int32, pageSize *int32) error return nil } -func ToPaging(opts PaginationOptions, count int) *Paging { +func ToPaging(opts Options, count int) *Paging { return &Paging{ Page: *opts.Page, Pages: int32(math.Ceil(float64(count) / float64(*opts.PageSize))), diff --git a/api/pkg/pagination/pagination_test.go b/api/pkg/pagination/pagination_test.go index 97d2fdb8..83b8f5bf 100644 --- a/api/pkg/pagination/pagination_test.go +++ b/api/pkg/pagination/pagination_test.go @@ -14,24 +14,24 @@ func TestNewPaginationOptions(t *testing.T) { tests := map[string]struct { page *int32 pageSize *int32 - expected PaginationOptions + expected Options }{ "defaults": { - expected: PaginationOptions{ + expected: Options{ Page: &one, PageSize: &ten, }, }, "missing page": { pageSize: &three, - expected: PaginationOptions{ + expected: Options{ Page: &one, PageSize: &three, }, }, "missing page size": { page: &two, - expected: PaginationOptions{ + expected: Options{ Page: &two, PageSize: &ten, }, @@ -39,7 +39,7 @@ func TestNewPaginationOptions(t *testing.T) { "new values": { page: &two, pageSize: &three, - expected: PaginationOptions{ + expected: Options{ Page: &two, PageSize: &three, }, @@ -124,7 +124,7 @@ func TestPaging(t *testing.T) { for idx, data := range tests { t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) { - actual := ToPaging(PaginationOptions{Page: &data.page, PageSize: &data.pageSize}, data.count) + actual := ToPaging(Options{Page: &data.page, PageSize: &data.pageSize}, data.count) assert.Equal(t, data.expected, *actual) }) }