X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/5ccc56b87f78f73830a04eb265dc7f04743984f5..c09adb3ad51eca81e21914582a3cbf92816a3700:/lib/controller/router/request.go diff --git a/lib/controller/router/request.go b/lib/controller/router/request.go index 8ea253e6c4..cc63794868 100644 --- a/lib/controller/router/request.go +++ b/lib/controller/router/request.go @@ -6,15 +6,55 @@ package router import ( "encoding/json" + "fmt" "io" "mime" "net/http" "strconv" "strings" - "github.com/julienschmidt/httprouter" + "github.com/gorilla/mux" ) +func guessAndParse(k, v string) (interface{}, error) { + // All of these form values arrive as strings, so we need some + // type-guessing to accept non-string inputs: + // + // Values for parameters that take ints (limit=1) or bools + // (include_trash=1) are parsed accordingly. + // + // "null" and "" are nil. + // + // Values that look like JSON objects, arrays, or strings are + // parsed as JSON. + // + // The rest are left as strings. + switch { + case intParams[k]: + return strconv.ParseInt(v, 10, 64) + case boolParams[k]: + return stringToBool(v), nil + case v == "null" || v == "": + return nil, nil + case strings.HasPrefix(v, "["): + var j []interface{} + err := json.Unmarshal([]byte(v), &j) + return j, err + case strings.HasPrefix(v, "{"): + var j map[string]interface{} + err := json.Unmarshal([]byte(v), &j) + return j, err + case strings.HasPrefix(v, "\""): + var j string + err := json.Unmarshal([]byte(v), &j) + return j, err + default: + return v, nil + } + // TODO: Need to accept "?foo[]=bar&foo[]=baz" as + // foo=["bar","baz"]? +} + // Parse req as an Arvados V1 API request and return the request // parameters. // @@ -26,54 +66,46 @@ func (rtr *router) loadRequestParams(req *http.Request, attrsKey string) (map[st return nil, httpError(http.StatusBadRequest, err) } params := map[string]interface{}{} + + // Load parameters from req.Form, which (after + // req.ParseForm()) includes the query string and -- when + // Content-Type is application/x-www-form-urlencoded -- the + // request body. for k, values := range req.Form { for _, v := range values { - switch { - case boolParams[k]: - params[k] = stringToBool(v) - case v == "null" || v == "": - params[k] = nil - case strings.HasPrefix(v, "["): - var j []interface{} - err := json.Unmarshal([]byte(v), &j) - if err != nil { - return nil, err - } - params[k] = j - case strings.HasPrefix(v, "{"): - var j map[string]interface{} - err := json.Unmarshal([]byte(v), &j) - if err != nil { - return nil, err - } - params[k] = j - case strings.HasPrefix(v, "\""): - var j string - err := json.Unmarshal([]byte(v), &j) - if err != nil { - return nil, err - } - params[k] = j - case k == "limit" || k == "offset": - params[k], err = strconv.ParseInt(v, 10, 64) - if err != nil { - return nil, err - } - default: - params[k] = v + params[k], err = guessAndParse(k, v) + if err != nil { + return nil, err } - // TODO: Need to accept "?foo[]=bar&foo[]=baz" - // as foo=["bar","baz"]? } } - if ct, _, err := mime.ParseMediaType(req.Header.Get("Content-Type")); err != nil && ct == "application/json" { + + // Decode body as JSON if Content-Type request header is + // missing or application/json. + mt := req.Header.Get("Content-Type") + if ct, _, err := mime.ParseMediaType(mt); err != nil && mt != "" { + return nil, fmt.Errorf("error parsing media type %q: %s", mt, err) + } else if (ct == "application/json" || mt == "") && req.ContentLength != 0 { jsonParams := map[string]interface{}{} - err := json.NewDecoder(req.Body).Decode(jsonParams) + err := json.NewDecoder(req.Body).Decode(&jsonParams) if err != nil { return nil, httpError(http.StatusBadRequest, err) } for k, v := range jsonParams { - params[k] = v + switch v := v.(type) { + case string: + // The Ruby "arv" cli tool sends a + // JSON-encode params map with + // JSON-encoded values. + dec, err := guessAndParse(k, v) + if err != nil { + return nil, err + } + jsonParams[k] = dec + params[k] = dec + default: + params[k] = v + } } if attrsKey != "" && params[attrsKey] == nil { // Copy top-level parameters from JSON request @@ -84,15 +116,27 @@ func (rtr *router) loadRequestParams(req *http.Request, attrsKey string) (map[st } } - routeParams, _ := req.Context().Value(httprouter.ParamsKey).(httprouter.Params) - for _, p := range routeParams { - params[p.Key] = p.Value + for k, v := range mux.Vars(req) { + params[k] = v } if v, ok := params[attrsKey]; ok && attrsKey != "" { params["attrs"] = v delete(params, attrsKey) } + + if order, ok := params["order"].(string); ok { + // We must accept strings ("foo, bar desc") and arrays + // (["foo", "bar desc"]) because RailsAPI does. + // Convert to an array here before trying to unmarshal + // into options structs. + if order == "" { + delete(params, "order") + } else { + params["order"] = strings.Split(order, ",") + } + } + return params, nil } @@ -113,7 +157,13 @@ func (rtr *router) transcode(src interface{}, dst interface{}) error { return err } +var intParams = map[string]bool{ + "limit": true, + "offset": true, +} + var boolParams = map[string]bool{ + "distinct": true, "ensure_unique_name": true, "include_trash": true, "include_old_versions": true,