1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
16 "github.com/gorilla/mux"
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{}{}
31 // Load parameters from req.Form, which (after
32 // req.ParseForm()) includes the query string and -- when
33 // Content-Type is application/x-www-form-urlencoded -- the
35 for k, values := range req.Form {
36 // All of these form values arrive as strings, so we
37 // need some type-guessing to accept non-string
40 // Values for parameters that take ints (limit=1) or
41 // bools (include_trash=1) are parsed accordingly.
43 // "null" and "" are nil.
45 // Values that look like JSON objects, arrays, or
46 // strings are parsed as JSON.
48 // The rest are left as strings.
49 for _, v := range values {
52 params[k], err = strconv.ParseInt(v, 10, 64)
57 params[k] = stringToBool(v)
58 case v == "null" || v == "":
60 case strings.HasPrefix(v, "["):
62 err := json.Unmarshal([]byte(v), &j)
67 case strings.HasPrefix(v, "{"):
68 var j map[string]interface{}
69 err := json.Unmarshal([]byte(v), &j)
74 case strings.HasPrefix(v, "\""):
76 err := json.Unmarshal([]byte(v), &j)
84 // TODO: Need to accept "?foo[]=bar&foo[]=baz"
85 // as foo=["bar","baz"]?
89 // Decode body as JSON if Content-Type request header is
90 // missing or application/json.
91 mt := req.Header.Get("Content-Type")
92 if ct, _, err := mime.ParseMediaType(mt); err != nil && mt != "" {
93 return nil, fmt.Errorf("error parsing media type %q: %s", mt, err)
94 } else if (ct == "application/json" || mt == "") && req.ContentLength != 0 {
95 jsonParams := map[string]interface{}{}
96 err := json.NewDecoder(req.Body).Decode(&jsonParams)
98 return nil, httpError(http.StatusBadRequest, err)
100 for k, v := range jsonParams {
103 if attrsKey != "" && params[attrsKey] == nil {
104 // Copy top-level parameters from JSON request
105 // body into params[attrsKey]. Some SDKs rely
106 // on this Rails API feature; see
107 // https://api.rubyonrails.org/v5.2.1/classes/ActionController/ParamsWrapper.html
108 params[attrsKey] = jsonParams
112 for k, v := range mux.Vars(req) {
116 if v, ok := params[attrsKey]; ok && attrsKey != "" {
118 delete(params, attrsKey)
121 if order, ok := params["order"].(string); ok {
122 // We must accept strings ("foo, bar desc") and arrays
123 // (["foo", "bar desc"]) because RailsAPI does.
124 // Convert to an array here before trying to unmarshal
125 // into options structs.
127 delete(params, "order")
129 params["order"] = strings.Split(order, ",")
136 // Copy src to dst, using json as an intermediate format in order to
137 // invoke src's json-marshaling and dst's json-unmarshaling behaviors.
138 func (rtr *router) transcode(src interface{}, dst interface{}) error {
143 errw = json.NewEncoder(pw).Encode(src)
146 err := json.NewDecoder(pr).Decode(dst)
153 var intParams = map[string]bool{
158 var boolParams = map[string]bool{
160 "ensure_unique_name": true,
161 "include_trash": true,
162 "include_old_versions": true,
165 func stringToBool(s string) bool {
167 case "", "false", "0":