influxdb/paging.go

210 lines
4.1 KiB
Go

package influxdb
import (
"fmt"
"net/http"
"net/url"
"strconv"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
)
const (
DefaultPageSize = 20
MaxPageSize = 100
)
// PagingFilter represents a filter containing url query params.
type PagingFilter interface {
// QueryParams returns a map containing url query params.
QueryParams() map[string][]string
}
// PagingLinks represents paging links.
type PagingLinks struct {
Prev string `json:"prev,omitempty"`
Self string `json:"self"`
Next string `json:"next,omitempty"`
}
// FindOptions represents options passed to all find methods with multiple results.
type FindOptions struct {
Limit int
Offset int
After *platform.ID
SortBy string
Descending bool
}
// GetLimit returns the resolved limit between then limit boundaries.
// Given a limit <= 0 it returns the default limit.
func (f *FindOptions) GetLimit() int {
if f == nil || f.Limit <= 0 {
return DefaultPageSize
}
if f.Limit > MaxPageSize {
return MaxPageSize
}
return f.Limit
}
// DecodeFindOptions returns a FindOptions decoded from http request.
func DecodeFindOptions(r *http.Request) (*FindOptions, error) {
opts := &FindOptions{}
qp := r.URL.Query()
if offset := qp.Get("offset"); offset != "" {
o, err := strconv.Atoi(offset)
if err != nil {
return nil, &errors.Error{
Code: errors.EInvalid,
Msg: "offset is invalid",
}
}
opts.Offset = o
}
if after := qp.Get("after"); after != "" {
id, err := platform.IDFromString(after)
if err != nil {
return nil, &errors.Error{
Code: errors.EInvalid,
Err: fmt.Errorf("decoding after: %w", err),
}
}
opts.After = id
}
if limit := qp.Get("limit"); limit != "" {
l, err := strconv.Atoi(limit)
if err != nil {
return nil, &errors.Error{
Code: errors.EInvalid,
Msg: "limit is invalid",
}
}
if l < 1 || l > MaxPageSize {
return nil, &errors.Error{
Code: errors.EInvalid,
Msg: fmt.Sprintf("limit must be between 1 and %d", MaxPageSize),
}
}
opts.Limit = l
} else {
opts.Limit = DefaultPageSize
}
if sortBy := qp.Get("sortBy"); sortBy != "" {
opts.SortBy = sortBy
}
if descending := qp.Get("descending"); descending != "" {
desc, err := strconv.ParseBool(descending)
if err != nil {
return nil, &errors.Error{
Code: errors.EInvalid,
Msg: "descending is invalid",
}
}
opts.Descending = desc
}
return opts, nil
}
func FindOptionParams(opts ...FindOptions) [][2]string {
var out [][2]string
for _, o := range opts {
for k, vals := range o.QueryParams() {
for _, v := range vals {
out = append(out, [2]string{k, v})
}
}
}
return out
}
// QueryParams returns a map containing url query params.
func (f FindOptions) QueryParams() map[string][]string {
qp := map[string][]string{
"descending": {strconv.FormatBool(f.Descending)},
"offset": {strconv.Itoa(f.Offset)},
}
if f.After != nil {
qp["after"] = []string{f.After.String()}
}
if f.Limit > 0 {
qp["limit"] = []string{strconv.Itoa(f.Limit)}
}
if f.SortBy != "" {
qp["sortBy"] = []string{f.SortBy}
}
return qp
}
// NewPagingLinks returns a PagingLinks.
// num is the number of returned results.
func NewPagingLinks(basePath string, opts FindOptions, f PagingFilter, num int) *PagingLinks {
u := url.URL{
Path: basePath,
}
values := url.Values{}
for k, vs := range f.QueryParams() {
for _, v := range vs {
if v != "" {
values.Add(k, v)
}
}
}
var self, next, prev string
for k, vs := range opts.QueryParams() {
for _, v := range vs {
if v != "" {
values.Add(k, v)
}
}
}
u.RawQuery = values.Encode()
self = u.String()
if num >= opts.Limit {
nextOffset := opts.Offset + opts.Limit
values.Set("offset", strconv.Itoa(nextOffset))
u.RawQuery = values.Encode()
next = u.String()
}
if opts.Offset > 0 {
prevOffset := opts.Offset - opts.Limit
if prevOffset < 0 {
prevOffset = 0
}
values.Set("offset", strconv.Itoa(prevOffset))
u.RawQuery = values.Encode()
prev = u.String()
}
links := &PagingLinks{
Prev: prev,
Self: self,
Next: next,
}
return links
}