1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
16 "github.com/julienschmidt/httprouter"
19 // Parse req as an Arvados V1 API request and return the request
22 // If the request has a parameter whose name is attrsKey (e.g.,
23 // "collection"), it is renamed to "attrs".
24 func (rtr *router) loadRequestParams(req *http.Request, attrsKey string) (map[string]interface{}, error) {
25 err := req.ParseForm()
27 return nil, httpError(http.StatusBadRequest, err)
29 params := map[string]interface{}{}
30 for k, values := range req.Form {
31 for _, v := range values {
34 params[k] = stringToBool(v)
35 case v == "null" || v == "":
37 case strings.HasPrefix(v, "["):
39 err := json.Unmarshal([]byte(v), &j)
44 case strings.HasPrefix(v, "{"):
45 var j map[string]interface{}
46 err := json.Unmarshal([]byte(v), &j)
51 case strings.HasPrefix(v, "\""):
53 err := json.Unmarshal([]byte(v), &j)
58 case k == "limit" || k == "offset":
59 params[k], err = strconv.ParseInt(v, 10, 64)
66 // TODO: Need to accept "?foo[]=bar&foo[]=baz"
67 // as foo=["bar","baz"]?
71 // Decode body as JSON if Content-Type request header is
72 // missing or application/json.
73 mt := req.Header.Get("Content-Type")
74 if ct, _, err := mime.ParseMediaType(mt); err != nil && mt != "" {
75 return nil, fmt.Errorf("error parsing media type %q: %s", mt, err)
76 } else if (ct == "application/json" || mt == "") && req.ContentLength != 0 {
77 jsonParams := map[string]interface{}{}
78 err := json.NewDecoder(req.Body).Decode(&jsonParams)
80 return nil, httpError(http.StatusBadRequest, err)
82 for k, v := range jsonParams {
85 if attrsKey != "" && params[attrsKey] == nil {
86 // Copy top-level parameters from JSON request
87 // body into params[attrsKey]. Some SDKs rely
88 // on this Rails API feature; see
89 // https://api.rubyonrails.org/v5.2.1/classes/ActionController/ParamsWrapper.html
90 params[attrsKey] = jsonParams
94 routeParams, _ := req.Context().Value(httprouter.ParamsKey).(httprouter.Params)
95 for _, p := range routeParams {
96 params[p.Key] = p.Value
99 if v, ok := params[attrsKey]; ok && attrsKey != "" {
101 delete(params, attrsKey)
104 if order, ok := params["order"].(string); ok {
105 // We must accept strings ("foo, bar desc") and arrays
106 // (["foo", "bar desc"]) because RailsAPI does.
107 // Convert to an array here before trying to unmarshal
108 // into options structs.
110 delete(params, "order")
112 params["order"] = strings.Split(order, ",")
119 // Copy src to dst, using json as an intermediate format in order to
120 // invoke src's json-marshaling and dst's json-unmarshaling behaviors.
121 func (rtr *router) transcode(src interface{}, dst interface{}) error {
126 errw = json.NewEncoder(pw).Encode(src)
129 err := json.NewDecoder(pr).Decode(dst)
136 var boolParams = map[string]bool{
138 "ensure_unique_name": true,
139 "include_trash": true,
140 "include_old_versions": true,
143 func stringToBool(s string) bool {
145 case "", "false", "0":