From 19450f8b74e936a409fe9666e3a06980288c179d Mon Sep 17 00:00:00 2001 From: Alam <1871216+aflmp@users.noreply.github.com> Date: Thu, 24 Mar 2022 12:55:54 -0700 Subject: [PATCH] server: add warning when input attribute is missing (#4416) This change adds an api_usage_warning when input attribute is missing in POST requests to /v1/data Fixes: #4386 Signed-off-by: Alam --- server/server.go | 4 ++ server/server_test.go | 128 +++++++++++++++++++++++++++++++++++++----- server/types/types.go | 18 ++++++ 3 files changed, 136 insertions(+), 14 deletions(-) diff --git a/server/server.go b/server/server.go index 46ba8976a3..d356f7945a 100644 --- a/server/server.go +++ b/server/server.go @@ -1630,6 +1630,10 @@ func (s *Server) v1DataPost(w http.ResponseWriter, r *http.Request) { DecisionID: decisionID, } + if input == nil { + result.Warning = types.NewWarning(types.CodeAPIUsageWarn, types.MsgInputKeyMissing) + } + if includeMetrics || includeInstrumentation { result.Metrics = m.All() } diff --git a/server/server_test.go b/server/server_test.go index 3bbd4361c7..3596c141f5 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1310,9 +1310,26 @@ p = true { false }` {http.MethodGet, "/data", "", 200, `{"result": {"testmod": {"p": [1,2,3,4], "q": {"a":1, "b": 2}}, "x": [1,2,3,4]}}`}, }}, {"post root", []tr{ - {http.MethodPost, "/data", "", 200, `{"result": {}}`}, + {http.MethodPost, "/data", "", 200, `{ + "result": {}, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, {http.MethodPut, "/policies/test", testMod2, 200, ""}, - {http.MethodPost, "/data", "", 200, `{"result": {"testmod": {"p": [1,2,3,4], "q": {"b": 2, "a": 1}}}}`}, + {http.MethodPost, "/data", "", 200, `{ + "result": { + "testmod": { + "p": [1,2,3,4], + "q": {"b": 2, "a": 1} + } + }, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, }}, {"post input", []tr{ {http.MethodPut, "/policies/test", testMod1, 200, ""}, @@ -1325,7 +1342,13 @@ p = true { false }` }`}, }}, {"post empty object", []tr{ - {http.MethodPost, "/data", `{}`, 200, `{"result": {}}`}, + {http.MethodPost, "/data", `{}`, 200, `{ + "result": {}, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, }}, {"post partial", []tr{ {http.MethodPut, "/policies/test", testMod7, 200, ""}, @@ -1348,9 +1371,20 @@ p = true { false }` }}, {"partial invalidate data", []tr{ {http.MethodPut, "/policies/test", testMod8, 200, ""}, - {http.MethodPost, "/data/testmod/p?partial", "", 200, `{}`}, + {http.MethodPost, "/data/testmod/p?partial", "", 200, `{ + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, {http.MethodPut, "/data/x", `1`, 204, ""}, - {http.MethodPost, "/data/testmod/p?partial", "", 200, `{"result": true}`}, + {http.MethodPost, "/data/testmod/p?partial", "", 200, `{ + "result": true, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, }}, {"partial ineffective fallback to normal", []tr{ {http.MethodPut, "/policies/test", testMod7, 200, ""}, @@ -1361,6 +1395,10 @@ p = true { false }` "q": [], "r": [] } + }, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" } }`}, {http.MethodPost, "/data", "", 200, `{ @@ -1370,6 +1408,10 @@ p = true { false }` "q": [], "r": [] } + }, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" } }`}, }}, @@ -1417,10 +1459,32 @@ p = true { false }` {http.MethodGet, "/data", "", 200, `{"result": {"a/b": {"c/d": 1}}}`}, {http.MethodGet, "/data/a%2Fb/c%2Fd", "", 200, `{"result": 1}`}, {http.MethodGet, "/data/a/b", "", 200, `{}`}, - {http.MethodPost, "/data/a%2Fb/c%2Fd", "", 200, `{"result": 1}`}, - {http.MethodPost, "/data/a/b", "", 200, `{}`}, + {http.MethodPost, "/data/a%2Fb/c%2Fd", "", 200, `{ + "result": 1, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, + {http.MethodPost, "/data/a/b", "", 200, `{ + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, {http.MethodPatch, "/data/a%2Fb", `[{"op": "add", "path": "/e%2Ff", "value": 2}]`, 204, ""}, - {http.MethodPost, "/data", "", 200, `{"result": {"a/b": {"c/d": 1, "e/f": 2}}}`}, + {http.MethodPost, "/data", "", 200, `{ + "result": { + "a/b": { + "c/d": 1, + "e/f": 2 + } + }, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, }}, {"strict-builtin-errors", []tr{ {http.MethodPut, "/policies/test", ` @@ -1446,7 +1510,13 @@ p = true { false }` } ] }`}, - {http.MethodPost, "/data/test/p", "", 200, `{"result": false}`}, + {http.MethodPost, "/data/test/p", "", 200, `{ + "result": false, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, {http.MethodPost, "/data/test/p?strict-builtin-errors", "", 500, `{ "code": "internal_error", "message": "error(s) occurred while evaluating query", @@ -1463,6 +1533,16 @@ p = true { false }` ] }`}, }}, + {"post api usage warning", []tr{ + {http.MethodPost, "/data", "", 200, `{ + "result": {}, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`}, + {http.MethodPost, "/data", `{"input": {}}`, 200, `{"result": {}}`}, + }}, } for _, tc := range tests { @@ -2823,7 +2903,13 @@ func TestDecisionIDs(t *testing.T) { t.Fatal(err) } - if err := f.v1("POST", "/data/undefined", "", 200, `{"decision_id": "2"}`); err != nil { + if err := f.v1("POST", "/data/undefined", "", 200, `{ + "decision_id": "2", + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`); err != nil { t.Fatal(err) } @@ -2831,7 +2917,14 @@ func TestDecisionIDs(t *testing.T) { t.Fatal(err) } - if err := f.v1("POST", "/data", "", 200, `{"decision_id": "4", "result": {}}`); err != nil { + if err := f.v1("POST", "/data", "", 200, `{ + "decision_id": "4", + "result": {}, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + } + }`); err != nil { t.Fatal(err) } @@ -2876,9 +2969,16 @@ func TestDecisionLogging(t *testing.T) { response: "{}", }, { - method: "POST", - path: "/data", - response: `{"result": {}, "decision_id": "1"}`, + method: "POST", + path: "/data", + response: `{ + "result": {}, + "warning": { + "code": "api_usage_warning", + "message": "'input' key missing from the request" + }, + "decision_id": "1" + }`, }, { method: "GET", diff --git a/server/types/types.go b/server/types/types.go index acc2da8bee..ecb4b1e961 100644 --- a/server/types/types.go +++ b/server/types/types.go @@ -149,6 +149,24 @@ type DataResponseV1 struct { Explanation TraceV1 `json:"explanation,omitempty"` Metrics MetricsV1 `json:"metrics,omitempty"` Result *interface{} `json:"result,omitempty"` + Warning *Warning `json:"warning,omitempty"` +} + +// Warning models DataResponse warnings +type Warning struct { + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +// Warning Codes +const CodeAPIUsageWarn = "api_usage_warning" + +// Warning Messages +const MsgInputKeyMissing = "'input' key missing from the request" + +// NewWarning returns a new Warning object +func NewWarning(code, message string) *Warning { + return &Warning{Code: code, Message: message} } // MetricsV1 models a collection of performance metrics.