diff --git a/coderd/httpapi/queryparams.go b/coderd/httpapi/queryparams.go index 15a67caa651a8..9eb5325eca53e 100644 --- a/coderd/httpapi/queryparams.go +++ b/coderd/httpapi/queryparams.go @@ -2,6 +2,7 @@ package httpapi import ( "database/sql" + "encoding/json" "errors" "fmt" "net/url" @@ -257,6 +258,23 @@ func (p *QueryParamParser) Strings(vals url.Values, def []string, queryParam str }) } +func (p *QueryParamParser) JSONStringMap(vals url.Values, def map[string]string, queryParam string) map[string]string { + v, err := parseQueryParam(p, vals, func(v string) (map[string]string, error) { + var m map[string]string + if err := json.NewDecoder(strings.NewReader(v)).Decode(&m); err != nil { + return nil, err + } + return m, nil + }, def, queryParam) + if err != nil { + p.Errors = append(p.Errors, codersdk.ValidationError{ + Field: queryParam, + Detail: fmt.Sprintf("Query param %q must be a valid JSON object: %s", queryParam, err.Error()), + }) + } + return v +} + // ValidEnum represents an enum that can be parsed and validated. type ValidEnum interface { // Add more types as needed (avoid importing large dependency trees). diff --git a/coderd/httpapi/queryparams_test.go b/coderd/httpapi/queryparams_test.go index 16cf805534b05..e95ce292404b2 100644 --- a/coderd/httpapi/queryparams_test.go +++ b/coderd/httpapi/queryparams_test.go @@ -473,6 +473,70 @@ func TestParseQueryParams(t *testing.T) { testQueryParams(t, expParams, parser, parser.UUIDs) }) + t.Run("JSONStringMap", func(t *testing.T) { + t.Parallel() + + expParams := []queryParamTestCase[map[string]string]{ + { + QueryParam: "valid_map", + Value: `{"key1": "value1", "key2": "value2"}`, + Expected: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + }, + { + QueryParam: "empty", + Value: "{}", + Default: map[string]string{}, + Expected: map[string]string{}, + }, + { + QueryParam: "no_value", + NoSet: true, + Default: map[string]string{}, + Expected: map[string]string{}, + }, + { + QueryParam: "default", + NoSet: true, + Default: map[string]string{"key": "value"}, + Expected: map[string]string{"key": "value"}, + }, + { + QueryParam: "null", + Value: "null", + Expected: map[string]string(nil), + }, + { + QueryParam: "undefined", + Value: "undefined", + Expected: map[string]string(nil), + }, + { + QueryParam: "invalid_map", + Value: `{"key1": "value1", "key2": "value2"`, // missing closing brace + Expected: map[string]string(nil), + Default: map[string]string{}, + ExpectedErrorContains: `Query param "invalid_map" must be a valid JSON object: unexpected EOF`, + }, + { + QueryParam: "incorrect_type", + Value: `{"key1": 1, "key2": true}`, + Expected: map[string]string(nil), + ExpectedErrorContains: `Query param "incorrect_type" must be a valid JSON object: json: cannot unmarshal number into Go value of type string`, + }, + { + QueryParam: "multiple_keys", + Values: []string{`{"key1": "value1"}`, `{"key2": "value2"}`}, + Expected: map[string]string(nil), + ExpectedErrorContains: `Query param "multiple_keys" provided more than once, found 2 times.`, + }, + } + parser := httpapi.NewQueryParamParser() + testQueryParams(t, expParams, parser, parser.JSONStringMap) + }) + t.Run("Required", func(t *testing.T) { t.Parallel() diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 6495c4eb15bee..332ae3b352e0a 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -44,7 +44,7 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) { p := httpapi.NewQueryParamParser() limit := p.PositiveInt32(qp, 50, "limit") ids := p.UUIDs(qp, nil, "ids") - tagsRaw := p.String(qp, "", "tags") + tags := p.JSONStringMap(qp, database.StringMap{}, "tags") p.ErrorExcessParams(qp) if len(p.Errors) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ @@ -54,17 +54,6 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) { return } - tags := database.StringMap{} - if tagsRaw != "" { - if err := tags.Scan([]byte(tagsRaw)); err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid tags query parameter", - Detail: err.Error(), - }) - return - } - } - daemons, err := api.Database.GetProvisionerDaemonsWithStatusByOrganization( ctx, database.GetProvisionerDaemonsWithStatusByOrganizationParams{ diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index b51c38021c7ad..47963798f4d32 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -108,7 +108,7 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt if ids == nil { ids = p.UUIDs(qp, nil, "ids") } - tagsRaw := p.String(qp, "", "tags") + tags := p.JSONStringMap(qp, database.StringMap{}, "tags") p.ErrorExcessParams(qp) if len(p.Errors) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ @@ -118,17 +118,6 @@ func (api *API) handleAuthAndFetchProvisionerJobs(rw http.ResponseWriter, r *htt return nil, false } - tags := database.StringMap{} - if tagsRaw != "" { - if err := tags.Scan([]byte(tagsRaw)); err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid tags query parameter", - Detail: err.Error(), - }) - return nil, false - } - } - jobs, err := api.Database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner(ctx, database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams{ OrganizationID: org.ID, Status: slice.StringEnums[database.ProvisionerJobStatus](status),
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: