67d4e0ffb6280b7ef5dde458dfe98770a4a55b4e
[arvados.git] / lib / controller / router / request.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package router
6
7 import (
8         "encoding/json"
9         "io"
10         "mime"
11         "net/http"
12         "strconv"
13         "strings"
14
15         "github.com/julienschmidt/httprouter"
16 )
17
18 // Parse req as an Arvados V1 API request and return the request
19 // parameters.
20 //
21 // If the request has a parameter whose name is attrsKey (e.g.,
22 // "collection"), it is renamed to "attrs".
23 func (rtr *router) loadRequestParams(req *http.Request, attrsKey string) (map[string]interface{}, error) {
24         err := req.ParseForm()
25         if err != nil {
26                 return nil, httpError(http.StatusBadRequest, err)
27         }
28         params := map[string]interface{}{}
29         for k, values := range req.Form {
30                 for _, v := range values {
31                         switch {
32                         case v == "null" || v == "":
33                                 params[k] = nil
34                         case strings.HasPrefix(v, "["):
35                                 var j []interface{}
36                                 err := json.Unmarshal([]byte(v), &j)
37                                 if err != nil {
38                                         return nil, err
39                                 }
40                                 params[k] = j
41                         case strings.HasPrefix(v, "{"):
42                                 var j map[string]interface{}
43                                 err := json.Unmarshal([]byte(v), &j)
44                                 if err != nil {
45                                         return nil, err
46                                 }
47                                 params[k] = j
48                         case strings.HasPrefix(v, "\""):
49                                 var j string
50                                 err := json.Unmarshal([]byte(v), &j)
51                                 if err != nil {
52                                         return nil, err
53                                 }
54                                 params[k] = j
55                         case k == "limit" || k == "offset":
56                                 params[k], err = strconv.ParseInt(v, 10, 64)
57                                 if err != nil {
58                                         return nil, err
59                                 }
60                         default:
61                                 params[k] = v
62                         }
63                         // TODO: Need to accept "?foo[]=bar&foo[]=baz"
64                         // as foo=["bar","baz"]?
65                 }
66         }
67         if ct, _, err := mime.ParseMediaType(req.Header.Get("Content-Type")); err != nil && ct == "application/json" {
68                 jsonParams := map[string]interface{}{}
69                 err := json.NewDecoder(req.Body).Decode(jsonParams)
70                 if err != nil {
71                         return nil, httpError(http.StatusBadRequest, err)
72                 }
73                 for k, v := range jsonParams {
74                         params[k] = v
75                 }
76                 if attrsKey != "" && params[attrsKey] == nil {
77                         // Copy top-level parameters from JSON request
78                         // body into params[attrsKey]. Some SDKs rely
79                         // on this Rails API feature; see
80                         // https://api.rubyonrails.org/v5.2.1/classes/ActionController/ParamsWrapper.html
81                         params[attrsKey] = jsonParams
82                 }
83         }
84
85         routeParams, _ := req.Context().Value(httprouter.ParamsKey).(httprouter.Params)
86         for _, p := range routeParams {
87                 params[p.Key] = p.Value
88         }
89
90         if v, ok := params[attrsKey]; ok && attrsKey != "" {
91                 params["attrs"] = v
92                 delete(params, attrsKey)
93         }
94         return params, nil
95 }
96
97 // Copy src to dst, using json as an intermediate format in order to
98 // invoke src's json-marshaling and dst's json-unmarshaling behaviors.
99 func (rtr *router) transcode(src interface{}, dst interface{}) error {
100         var errw error
101         pr, pw := io.Pipe()
102         go func() {
103                 defer pw.Close()
104                 errw = json.NewEncoder(pw).Encode(src)
105         }()
106         defer pr.Close()
107         err := json.NewDecoder(pr).Decode(dst)
108         if errw != nil {
109                 return errw
110         }
111         return err
112 }