Skip to content

Commit

Permalink
chore: fixup
Browse files Browse the repository at this point in the history
Signed-off-by: moul <[email protected]>
  • Loading branch information
moul committed Jan 15, 2025
1 parent e9d4544 commit d214d18
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 56 deletions.
130 changes: 74 additions & 56 deletions examples/gno.land/p/moul/pageable/pageable.gno
Original file line number Diff line number Diff line change
Expand Up @@ -62,74 +62,92 @@ func (p *Pager) GetPage(pageNumber int) *Page {
return p.GetPageWithSize(pageNumber, p.DefaultPageSize)
}

func (p *Pager) GetPageWithSize(pageNumber, pageSize int) *Page {
// Helper function to create a basic page
func (p *Pager) newBasePage(pageSize int) *Page {
totalItems := p.Source.Size()

// Handle invalid page size
if pageSize <= 0 {
return &Page{
TotalItems: totalItems,
TotalPages: 0,
PageSize: pageSize,
Pager: p,
}
totalPages := 0
if pageSize > 0 {
totalPages = int(math.Ceil(float64(totalItems) / float64(pageSize)))
}

totalPages := int(math.Ceil(float64(totalItems) / float64(pageSize)))

page := &Page{
return &Page{
TotalItems: totalItems,
TotalPages: totalPages,
PageSize: pageSize,
Pager: p,
}
}

func (p *Pager) GetPageWithSize(pageNumber, pageSize int) *Page {
// Handle invalid page size
if pageSize <= 0 {
return p.newBasePage(pageSize)
}

// pages without content
if pageSize < 1 {
page := p.newBasePage(pageSize)

// For empty source, return empty page with no navigation
if page.TotalItems == 0 {
page.HasPrev = false
page.HasNext = false
return page
}

// page number provided is not available
if pageNumber < 1 {
page.HasNext = totalPages > 0
page.HasNext = page.TotalPages > 0
return page
}

// page number provided is outside the range of total pages
if pageNumber > totalPages {
if pageNumber > page.TotalPages {
page.PageNumber = pageNumber
page.HasPrev = pageNumber > 0
page.HasPrev = page.TotalPages > 0
return page
}

// Calculate offset based on page number
offset := (pageNumber - 1) * pageSize
// Calculate offset and size
var offset, iterCount int
if p.Reversed {
offset = totalItems - pageSize - offset
if offset < 0 {
pageSize += offset // Reduce pageSize if we're going past the start
offset = 0
}
// For reversed order, calculate from the end
iterCount = min(pageSize, page.TotalItems-((pageNumber-1)*pageSize))
offset = max(0, page.TotalItems-(pageNumber*pageSize))
} else {
offset = (pageNumber - 1) * pageSize
iterCount = pageSize
}

// Collect items for the current page
var items []Item
iterCount := pageSize
if p.Reversed {
iterCount = -pageSize
}
p.Source.IterateByOffset(offset, iterCount, func(index interface{}, value interface{}) bool {
items = append(items, Item{
Index: index,
Value: value,
// For reversed order, iterate from the end of the array
if offset == 0 && iterCount == 1 {
// Special case for last partial page
p.Source.IterateByOffset(0, 1, func(index interface{}, value interface{}) bool {
items = append(items, Item{Index: index, Value: value})
return true
})
} else {
startIndex := page.TotalItems - offset - 1
endIndex := startIndex - iterCount + 1
for i := startIndex; i >= endIndex && i >= 0; i-- {
p.Source.IterateByOffset(i, 1, func(index interface{}, value interface{}) bool {
items = append(items, Item{Index: index, Value: value})
return true
})
}
}
} else {
p.Source.IterateByOffset(offset, iterCount, func(index interface{}, value interface{}) bool {
items = append(items, Item{Index: index, Value: value})
return false
})
return false
})
}

page.Items = items
page.PageNumber = pageNumber
page.HasPrev = pageNumber > 1
page.HasNext = pageNumber < totalPages
page.HasNext = pageNumber < page.TotalPages
return page
}

Expand Down Expand Up @@ -208,34 +226,27 @@ func (p *Page) Picker() string {
return md
}

// ParseQuery parses the URL to extract the page number and page size.
// Helper to safely parse positive integers with a default value
func parsePositiveIntOrDefault(str string, defaultValue int) int {
if str == "" {
return defaultValue
}
val, err := strconv.Atoi(str)
if err != nil || val < 1 {
return defaultValue
}
return val
}

func (p *Pager) ParseQuery(rawURL string) (int, int, error) {
u, err := url.Parse(rawURL)
if err != nil {
return 1, p.DefaultPageSize, err
}

query := u.Query()
pageNumber := 1
pageSize := p.DefaultPageSize

if p.PageQueryParam != "" {
if pageStr := query.Get(p.PageQueryParam); pageStr != "" {
pageNumber, err = strconv.Atoi(pageStr)
if err != nil || pageNumber < 1 {
pageNumber = 1
}
}
}

if p.SizeQueryParam != "" {
if sizeStr := query.Get(p.SizeQueryParam); sizeStr != "" {
pageSize, err = strconv.Atoi(sizeStr)
if err != nil || pageSize < 1 {
pageSize = p.DefaultPageSize
}
}
}
pageNumber := parsePositiveIntOrDefault(query.Get(p.PageQueryParam), 1)
pageSize := parsePositiveIntOrDefault(query.Get(p.SizeQueryParam), p.DefaultPageSize)

return pageNumber, pageSize, nil
}
Expand All @@ -246,3 +257,10 @@ func max(a, b int) int {
}
return b
}

func min(a, b int) int {
if a < b {
return a
}
return b
}
107 changes: 107 additions & 0 deletions examples/gno.land/p/moul/pageable/pageable_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,110 @@ func TestInvalidInputs(t *testing.T) {
})
}
}

func TestEmptySource(t *testing.T) {
mock := &MockPageable{items: []int{}}
pager := NewPager(mock, 10, false)

page := pager.GetPage(1)
if page.TotalItems != 0 {
t.Errorf("got total items %d, want 0", page.TotalItems)
}
if page.TotalPages != 0 {
t.Errorf("got total pages %d, want 0", page.TotalPages)
}
if page.HasNext {
t.Error("got HasNext=true, want false")
}
if page.HasPrev {
t.Error("got HasPrev=true, want false")
}
if len(page.Items) != 0 {
t.Errorf("got %d items, want 0", len(page.Items))
}
}

func TestReversedPagerEdgeCases(t *testing.T) {
mock := &MockPageable{
items: []int{1, 2, 3, 4, 5},
}
pager := NewPager(mock, 2, true)

tests := []struct {
name string
pageNum int
pageSize int
want []int
}{
{"last partial page", 3, 2, []int{1}},
{"oversized page", 1, 10, []int{5, 4, 3, 2, 1}},
{"empty result", 4, 2, nil},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
page := pager.GetPageWithSize(tt.pageNum, tt.pageSize)
got := make([]int, len(page.Items))
for i, item := range page.Items {
got[i] = item.Value.(int)
}

if len(got) != len(tt.want) {
t.Errorf("got length %d, want length %d", len(got), len(tt.want))
return
}

for i := 0; i < len(got); i++ {
if got[i] != tt.want[i] {
t.Errorf("at index %d: got %d, want %d", i, got[i], tt.want[i])
}
}
})
}
}

func TestCustomQueryParams(t *testing.T) {
mock := &MockPageable{items: []int{1, 2, 3}}
pager := NewPager(mock, 10, false)
pager.PageQueryParam = "p"
pager.SizeQueryParam = "s"

tests := []struct {
name string
url string
wantPage int
wantSize int
}{
{"custom params", "/?p=2&s=1", 2, 1},
{"old params ignored", "/?page=3&size=2", 1, 10},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
page, size, err := pager.ParseQuery(tt.url)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if page != tt.wantPage {
t.Errorf("got page=%d, want %d", page, tt.wantPage)
}
if size != tt.wantSize {
t.Errorf("got size=%d, want %d", size, tt.wantSize)
}
})
}
}

func TestMustGetPageByPath(t *testing.T) {
mock := &MockPageable{items: []int{1, 2, 3}}
pager := NewPager(mock, 10, false)

t.Run("panic on invalid url", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("expected panic but got none")
}
}()
pager.MustGetPageByPath(":%invalid")
})
}

0 comments on commit d214d18

Please sign in to comment.