diff --git a/docs/book/rest-api.md b/docs/book/rest-api.md index 45afb51897..2773131717 100644 --- a/docs/book/rest-api.md +++ b/docs/book/rest-api.md @@ -1084,6 +1084,10 @@ HTTP/1.1 200 OK {} ``` +#### Request Headers + +- **Content-Type: application/x-yaml**: Indicates the request body is a YAML encoded object. + #### Query Parameters - **partial** - Use the partial evaluation (optimization) when evaluating the query. @@ -1160,6 +1164,10 @@ Content-Type: application/json true ``` +#### Request Headers + +- **Content-Type: application/x-yaml**: Indicates the request body is a YAML encoded object. + #### Query Parameters - **pretty** - If parameter is `true`, response will formatted for humans. @@ -1350,6 +1358,10 @@ Content-Type: application/json "hello, alice" ``` +#### Request Headers + +- **Content-Type: application/x-yaml**: Indicates the request body is a YAML encoded object. + #### Query Parameters - **pretty** - If parameter is `true`, response will formatted for humans. diff --git a/server/server.go b/server/server.go index de06d67c0e..acc45b9162 100644 --- a/server/server.go +++ b/server/server.go @@ -515,7 +515,7 @@ func (s *Server) v0QueryPath(w http.ResponseWriter, r *http.Request, path ast.Re ctx := r.Context() m := metrics.New() diagLogger := s.evalDiagnosticPolicy(r) - input, err := readInputV0(r.Body) + input, err := readInputV0(r) if err != nil { writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, errors.Wrapf(err, "unexpected parse error for input")) return @@ -1732,8 +1732,8 @@ func makeDiagnosticsInput(r *http.Request) (map[string]interface{}, error) { return input, nil } -func readInputV0(r io.ReadCloser) (ast.Value, error) { - bs, err := ioutil.ReadAll(r) +func readInputV0(r *http.Request) (ast.Value, error) { + bs, err := ioutil.ReadAll(r.Body) if err != nil { return nil, err } @@ -1742,9 +1742,15 @@ func readInputV0(r io.ReadCloser) (ast.Value, error) { return nil, nil } var x interface{} - if err := util.UnmarshalJSON(bs, &x); err != nil { + + if strings.Contains(r.Header.Get("Content-Type"), "yaml") { + if err := util.Unmarshal(bs, &x); err != nil { + return nil, err + } + } else if err := util.UnmarshalJSON(bs, &x); err != nil { return nil, err } + return ast.InterfaceToValue(x) } diff --git a/server/server_test.go b/server/server_test.go index 7402b38bcc..e47db654d2 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -370,7 +370,6 @@ func TestDataV1Redirection(t *testing.T) { t.Fatalf("Unexpected error Location header value: %v", locHdr) } RedirectedPath := strings.SplitAfter(locHdr, "/v1")[1] - fmt.Println(RedirectedPath) if err := f.v1(http.MethodPut, RedirectedPath, `{"foo": [1,2,3]}`, 204, ""); err != nil { t.Fatalf("Unexpected error from PUT: %v", err) } @@ -386,7 +385,6 @@ func TestDataV1Redirection(t *testing.T) { t.Fatalf("Unexpected error Location header value: %v", locHdrLv) } RedirectedPathLvl := strings.SplitAfter(locHdrLv, "/v1")[1] - fmt.Println(RedirectedPathLvl) if err := f.v1(http.MethodPut, RedirectedPathLvl, `{"foo": [1,2,3]}`, 204, ""); err != nil { t.Fatalf("Unexpected error from PUT: %v", err) } @@ -691,42 +689,55 @@ p = true { false }` } } -func TestDataPostInputV1Yaml(t *testing.T) { +func TestDataYAML(t *testing.T) { testMod1 := `package testmod - import input.req1 -import input.req2 as reqx -import input.req3.attr1 +gt1 = true { req1 > 1 }` -p[x] { q[x]; not r[x] } -q[x] { data.x.y[i] = x } -r[x] { data.x.z[i] = x } -g = true { req1.a[0] = 1; reqx.b[i] = 1 } -h = true { attr1[i] > 1 } -gt1 = true { req1 > 1 } -arr = [1, 2, 3, 4] { true } -undef = true { false }` - - inputYaml := ` + inputYaml1 := ` --- input: req1: 2` + inputYaml2 := ` +--- +req1: 2` + f := newFixture(t) + if err := f.v1(http.MethodPut, "/policies/test", testMod1, 200, ""); err != nil { t.Fatalf("Unexpected error from PUT /policies/test: %v", err) } + // First JSON and then later yaml to make sure both work if err := f.v1(http.MethodPost, "/data/testmod/gt1", `{"input": {"req1": 2}}`, 200, `{"result": true}`); err != nil { t.Fatalf("Unexpected error from PUT /policies/test: %v", err) } - req := newReqV1(http.MethodPost, "/data/testmod/gt1", inputYaml) - // There is no standard for yaml mime-type + + req := newReqV1(http.MethodPost, "/data/testmod/gt1", inputYaml1) req.Header.Set("Content-Type", "application/x-yaml") if err := f.executeRequest(req, 200, `{"result": true}`); err != nil { t.Fatalf("Unexpected error from POST with yaml: %v", err) } + + req = newReqV0(http.MethodPost, "/data/testmod/gt1", inputYaml2) + req.Header.Set("Content-Type", "application/x-yaml") + if err := f.executeRequest(req, 200, `true`); err != nil { + t.Fatalf("Unexpected error from POST with yaml: %v", err) + } + + if err := f.v1(http.MethodPut, "/policies/test2", `package system +main = data.testmod.gt1`, 200, ""); err != nil { + t.Fatalf("Unexpected error from PUT /policies/test: %v", err) + } + + req = newReqUnversioned(http.MethodPost, "/", inputYaml2) + req.Header.Set("Content-Type", "application/x-yaml") + if err := f.executeRequest(req, 200, `true`); err != nil { + t.Fatalf("Unexpected error from POST with yaml: %v", err) + } + } func TestDataPutV1IfNoneMatch(t *testing.T) {