a8a1c613a4
Adds a check for empty pages in the pagination logic to return a nil slice and empty PageInfo when there are no items to display. Updates tests to verify the behavior when no more items are available, ensuring robustness in pagination functionality.
112 lines
2.5 KiB
Go
112 lines
2.5 KiB
Go
package pagination
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"slices"
|
|
)
|
|
|
|
func Validate(first *int, after *string, last *int, before *string) error {
|
|
if first != nil && last != nil {
|
|
return fmt.Errorf("only one of first and last can be provided")
|
|
}
|
|
if first != nil && *first < 0 {
|
|
return fmt.Errorf("first must be greater than 0")
|
|
}
|
|
if last != nil && *last < 0 {
|
|
return fmt.Errorf("last must be greater than 0")
|
|
}
|
|
if after != nil && len(*after) > 0 && before != nil && len(*before) > 0 {
|
|
return fmt.Errorf("only one of after and before can be provided")
|
|
}
|
|
if ValidateCursor(after) != nil {
|
|
return fmt.Errorf("after is not a valid cursor")
|
|
}
|
|
if ValidateCursor(before) != nil {
|
|
return fmt.Errorf("before is not a valid cursor")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateCursor(cursor *string) error {
|
|
_, err := DecodeCursor(cursor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DecodeCursor(cursor *string) (string, error) {
|
|
if cursor == nil {
|
|
return "", nil
|
|
}
|
|
b64, err := base64.StdEncoding.DecodeString(*cursor)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(b64), nil
|
|
}
|
|
|
|
func EncodeCursor(cursor string) string {
|
|
return base64.StdEncoding.EncodeToString([]byte(cursor))
|
|
}
|
|
|
|
func GetPage[T any](items []T, first *int, after *string, last *int, before *string, max int, fn func(T) string) ([]T, PageInfo) {
|
|
if len(items) == 0 {
|
|
return nil, PageInfo{}
|
|
}
|
|
tmp := min(max, len(items))
|
|
sIx := 0
|
|
eIx := sIx + tmp
|
|
if first != nil {
|
|
tmp = *first
|
|
eIx = sIx + tmp
|
|
} else if last != nil {
|
|
tmp = *last
|
|
sIx = len(items) - tmp
|
|
eIx = len(items)
|
|
}
|
|
if cursor, err := DecodeCursor(after); err == nil && cursor != "" {
|
|
idx := slices.IndexFunc(items, func(item T) bool {
|
|
return fn(item) == cursor
|
|
})
|
|
idx = idx + 1
|
|
if idx+tmp >= len(items) {
|
|
tmp = len(items) - idx
|
|
}
|
|
sIx = idx
|
|
eIx = idx + tmp
|
|
} else if cursor, err := DecodeCursor(before); err == nil && cursor != "" {
|
|
idx := slices.IndexFunc(items, func(item T) bool {
|
|
return fn(item) == cursor
|
|
})
|
|
f := idx - tmp
|
|
if f < 0 {
|
|
f = 0
|
|
}
|
|
sIx = f
|
|
eIx = idx
|
|
}
|
|
page := items[sIx:min(eIx, len(items))]
|
|
if len(page) == 0 {
|
|
return nil, PageInfo{}
|
|
}
|
|
return page, PageInfo{
|
|
StartCursor: ptr(EncodeCursor(fn(page[0]))),
|
|
HasNextPage: eIx < len(items),
|
|
HasPreviousPage: sIx > 0,
|
|
EndCursor: ptr(EncodeCursor(fn(page[len(page)-1]))),
|
|
}
|
|
}
|
|
|
|
func ptr[T any](v T) *T {
|
|
return &v
|
|
}
|
|
|
|
type PageInfo struct {
|
|
StartCursor *string
|
|
HasNextPage bool
|
|
HasPreviousPage bool
|
|
EndCursor *string
|
|
}
|