Files
go-omni/pagination/pagination.go

74 lines
2.1 KiB
Go

// Package pagination defines bounded offset pagination with camelCase fields.
package pagination
import (
"errors"
"net/url"
"strconv"
)
type Config struct {
DefaultSize int `json:"defaultSize"`
MaxSize int `json:"maxSize"`
MaxOffset int64 `json:"maxOffset"`
}
type Request struct {
Page int64 `json:"page"`
PageSize int `json:"pageSize"`
}
func (r Request) Offset() int64 { return (r.Page - 1) * int64(r.PageSize) }
func Parse(query url.Values, cfg Config) (Request, error) {
if cfg.DefaultSize == 0 {
cfg.DefaultSize = 20
}
if cfg.MaxSize == 0 {
cfg.MaxSize = 100
}
if cfg.MaxOffset == 0 {
cfg.MaxOffset = 1000000
}
if cfg.DefaultSize < 1 || cfg.MaxSize < cfg.DefaultSize || cfg.MaxSize > 10000 || cfg.MaxOffset < 0 {
return Request{}, errors.New("pagination: invalid configuration")
}
page, size := int64(1), int64(cfg.DefaultSize)
for key, target := range map[string]*int64{"page": &page, "pageSize": &size} {
if v, ok := query[key]; ok {
if len(v) != 1 {
return Request{}, errors.New("pagination: duplicate parameter")
}
n, err := strconv.ParseInt(v[0], 10, 64)
if err != nil || n < 1 {
return Request{}, errors.New("pagination: positive integer required")
}
*target = n
}
}
if size > int64(cfg.MaxSize) || (page-1) > cfg.MaxOffset/size {
return Request{}, errors.New("pagination: size or offset exceeds limit")
}
return Request{Page: page, PageSize: int(size)}, nil
}
type Result[T any] struct {
Items []T `json:"items"`
Page int64 `json:"page"`
PageSize int `json:"pageSize"`
Total int64 `json:"total"`
TotalPages int64 `json:"totalPages"`
}
func NewResult[T any](r Request, items []T, total int64) (Result[T], error) {
if r.Page < 1 || r.PageSize < 1 || total < 0 || len(items) > r.PageSize || int64(len(items)) > total {
return Result[T]{}, errors.New("pagination: invalid result")
}
if items == nil {
items = []T{}
}
pages := total / int64(r.PageSize)
if total%int64(r.PageSize) != 0 {
pages++
}
return Result[T]{Items: items, Page: r.Page, PageSize: r.PageSize, Total: total, TotalPages: pages}, nil
}