Files
pagination/pagination.go
T

125 lines
2.9 KiB
Go
Raw Normal View History

2025-06-07 19:23:44 +02:00
package pagination
import (
"encoding/base64"
"errors"
2025-06-07 19:23:44 +02:00
"slices"
)
var (
ErrFirstAndLastProvided = errors.New("only one of first and last can be provided")
ErrFirstNegative = errors.New("first must be greater than 0")
ErrLastNegative = errors.New("last must be greater than 0")
ErrAfterAndBeforeProvided = errors.New("only one of after and before can be provided")
ErrInvalidAfterCursor = errors.New("after is not a valid cursor")
ErrInvalidBeforeCursor = errors.New("before is not a valid cursor")
ErrInvalidCursor = errors.New("invalid cursor")
)
2025-06-07 19:23:44 +02:00
func Validate(first *int, after *string, last *int, before *string) error {
if first != nil && last != nil {
return ErrFirstAndLastProvided
2025-06-07 19:23:44 +02:00
}
if first != nil && *first < 0 {
return ErrFirstNegative
2025-06-07 19:23:44 +02:00
}
if last != nil && *last < 0 {
return ErrLastNegative
2025-06-07 19:23:44 +02:00
}
if after != nil && len(*after) > 0 && before != nil && len(*before) > 0 {
return ErrAfterAndBeforeProvided
2025-06-07 19:23:44 +02:00
}
if ValidateCursor(after) != nil {
return ErrInvalidAfterCursor
2025-06-07 19:23:44 +02:00
}
if ValidateCursor(before) != nil {
return ErrInvalidBeforeCursor
2025-06-07 19:23:44 +02:00
}
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 "", ErrInvalidCursor
2025-06-07 19:23:44 +02:00
}
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 {
2025-06-07 19:23:44 +02:00
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
if sIx < 0 {
sIx = 0
}
2025-06-07 19:23:44 +02:00
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{}
}
2025-06-07 19:23:44 +02:00
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
}