github.com/mendersoftware/go-lib-micro@v0.0.0-20240304135804-e8e39c59b148/rest_utils/paging.go (about) 1 // Copyright 2023 Northern.tech AS 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package rest_utils 16 17 import ( 18 "errors" 19 "fmt" 20 "math" 21 "net/url" 22 "strconv" 23 "strings" 24 25 "github.com/ant0ine/go-json-rest/rest" 26 27 micro_strings "github.com/mendersoftware/go-lib-micro/strings" 28 ) 29 30 // pagination constants 31 const ( 32 PageName = "page" 33 PerPageName = "per_page" 34 PageMin = 1 35 PageDefault = 1 36 PerPageMin = 1 37 PerPageMax = 500 38 PerPageDefault = 20 39 LinkHdr = "Link" 40 LinkTmpl = "<%s>; rel=\"%s\"" 41 LinkPrev = "prev" 42 LinkNext = "next" 43 LinkFirst = "first" 44 DefaultScheme = "http" 45 ) 46 47 // error msgs 48 func MsgQueryParmInvalid(name string) string { 49 return fmt.Sprintf("Can't parse param %s", name) 50 } 51 52 func MsgQueryParmMissing(name string) string { 53 return fmt.Sprintf("Missing required param %s", name) 54 } 55 56 func MsgQueryParmLimit(name string) string { 57 return fmt.Sprintf("Param %s is out of bounds", name) 58 } 59 60 func MsgQueryParmOneOf(name string, allowed []string) string { 61 return fmt.Sprintf("Param %s must be one of %v", name, allowed) 62 } 63 64 // query param parsing/validation 65 func ParseQueryParmUInt(r *rest.Request, name string, required bool, min, max, def uint64) (uint64, error) { 66 strVal := r.URL.Query().Get(name) 67 68 if strVal == "" { 69 if required { 70 return 0, errors.New(MsgQueryParmMissing(name)) 71 } else { 72 return def, nil 73 } 74 } 75 76 uintVal, err := strconv.ParseUint(strVal, 10, 32) 77 if err != nil { 78 return 0, errors.New(MsgQueryParmInvalid(name)) 79 } 80 81 if uintVal < min || uintVal > max { 82 return 0, errors.New(MsgQueryParmLimit(name)) 83 } 84 85 return uintVal, nil 86 } 87 88 func ParseQueryParmBool(r *rest.Request, name string, required bool, def *bool) (*bool, error) { 89 strVal := r.URL.Query().Get(name) 90 91 if strVal == "" { 92 if required { 93 return nil, errors.New(MsgQueryParmMissing(name)) 94 } else { 95 return def, nil 96 } 97 } 98 99 boolVal, err := strconv.ParseBool(strVal) 100 if err != nil { 101 return nil, errors.New(MsgQueryParmInvalid(name)) 102 } 103 104 return &boolVal, nil 105 } 106 107 func ParseQueryParmStr(r *rest.Request, name string, required bool, allowed []string) (string, error) { 108 val := r.URL.Query().Get(name) 109 110 if val == "" { 111 if required { 112 return "", errors.New(MsgQueryParmMissing(name)) 113 } 114 } else { 115 if allowed != nil && !micro_strings.ContainsString(val, allowed) { 116 return "", errors.New(MsgQueryParmOneOf(name, allowed)) 117 } 118 } 119 120 return val, nil 121 } 122 123 // pagination helpers 124 func ParsePagination(r *rest.Request) (uint64, uint64, error) { 125 page, err := ParseQueryParmUInt(r, PageName, false, PageMin, math.MaxUint64, PageDefault) 126 if err != nil { 127 return 0, 0, err 128 } 129 130 per_page, err := ParseQueryParmUInt(r, PerPageName, false, PerPageMin, PerPageMax, PerPageDefault) 131 if err != nil { 132 return 0, 0, err 133 } 134 135 return page, per_page, nil 136 } 137 138 func MakePageLinkHdrs(r *rest.Request, page, per_page uint64, has_next bool) []string { 139 var links []string 140 if page > 1 { 141 links = append(links, MakeLink(LinkPrev, r, page-1, per_page)) 142 } 143 144 if has_next { 145 links = append(links, MakeLink(LinkNext, r, page+1, per_page)) 146 } 147 148 links = append(links, MakeLink(LinkFirst, r, 1, per_page)) 149 return links 150 } 151 152 // MakeLink creates a relative URL for insertion in the link header URL field. 153 func MakeLink(link_type string, r *rest.Request, page, per_page uint64) string { 154 q := r.URL.Query() 155 q.Set(PageName, strconv.Itoa(int(page))) 156 q.Set(PerPageName, strconv.Itoa(int(per_page))) 157 url := url.URL{ 158 Path: r.URL.Path, 159 RawPath: r.URL.RawPath, 160 RawQuery: q.Encode(), 161 Fragment: r.URL.Fragment, 162 } 163 164 return fmt.Sprintf(LinkTmpl, url.String(), link_type) 165 } 166 167 // build URL using request 'r' and template, replace path params with 168 // elements from 'params' using lexical match as in strings.Replace() 169 func BuildURL(r *rest.Request, template string, params map[string]string) *url.URL { 170 url := r.BaseUrl() 171 172 path := template 173 for k, v := range params { 174 path = strings.Replace(path, k, v, -1) 175 } 176 url.Path = path 177 178 return url 179 }