124 lines
3.3 KiB
Go
124 lines
3.3 KiB
Go
package qs
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
)
|
|
|
|
// Query error.
|
|
var (
|
|
ErrInvalidLimit = errors.New("invalid limit")
|
|
ErrInvalidOffset = errors.New("invalid offset")
|
|
ErrInvalidPage = errors.New("invalid page")
|
|
)
|
|
|
|
// Pagination represents a page size and offset for, most likely, a database query.
|
|
type Pagination struct {
|
|
Limit int `json:"limit"` // Maximum number of results in the page.
|
|
Offset int `json:"offset"` // Results offset.
|
|
Page int `json:"page,omitempty"` // Page number. This is 0 if the query specifies Offset directly.
|
|
}
|
|
|
|
// ReadPaginationOptions configures the behaviour of ReadPagination.
|
|
type ReadPaginationOptions struct {
|
|
LimitKey string // Query string key for limit. The default value is "limit"
|
|
OffsetKey string // Query string key for offset. The default value is "offset"
|
|
PageKey string // Query string key for page. The default value is "page"
|
|
|
|
MaxLimit int // If this is > 0, the limit is clamped to this maximum value
|
|
MinLimit int // The limit is clamped to this minimum value
|
|
}
|
|
|
|
// ReadPagination parses URL values into a Pagination struct.
|
|
// This function offers support for both Page and Offset values.
|
|
// If both are provided, Offset is always prioritised.
|
|
// If only Page is provided, Offset is calculated based on Limit.
|
|
func ReadPagination(values url.Values, opt *ReadPaginationOptions) (*Pagination, error) {
|
|
opt = initPaginationOptions(opt)
|
|
|
|
limit := 0
|
|
offset := 0
|
|
page := 0
|
|
var err error = nil
|
|
|
|
if values.Has(opt.LimitKey) {
|
|
limit, err = strconv.Atoi(values.Get(opt.LimitKey))
|
|
if err != nil {
|
|
return nil, ErrInvalidLimit
|
|
}
|
|
}
|
|
|
|
if opt.MaxLimit > 0 && limit > opt.MaxLimit {
|
|
limit = opt.MaxLimit
|
|
} else if limit < opt.MinLimit {
|
|
limit = opt.MinLimit
|
|
}
|
|
|
|
if values.Has(opt.OffsetKey) {
|
|
offset, err = strconv.Atoi(values.Get(opt.OffsetKey))
|
|
if err != nil {
|
|
return nil, ErrInvalidOffset
|
|
}
|
|
} else if values.Has(opt.PageKey) {
|
|
page, err = strconv.Atoi(values.Get(opt.PageKey))
|
|
if err != nil {
|
|
return nil, ErrInvalidPage
|
|
}
|
|
offset = (page - 1) * limit
|
|
}
|
|
|
|
pag := &Pagination{
|
|
Limit: limit,
|
|
Offset: offset,
|
|
Page: page,
|
|
}
|
|
return pag, nil
|
|
}
|
|
|
|
// ReadRequestPagination parses a request's query string into a slice of filters.
|
|
// This function always returns a value if it does not encounter an error.
|
|
func ReadRequestPagination(req *http.Request, opt *ReadPaginationOptions) (*Pagination, error) {
|
|
return ReadPagination(req.URL.Query(), opt)
|
|
}
|
|
|
|
// ReadStringPagination parses a query string literal into a slice of filters.
|
|
// This function always returns a value if it does not encounter an error.
|
|
func ReadStringPagination(qs string, opt *ReadPaginationOptions) (*Pagination, error) {
|
|
values, err := url.ParseQuery(qs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ReadPagination(values, opt)
|
|
}
|
|
|
|
func initPaginationOptions(opt *ReadPaginationOptions) *ReadPaginationOptions {
|
|
def := &ReadPaginationOptions{
|
|
LimitKey: "limit",
|
|
OffsetKey: "offset",
|
|
PageKey: "page",
|
|
}
|
|
|
|
if opt != nil {
|
|
if len(opt.LimitKey) > 0 {
|
|
def.LimitKey = opt.LimitKey
|
|
}
|
|
if len(opt.OffsetKey) > 0 {
|
|
def.OffsetKey = opt.OffsetKey
|
|
}
|
|
if len(opt.PageKey) > 0 {
|
|
def.PageKey = opt.PageKey
|
|
}
|
|
|
|
if opt.MaxLimit > def.MaxLimit {
|
|
def.MaxLimit = opt.MaxLimit
|
|
}
|
|
if opt.MinLimit > def.MinLimit {
|
|
def.MinLimit = opt.MinLimit
|
|
}
|
|
}
|
|
|
|
return def
|
|
}
|