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 }