109 lines
2.4 KiB
Go
109 lines
2.4 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 items == nil {
|
||
|
|
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:eIx]
|
||
|
|
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
|
||
|
|
}
|