@@ -61,7 +61,7 @@ func (p *QueryParamParser) UInt(vals url.Values, def uint64, queryParam string)
61
61
if err != nil {
62
62
p .Errors = append (p .Errors , codersdk.ValidationError {
63
63
Field : queryParam ,
64
- Detail : fmt .Sprintf ("Query param %q must be a valid positive integer (%s) " , queryParam , err .Error ()),
64
+ Detail : fmt .Sprintf ("Query param %q must be a valid positive integer: %s " , queryParam , err .Error ()),
65
65
})
66
66
return 0
67
67
}
@@ -73,7 +73,7 @@ func (p *QueryParamParser) Int(vals url.Values, def int, queryParam string) int
73
73
if err != nil {
74
74
p .Errors = append (p .Errors , codersdk.ValidationError {
75
75
Field : queryParam ,
76
- Detail : fmt .Sprintf ("Query param %q must be a valid integer (%s) " , queryParam , err .Error ()),
76
+ Detail : fmt .Sprintf ("Query param %q must be a valid integer: %s " , queryParam , err .Error ()),
77
77
})
78
78
}
79
79
return v
@@ -97,7 +97,7 @@ func (p *QueryParamParser) PositiveInt32(vals url.Values, def int32, queryParam
97
97
if err != nil {
98
98
p .Errors = append (p .Errors , codersdk.ValidationError {
99
99
Field : queryParam ,
100
- Detail : fmt .Sprintf ("Query param %q must be a valid 32-bit positive integer (%s) " , queryParam , err .Error ()),
100
+ Detail : fmt .Sprintf ("Query param %q must be a valid 32-bit positive integer: %s " , queryParam , err .Error ()),
101
101
})
102
102
}
103
103
return v
@@ -108,7 +108,7 @@ func (p *QueryParamParser) Boolean(vals url.Values, def bool, queryParam string)
108
108
if err != nil {
109
109
p .Errors = append (p .Errors , codersdk.ValidationError {
110
110
Field : queryParam ,
111
- Detail : fmt .Sprintf ("Query param %q must be a valid boolean (%s) " , queryParam , err .Error ()),
111
+ Detail : fmt .Sprintf ("Query param %q must be a valid boolean: %s " , queryParam , err .Error ()),
112
112
})
113
113
}
114
114
return v
@@ -203,9 +203,15 @@ func (p *QueryParamParser) timeWithMutate(vals url.Values, def time.Time, queryP
203
203
}
204
204
205
205
func (p * QueryParamParser ) String (vals url.Values , def string , queryParam string ) string {
206
- v , _ := parseQueryParam (p , vals , func (v string ) (string , error ) {
206
+ v , err := parseQueryParam (p , vals , func (v string ) (string , error ) {
207
207
return v , nil
208
208
}, def , queryParam )
209
+ if err != nil {
210
+ p .Errors = append (p .Errors , codersdk.ValidationError {
211
+ Field : queryParam ,
212
+ Detail : fmt .Sprintf ("Query param %q must be a valid string: %s" , queryParam , err .Error ()),
213
+ })
214
+ }
209
215
return v
210
216
}
211
217
@@ -248,13 +254,23 @@ func ParseCustom[T any](parser *QueryParamParser, vals url.Values, def T, queryP
248
254
return v
249
255
}
250
256
251
- // ParseCustomList is a function that handles csv query params.
257
+ // ParseCustomList is a function that handles csv query params or multiple values
258
+ // for a query param.
259
+ // Csv is supported as it is a common way to pass multiple values in a query param.
260
+ // Multiple values is supported (key=value&key=value2) for feature parity with GitHub issue search.
252
261
func ParseCustomList [T any ](parser * QueryParamParser , vals url.Values , def []T , queryParam string , parseFunc func (v string ) (T , error )) []T {
253
- v , err := parseQueryParam (parser , vals , func (v string ) ([]T , error ) {
254
- terms := strings .Split (v , "," )
262
+ v , err := parseQueryParamSet (parser , vals , func (set []string ) ([]T , error ) {
263
+ // Gather all terms.
264
+ allTerms := make ([]string , 0 , len (set ))
265
+ for _ , s := range set {
266
+ // If a term is a csv, break it out into individual terms.
267
+ terms := strings .Split (s , "," )
268
+ allTerms = append (allTerms , terms ... )
269
+ }
270
+
255
271
var badValues []string
256
272
var output []T
257
- for _ , s := range terms {
273
+ for _ , s := range allTerms {
258
274
good , err := parseFunc (s )
259
275
if err != nil {
260
276
badValues = append (badValues , s )
@@ -277,7 +293,27 @@ func ParseCustomList[T any](parser *QueryParamParser, vals url.Values, def []T,
277
293
return v
278
294
}
279
295
296
+ // parseQueryParam expects just 1 value set for the given query param.
280
297
func parseQueryParam [T any ](parser * QueryParamParser , vals url.Values , parse func (v string ) (T , error ), def T , queryParam string ) (T , error ) {
298
+ setParse := func (set []string ) (T , error ) {
299
+ if len (set ) > 1 {
300
+ // Set as a parser.Error rather than return an error.
301
+ // Returned errors are errors from the passed in `parse` function, and
302
+ // imply the query param value had attempted to be parsed.
303
+ // By raising the error this way, we can also more easily control how it
304
+ // is presented to the user. A returned error is wrapped with more text.
305
+ parser .Errors = append (parser .Errors , codersdk.ValidationError {
306
+ Field : queryParam ,
307
+ Detail : fmt .Sprintf ("Query param %q provided more than once, found %d times. Only provide 1 instance of this query param." , queryParam , len (set )),
308
+ })
309
+ return def , nil
310
+ }
311
+ return parse (set [0 ])
312
+ }
313
+ return parseQueryParamSet (parser , vals , setParse , def , queryParam )
314
+ }
315
+
316
+ func parseQueryParamSet [T any ](parser * QueryParamParser , vals url.Values , parse func (set []string ) (T , error ), def T , queryParam string ) (T , error ) {
281
317
parser .addParsed (queryParam )
282
318
// If the query param is required and not present, return an error.
283
319
if parser .RequiredNotEmptyParams [queryParam ] && (! vals .Has (queryParam ) || vals .Get (queryParam ) == "" ) {
@@ -293,6 +329,5 @@ func parseQueryParam[T any](parser *QueryParamParser, vals url.Values, parse fun
293
329
return def , nil
294
330
}
295
331
296
- str := vals .Get (queryParam )
297
- return parse (str )
332
+ return parse (vals [queryParam ])
298
333
}
0 commit comments