123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- 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
- }
|