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 }