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 }