github.com/influxdata/influxdb/v2@v2.7.6/paging.go (about)

     1  package influxdb
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/url"
     7  	"strconv"
     8  
     9  	"github.com/influxdata/influxdb/v2/kit/platform"
    10  	"github.com/influxdata/influxdb/v2/kit/platform/errors"
    11  )
    12  
    13  const (
    14  	DefaultPageSize = 20
    15  	MaxPageSize     = 100
    16  )
    17  
    18  // PagingFilter represents a filter containing url query params.
    19  type PagingFilter interface {
    20  	// QueryParams returns a map containing url query params.
    21  	QueryParams() map[string][]string
    22  }
    23  
    24  // PagingLinks represents paging links.
    25  type PagingLinks struct {
    26  	Prev string `json:"prev,omitempty"`
    27  	Self string `json:"self"`
    28  	Next string `json:"next,omitempty"`
    29  }
    30  
    31  // FindOptions represents options passed to all find methods with multiple results.
    32  type FindOptions struct {
    33  	Limit      int
    34  	Offset     int
    35  	After      *platform.ID
    36  	SortBy     string
    37  	Descending bool
    38  }
    39  
    40  // GetLimit returns the resolved limit between then limit boundaries.
    41  // Given a limit <= 0 it returns the default limit.
    42  func (f *FindOptions) GetLimit() int {
    43  	if f == nil || f.Limit <= 0 {
    44  		return DefaultPageSize
    45  	}
    46  
    47  	if f.Limit > MaxPageSize {
    48  		return MaxPageSize
    49  	}
    50  
    51  	return f.Limit
    52  }
    53  
    54  // DecodeFindOptions returns a FindOptions decoded from http request.
    55  func DecodeFindOptions(r *http.Request) (*FindOptions, error) {
    56  	opts := &FindOptions{}
    57  	qp := r.URL.Query()
    58  
    59  	if offset := qp.Get("offset"); offset != "" {
    60  		o, err := strconv.Atoi(offset)
    61  		if err != nil {
    62  			return nil, &errors.Error{
    63  				Code: errors.EInvalid,
    64  				Msg:  "offset is invalid",
    65  			}
    66  		}
    67  
    68  		opts.Offset = o
    69  	}
    70  
    71  	if after := qp.Get("after"); after != "" {
    72  		id, err := platform.IDFromString(after)
    73  		if err != nil {
    74  			return nil, &errors.Error{
    75  				Code: errors.EInvalid,
    76  				Err:  fmt.Errorf("decoding after: %w", err),
    77  			}
    78  		}
    79  
    80  		opts.After = id
    81  	}
    82  
    83  	if limit := qp.Get("limit"); limit != "" {
    84  		l, err := strconv.Atoi(limit)
    85  		if err != nil {
    86  			return nil, &errors.Error{
    87  				Code: errors.EInvalid,
    88  				Msg:  "limit is invalid",
    89  			}
    90  		}
    91  
    92  		if l < 1 || l > MaxPageSize {
    93  			return nil, &errors.Error{
    94  				Code: errors.EInvalid,
    95  				Msg:  fmt.Sprintf("limit must be between 1 and %d", MaxPageSize),
    96  			}
    97  		}
    98  
    99  		opts.Limit = l
   100  	} else {
   101  		opts.Limit = DefaultPageSize
   102  	}
   103  
   104  	if sortBy := qp.Get("sortBy"); sortBy != "" {
   105  		opts.SortBy = sortBy
   106  	}
   107  
   108  	if descending := qp.Get("descending"); descending != "" {
   109  		desc, err := strconv.ParseBool(descending)
   110  		if err != nil {
   111  			return nil, &errors.Error{
   112  				Code: errors.EInvalid,
   113  				Msg:  "descending is invalid",
   114  			}
   115  		}
   116  
   117  		opts.Descending = desc
   118  	}
   119  
   120  	return opts, nil
   121  }
   122  
   123  func FindOptionParams(opts ...FindOptions) [][2]string {
   124  	var out [][2]string
   125  	for _, o := range opts {
   126  		for k, vals := range o.QueryParams() {
   127  			for _, v := range vals {
   128  				out = append(out, [2]string{k, v})
   129  			}
   130  		}
   131  	}
   132  	return out
   133  }
   134  
   135  // QueryParams returns a map containing url query params.
   136  func (f FindOptions) QueryParams() map[string][]string {
   137  	qp := map[string][]string{
   138  		"descending": {strconv.FormatBool(f.Descending)},
   139  		"offset":     {strconv.Itoa(f.Offset)},
   140  	}
   141  
   142  	if f.After != nil {
   143  		qp["after"] = []string{f.After.String()}
   144  	}
   145  
   146  	if f.Limit > 0 {
   147  		qp["limit"] = []string{strconv.Itoa(f.Limit)}
   148  	}
   149  
   150  	if f.SortBy != "" {
   151  		qp["sortBy"] = []string{f.SortBy}
   152  	}
   153  
   154  	return qp
   155  }
   156  
   157  // NewPagingLinks returns a PagingLinks.
   158  // num is the number of returned results.
   159  func NewPagingLinks(basePath string, opts FindOptions, f PagingFilter, num int) *PagingLinks {
   160  	u := url.URL{
   161  		Path: basePath,
   162  	}
   163  
   164  	values := url.Values{}
   165  	for k, vs := range f.QueryParams() {
   166  		for _, v := range vs {
   167  			if v != "" {
   168  				values.Add(k, v)
   169  			}
   170  		}
   171  	}
   172  
   173  	var self, next, prev string
   174  	for k, vs := range opts.QueryParams() {
   175  		for _, v := range vs {
   176  			if v != "" {
   177  				values.Add(k, v)
   178  			}
   179  		}
   180  	}
   181  
   182  	u.RawQuery = values.Encode()
   183  	self = u.String()
   184  
   185  	if num >= opts.Limit {
   186  		nextOffset := opts.Offset + opts.Limit
   187  		values.Set("offset", strconv.Itoa(nextOffset))
   188  		u.RawQuery = values.Encode()
   189  		next = u.String()
   190  	}
   191  
   192  	if opts.Offset > 0 {
   193  		prevOffset := opts.Offset - opts.Limit
   194  		if prevOffset < 0 {
   195  			prevOffset = 0
   196  		}
   197  		values.Set("offset", strconv.Itoa(prevOffset))
   198  		u.RawQuery = values.Encode()
   199  		prev = u.String()
   200  	}
   201  
   202  	links := &PagingLinks{
   203  		Prev: prev,
   204  		Self: self,
   205  		Next: next,
   206  	}
   207  
   208  	return links
   209  }