mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
177 lines
3.5 KiB
Go
177 lines
3.5 KiB
Go
package bunpaginate
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
OrderAsc = iota
|
|
OrderDesc
|
|
|
|
QueryDefaultPageSize = 15
|
|
MaxPageSize = 100
|
|
|
|
QueryKeyPageSize = "pageSize"
|
|
QueryKeyCursor = "cursor"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidPageSize = errors.New("invalid 'pageSize' query param")
|
|
)
|
|
|
|
type Order int
|
|
|
|
func (o Order) String() string {
|
|
switch o {
|
|
case OrderAsc:
|
|
return "ASC"
|
|
case OrderDesc:
|
|
return "DESC"
|
|
}
|
|
panic("should not happen")
|
|
}
|
|
|
|
func (o Order) Reverse() Order {
|
|
return (o + 1) % 2
|
|
}
|
|
|
|
type ColumnPaginatedQuery[OPTIONS any] struct {
|
|
PageSize uint64 `json:"pageSize"`
|
|
Bottom *big.Int `json:"bottom"`
|
|
Column string `json:"column"`
|
|
PaginationID *big.Int `json:"paginationID"`
|
|
Order Order `json:"order"`
|
|
Options OPTIONS `json:"filters"`
|
|
Reverse bool `json:"reverse"`
|
|
}
|
|
|
|
func (q *ColumnPaginatedQuery[PAYLOAD]) EncodeAsCursor() string {
|
|
return encodeCursor(q)
|
|
}
|
|
|
|
func (a *ColumnPaginatedQuery[PAYLOAD]) WithPageSize(pageSize uint64) *ColumnPaginatedQuery[PAYLOAD] {
|
|
if pageSize != 0 {
|
|
a.PageSize = pageSize
|
|
}
|
|
|
|
return a
|
|
}
|
|
|
|
type OffsetPaginatedQuery[OPTIONS any] struct {
|
|
Offset uint64 `json:"offset"`
|
|
Order Order `json:"order"`
|
|
PageSize uint64 `json:"pageSize"`
|
|
Options OPTIONS `json:"filters"`
|
|
}
|
|
|
|
func (q *OffsetPaginatedQuery[PAYLOAD]) EncodeAsCursor() string {
|
|
return encodeCursor(q)
|
|
}
|
|
|
|
func (a *OffsetPaginatedQuery[PAYLOAD]) WithPageSize(pageSize uint64) *OffsetPaginatedQuery[PAYLOAD] {
|
|
if pageSize != 0 {
|
|
a.PageSize = pageSize
|
|
}
|
|
|
|
return a
|
|
}
|
|
|
|
func encodeCursor[T any](v *T) string {
|
|
if v == nil {
|
|
return ""
|
|
}
|
|
return EncodeCursor(*v)
|
|
}
|
|
|
|
func EncodeCursor[T any](v T) string {
|
|
data, err := json.Marshal(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return base64.RawURLEncoding.EncodeToString(data)
|
|
}
|
|
|
|
func UnmarshalCursor(v string, to any) error {
|
|
res, err := base64.RawURLEncoding.DecodeString(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := json.Unmarshal(res, &to); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type pageSizeConfiguration struct {
|
|
defaultPageSize uint64
|
|
maxPageSize uint64
|
|
}
|
|
|
|
func WithDefaultPageSize(v uint64) func(configuration *pageSizeConfiguration) {
|
|
return func(configuration *pageSizeConfiguration) {
|
|
configuration.defaultPageSize = v
|
|
}
|
|
}
|
|
|
|
func WithMaxPageSize(v uint64) func(configuration *pageSizeConfiguration) {
|
|
return func(configuration *pageSizeConfiguration) {
|
|
configuration.maxPageSize = v
|
|
}
|
|
}
|
|
|
|
func GetPageSize(r *http.Request, opts ...func(*pageSizeConfiguration)) (uint64, error) {
|
|
cfg := pageSizeConfiguration{
|
|
defaultPageSize: QueryDefaultPageSize,
|
|
maxPageSize: MaxPageSize,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(&cfg)
|
|
}
|
|
|
|
pageSizeParam := r.URL.Query().Get(QueryKeyPageSize)
|
|
if pageSizeParam == "" {
|
|
return cfg.defaultPageSize, nil
|
|
}
|
|
|
|
var pageSize uint64
|
|
var err error
|
|
if pageSizeParam != "" {
|
|
pageSize, err = strconv.ParseUint(pageSizeParam, 10, 32)
|
|
if err != nil {
|
|
return 0, ErrInvalidPageSize
|
|
}
|
|
}
|
|
|
|
if pageSize == 0 {
|
|
return cfg.defaultPageSize, nil
|
|
}
|
|
|
|
if pageSize > cfg.maxPageSize {
|
|
return cfg.maxPageSize, nil
|
|
}
|
|
|
|
return pageSize, nil
|
|
}
|
|
|
|
func Extract[Q any](r *http.Request, defaulter func() (*Q, error)) (*Q, error) {
|
|
var query Q
|
|
if r.URL.Query().Get(QueryKeyCursor) != "" {
|
|
err := UnmarshalCursor(r.URL.Query().Get(QueryKeyCursor), &query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid '%s' query param", QueryKeyCursor)
|
|
}
|
|
return &query, nil
|
|
} else {
|
|
return defaulter()
|
|
}
|
|
}
|