Skip to content

Commit 3a77375

Browse files
authored
feat(coderd/httpapi): add QueryParamParser.JSONStringMap (#16578)
This PR provides a convenience function for parsing a `map[string]string` from a query parameter. Context: #16558 (comment)
1 parent a17cf03 commit 3a77375

File tree

4 files changed

+84
-24
lines changed

4 files changed

+84
-24
lines changed

coderd/httpapi/queryparams.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package httpapi
22

33
import (
44
"database/sql"
5+
"encoding/json"
56
"errors"
67
"fmt"
78
"net/url"
@@ -257,6 +258,23 @@ func (p *QueryParamParser) Strings(vals url.Values, def []string, queryParam str
257258
})
258259
}
259260

261+
func (p *QueryParamParser) JSONStringMap(vals url.Values, def map[string]string, queryParam string) map[string]string {
262+
v, err := parseQueryParam(p, vals, func(v string) (map[string]string, error) {
263+
var m map[string]string
264+
if err := json.NewDecoder(strings.NewReader(v)).Decode(&m); err != nil {
265+
return nil, err
266+
}
267+
return m, nil
268+
}, def, queryParam)
269+
if err != nil {
270+
p.Errors = append(p.Errors, codersdk.ValidationError{
271+
Field: queryParam,
272+
Detail: fmt.Sprintf("Query param %q must be a valid JSON object: %s", queryParam, err.Error()),
273+
})
274+
}
275+
return v
276+
}
277+
260278
// ValidEnum represents an enum that can be parsed and validated.
261279
type ValidEnum interface {
262280
// Add more types as needed (avoid importing large dependency trees).

coderd/httpapi/queryparams_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,70 @@ func TestParseQueryParams(t *testing.T) {
473473
testQueryParams(t, expParams, parser, parser.UUIDs)
474474
})
475475

476+
t.Run("JSONStringMap", func(t *testing.T) {
477+
t.Parallel()
478+
479+
expParams := []queryParamTestCase[map[string]string]{
480+
{
481+
QueryParam: "valid_map",
482+
Value: `{"key1": "value1", "key2": "value2"}`,
483+
Expected: map[string]string{
484+
"key1": "value1",
485+
"key2": "value2",
486+
},
487+
},
488+
{
489+
QueryParam: "empty",
490+
Value: "{}",
491+
Default: map[string]string{},
492+
Expected: map[string]string{},
493+
},
494+
{
495+
QueryParam: "no_value",
496+
NoSet: true,
497+
Default: map[string]string{},
498+
Expected: map[string]string{},
499+
},
500+
{
501+
QueryParam: "default",
502+
NoSet: true,
503+
Default: map[string]string{"key": "value"},
504+
Expected: map[string]string{"key": "value"},
505+
},
506+
{
507+
QueryParam: "null",
508+
Value: "null",
509+
Expected: map[string]string(nil),
510+
},
511+
{
512+
QueryParam: "undefined",
513+
Value: "undefined",
514+
Expected: map[string]string(nil),
515+
},
516+
{
517+
QueryParam: "invalid_map",
518+
Value: `{"key1": "value1", "key2": "value2"`, // missing closing brace
519+
Expected: map[string]string(nil),
520+
Default: map[string]string{},
521+
ExpectedErrorContains: `Query param "invalid_map" must be a valid JSON object: unexpected EOF`,
522+
},
523+
{
524+
QueryParam: "incorrect_type",
525+
Value: `{"key1": 1, "key2": true}`,
526+
Expected: map[string]string(nil),
527+
ExpectedErrorContains: `Query param "incorrect_type" must be a valid JSON object: json: cannot unmarshal number into Go value of type string`,
528+
},
529+
{
530+
QueryParam: "multiple_keys",
531+
Values: []string{`{"key1": "value1"}`, `{"key2": "value2"}`},
532+
Expected: map[string]string(nil),
533+
ExpectedErrorContains: `Query param "multiple_keys" provided more than once, found 2 times.`,
534+
},
535+
}
536+
parser := httpapi.NewQueryParamParser()
537+
testQueryParams(t, expParams, parser, parser.JSONStringMap)
538+
})
539+
476540
t.Run("Required", func(t *testing.T) {
477541
t.Parallel()
478542

coderd/provisionerdaemons.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
4444
p := httpapi.NewQueryParamParser()
4545
limit := p.PositiveInt32(qp, 50, "limit")
4646
ids := p.UUIDs(qp, nil, "ids")
47-
tagsRaw := p.String(qp, "", "tags")
47+
tags := p.JSONStringMap(qp, database.StringMap{}, "tags")
4848
p.ErrorExcessParams(qp)
4949
if len(p.Errors) > 0 {
5050
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@@ -54,17 +54,6 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
5454
return
5555
}
5656

57-
tags := database.StringMap{}
58-
if tagsRaw != "" {
59-
if err := tags.Scan([]byte(tagsRaw)); err != nil {
60-
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
61-
Message: "Invalid tags query parameter",
62-
Detail: err.Error(),
63-
})
64-
return
65-
}
66-
}
67-
6857
daemons, err := api.Database.GetProvisionerDaemonsWithStatusByOrganization(
6958
ctx,
7059
database.GetProvisionerDaemonsWithStatusByOrganizationParams{

coderd/provisionerjobs.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt
108108
if ids == nil {
109109
ids = p.UUIDs(qp, nil, "ids")
110110
}
111-
tagsRaw := p.String(qp, "", "tags")
111+
tags := p.JSONStringMap(qp, database.StringMap{}, "tags")
112112
p.ErrorExcessParams(qp)
113113
if len(p.Errors) > 0 {
114114
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@@ -118,17 +118,6 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt
118118
return nil, false
119119
}
120120

121-
tags := database.StringMap{}
122-
if tagsRaw != "" {
123-
if err := tags.Scan([]byte(tagsRaw)); err != nil {
124-
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
125-
Message: "Invalid tags query parameter",
126-
Detail: err.Error(),
127-
})
128-
return nil, false
129-
}
130-
}
131-
132121
jobs, err := api.Database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner(ctx, database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams{
133122
OrganizationID: org.ID,
134123
Status: slice.StringEnums[database.ProvisionerJobStatus](status),

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy