diff --git a/design/design.go b/design/design.go index abce41007ea0e4a0f318f53ea93c190092d77992..3e5bc9fd35b9fe5578ec2ef4bdff0eb9aed77d2d 100644 --- a/design/design.go +++ b/design/design.go @@ -21,14 +21,17 @@ var _ = Service("policy", func() { Method("Evaluate", func() { Description("Evaluate executes a policy with the given 'data' as input.") Payload(EvaluateRequest) - Result(Any) + Result(EvaluateResult) HTTP(func() { POST("/policy/{group}/{policyName}/{version}/evaluation") Header("evaluationID:x-evaluation-id", String, "EvaluationID allows overwriting the randomly generated evaluationID", func() { Example("did:web:example.com") }) Body("input") - Response(StatusOK) + Response(StatusOK, func() { + Body("result") + Header("ETag") + }) }) }) diff --git a/design/types.go b/design/types.go index ca2d039047465aec00835e953f49b0b2d25adf86..c003fe04928b050c4c31cc88d99414daca1c16b7 100644 --- a/design/types.go +++ b/design/types.go @@ -20,7 +20,8 @@ var EvaluateRequest = Type("EvaluateRequest", func() { var EvaluateResult = Type("EvaluateResult", func() { Field(1, "result", Any, "Arbitrary JSON response.") - Required("result") + Field(2, "ETag", String, "ETag contains unique identifier of the policy evaluation and can be used to later retrieve the results from Cache.") + Required("result", "ETag") }) var LockRequest = Type("LockRequest", func() { diff --git a/gen/http/cli/policy/cli.go b/gen/http/cli/policy/cli.go index 91de667a21a728f23092a956ff5f4f5255a262b8..1a0dada69c8a50dc8e73a794608b07fbe806f37e 100644 --- a/gen/http/cli/policy/cli.go +++ b/gen/http/cli/policy/cli.go @@ -32,7 +32,7 @@ policy (evaluate|lock|unlock) // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { return os.Args[0] + ` health liveness` + "\n" + - os.Args[0] + ` policy evaluate --body "Illum ad assumenda consectetur minima voluptatibus." --group "example" --policy-name "example" --version "1.0" --evaluation-id "Ab accusamus voluptatem et est."` + "\n" + + os.Args[0] + ` policy evaluate --body "Id odio aperiam voluptatem molestias corrupti sunt." --group "example" --policy-name "example" --version "1.0" --evaluation-id "Et voluptates."` + "\n" + "" } @@ -249,7 +249,7 @@ Evaluate executes a policy with the given 'data' as input. -evaluation-id STRING: Example: - %[1]s policy evaluate --body "Illum ad assumenda consectetur minima voluptatibus." --group "example" --policy-name "example" --version "1.0" --evaluation-id "Ab accusamus voluptatem et est." + %[1]s policy evaluate --body "Id odio aperiam voluptatem molestias corrupti sunt." --group "example" --policy-name "example" --version "1.0" --evaluation-id "Et voluptates." `, os.Args[0]) } @@ -262,7 +262,7 @@ Lock a policy so that it cannot be evaluated. -version STRING: Policy version. Example: - %[1]s policy lock --group "Commodi vitae voluptatem." --policy-name "Similique quisquam optio." --version "Explicabo beatae quisquam officiis libero voluptatibus." + %[1]s policy lock --group "Explicabo beatae quisquam officiis libero voluptatibus." --policy-name "Repudiandae dolore quod." --version "Aut ut fuga quae eius minus." `, os.Args[0]) } @@ -275,6 +275,6 @@ Unlock a policy so it can be evaluated again. -version STRING: Policy version. Example: - %[1]s policy unlock --group "In illum est et hic." --policy-name "Deleniti non nihil dolor aut sed." --version "Incidunt unde consequatur voluptas dolorem nisi temporibus." + %[1]s policy unlock --group "Incidunt unde consequatur voluptas dolorem nisi temporibus." --policy-name "Omnis quasi aut consequuntur." --version "Tempore minus." `, os.Args[0]) } diff --git a/gen/http/openapi.json b/gen/http/openapi.json index b87425d258c3be0080a5d16b598585dcfe870853..929e18c02fd206e83548d6434969bb54cd788477 100644 --- a/gen/http/openapi.json +++ b/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":""},"host":"localhost:8081","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"},{"name":"x-evaluation-id","in":"header","description":"EvaluationID allows overwriting the randomly generated evaluationID","required":false,"type":"string"},{"name":"any","in":"body","description":"Input data passed to the policy execution runtime.","required":true,"schema":{"type":"string","format":"binary"}}],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"binary"}}},"schemes":["http"]}},"/policy/{group}/{policyName}/{version}/lock":{"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response."}},"schemes":["http"]},"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":""},"host":"localhost:8081","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"},{"name":"x-evaluation-id","in":"header","description":"EvaluationID allows overwriting the randomly generated evaluationID","required":false,"type":"string"},{"name":"any","in":"body","description":"Input data passed to the policy execution runtime.","required":true,"schema":{"type":"string","format":"binary"}}],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"binary"},"headers":{"ETag":{"description":"ETag contains unique identifier of the policy evaluation and can be used to later retrieve the results from Cache.","type":"string"}}}},"schemes":["http"]}},"/policy/{group}/{policyName}/{version}/lock":{"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response."}},"schemes":["http"]},"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}}}} \ No newline at end of file diff --git a/gen/http/openapi.yaml b/gen/http/openapi.yaml index 3552e210a9053c6309155fb89bafad26270d441b..4abd44f0e18a7acc6cc212b53bf305a3690d12b5 100644 --- a/gen/http/openapi.yaml +++ b/gen/http/openapi.yaml @@ -65,6 +65,11 @@ paths: schema: type: string format: binary + headers: + ETag: + description: ETag contains unique identifier of the policy evaluation + and can be used to later retrieve the results from Cache. + type: string schemes: - http /policy/{group}/{policyName}/{version}/lock: diff --git a/gen/http/openapi3.json b/gen/http/openapi3.json index ab7d6bd20a75a405353148b5eadf480c81cb12b1..9471ba5faaefea2d67b787a89ff07dcbc4507778 100644 --- a/gen/http/openapi3.json +++ b/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":"1.0"},"servers":[{"url":"http://localhost:8081","description":"Policy Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"example"},"example":"example"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"example"},"example":"example"},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"1.0"},"example":"1.0"},{"name":"x-evaluation-id","in":"header","description":"EvaluationID allows overwriting the randomly generated evaluationID","allowEmptyValue":true,"schema":{"type":"string","description":"EvaluationID allows overwriting the randomly generated evaluationID","example":"did:web:example.com"},"example":"did:web:example.com"}],"requestBody":{"description":"Input data passed to the policy execution runtime.","required":true,"content":{"application/json":{"schema":{"type":"string","description":"Input data passed to the policy execution runtime.","example":"Omnis quasi aut consequuntur.","format":"binary"},"example":"Quis quos qui earum velit illum."}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Tempore minus.","format":"binary"},"example":"Aliquam atque voluptatum ut dolorem."}}}}}},"/policy/{group}/{policyName}/{version}/lock":{"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Sint nam voluptatem ea consequatur similique et."},"example":"Non mollitia nesciunt impedit facere."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Ut commodi perspiciatis corporis."},"example":"Accusamus autem sequi."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Et nulla."},"example":"In quis nesciunt autem et."}],"responses":{"200":{"description":"OK response."}}},"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Aut facere veniam repudiandae id."},"example":"Aut minus alias."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"At eos facilis molestias in voluptas rem."},"example":"Ab accusantium ut ut aliquid sint animi."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Dolorem cumque laborum quis nesciunt."},"example":"Aut voluptas."}],"responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}}},"components":{},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"policy","description":"Policy Service provides evaluation of policies through Open Policy Agent."}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":"1.0"},"servers":[{"url":"http://localhost:8081","description":"Policy Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"example"},"example":"example"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"example"},"example":"example"},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"1.0"},"example":"1.0"},{"name":"x-evaluation-id","in":"header","description":"EvaluationID allows overwriting the randomly generated evaluationID","allowEmptyValue":true,"schema":{"type":"string","description":"EvaluationID allows overwriting the randomly generated evaluationID","example":"did:web:example.com"},"example":"did:web:example.com"}],"requestBody":{"description":"Input data passed to the policy execution runtime.","required":true,"content":{"application/json":{"schema":{"type":"string","description":"Input data passed to the policy execution runtime.","example":"Quis quos qui earum velit illum.","format":"binary"},"example":"Aut facere veniam repudiandae id."}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag contains unique identifier of the policy evaluation and can be used to later retrieve the results from Cache.","required":true,"schema":{"type":"string","description":"ETag contains unique identifier of the policy evaluation and can be used to later retrieve the results from Cache.","example":"Aut minus alias."},"example":"At eos facilis molestias in voluptas rem."}},"content":{"application/json":{"schema":{"type":"string","description":"Arbitrary JSON response.","example":"Aliquam atque voluptatum ut dolorem.","format":"binary"},"example":"Ab accusantium ut ut aliquid sint animi."}}}}}},"/policy/{group}/{policyName}/{version}/lock":{"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Et nulla."},"example":"In quis nesciunt autem et."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Sunt in et quia cum."},"example":"Commodi nemo fugiat id praesentium accusantium expedita."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Qui non quia."},"example":"Error maxime quasi quia non voluptatibus error."}],"responses":{"200":{"description":"OK response."}}},"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Dolorem cumque laborum quis nesciunt."},"example":"Aut voluptas."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Sint nam voluptatem ea consequatur similique et."},"example":"Non mollitia nesciunt impedit facere."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Ut commodi perspiciatis corporis."},"example":"Accusamus autem sequi."}],"responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}}},"components":{},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"policy","description":"Policy Service provides evaluation of policies through Open Policy Agent."}]} \ No newline at end of file diff --git a/gen/http/openapi3.yaml b/gen/http/openapi3.yaml index db303ce37efc518726e4239ffdc17e45c41e86ac..843953c8f1c2ebd994eb1769f66cb5417e58fcc8 100644 --- a/gen/http/openapi3.yaml +++ b/gen/http/openapi3.yaml @@ -68,19 +68,31 @@ paths: schema: type: string description: Input data passed to the policy execution runtime. - example: Omnis quasi aut consequuntur. + example: Quis quos qui earum velit illum. format: binary - example: Quis quos qui earum velit illum. + example: Aut facere veniam repudiandae id. responses: "200": description: OK response. + headers: + ETag: + description: ETag contains unique identifier of the policy evaluation + and can be used to later retrieve the results from Cache. + required: true + schema: + type: string + description: ETag contains unique identifier of the policy evaluation + and can be used to later retrieve the results from Cache. + example: Aut minus alias. + example: At eos facilis molestias in voluptas rem. content: application/json: schema: type: string - example: Tempore minus. + description: Arbitrary JSON response. + example: Aliquam atque voluptatum ut dolorem. format: binary - example: Aliquam atque voluptatum ut dolorem. + example: Ab accusantium ut ut aliquid sint animi. /policy/{group}/{policyName}/{version}/lock: delete: tags: @@ -96,8 +108,8 @@ paths: schema: type: string description: Policy group. - example: Sint nam voluptatem ea consequatur similique et. - example: Non mollitia nesciunt impedit facere. + example: Et nulla. + example: In quis nesciunt autem et. - name: policyName in: path description: Policy name. @@ -105,8 +117,8 @@ paths: schema: type: string description: Policy name. - example: Ut commodi perspiciatis corporis. - example: Accusamus autem sequi. + example: Sunt in et quia cum. + example: Commodi nemo fugiat id praesentium accusantium expedita. - name: version in: path description: Policy version. @@ -114,8 +126,8 @@ paths: schema: type: string description: Policy version. - example: Et nulla. - example: In quis nesciunt autem et. + example: Qui non quia. + example: Error maxime quasi quia non voluptatibus error. responses: "200": description: OK response. @@ -133,8 +145,8 @@ paths: schema: type: string description: Policy group. - example: Aut facere veniam repudiandae id. - example: Aut minus alias. + example: Dolorem cumque laborum quis nesciunt. + example: Aut voluptas. - name: policyName in: path description: Policy name. @@ -142,8 +154,8 @@ paths: schema: type: string description: Policy name. - example: At eos facilis molestias in voluptas rem. - example: Ab accusantium ut ut aliquid sint animi. + example: Sint nam voluptatem ea consequatur similique et. + example: Non mollitia nesciunt impedit facere. - name: version in: path description: Policy version. @@ -151,8 +163,8 @@ paths: schema: type: string description: Policy version. - example: Dolorem cumque laborum quis nesciunt. - example: Aut voluptas. + example: Ut commodi perspiciatis corporis. + example: Accusamus autem sequi. responses: "200": description: OK response. diff --git a/gen/http/policy/client/cli.go b/gen/http/policy/client/cli.go index 0f885b7a30e6878583c5f23773188cc47a17405b..8f66c95bada522cb67328bec3978efbae30f07ca 100644 --- a/gen/http/policy/client/cli.go +++ b/gen/http/policy/client/cli.go @@ -22,7 +22,7 @@ func BuildEvaluatePayload(policyEvaluateBody string, policyEvaluateGroup string, { err = json.Unmarshal([]byte(policyEvaluateBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "\"Illum ad assumenda consectetur minima voluptatibus.\"") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "\"Id odio aperiam voluptatem molestias corrupti sunt.\"") } } var group string diff --git a/gen/http/policy/client/encode_decode.go b/gen/http/policy/client/encode_decode.go index 309e2fd62787e93acf767008b248386215d26901..5f2cca5429dbde1f3d823d2f5b96d95dab33c7c4 100644 --- a/gen/http/policy/client/encode_decode.go +++ b/gen/http/policy/client/encode_decode.go @@ -16,6 +16,7 @@ import ( policy "code.vereign.com/gaiax/tsa/policy/gen/policy" goahttp "goa.design/goa/v3/http" + goa "goa.design/goa/v3/pkg" ) // BuildEvaluateRequest instantiates a HTTP request object with method and path @@ -94,7 +95,19 @@ func DecodeEvaluateResponse(decoder func(*http.Response) goahttp.Decoder, restor if err != nil { return nil, goahttp.ErrDecodingError("policy", "Evaluate", err) } - return body, nil + var ( + eTag string + ) + eTagRaw := resp.Header.Get("Etag") + if eTagRaw == "" { + err = goa.MergeErrors(err, goa.MissingFieldError("ETag", "header")) + } + eTag = eTagRaw + if err != nil { + return nil, goahttp.ErrValidationError("policy", "Evaluate", err) + } + res := NewEvaluateResultOK(body, eTag) + return res, nil default: body, _ := ioutil.ReadAll(resp.Body) return nil, goahttp.ErrInvalidResponse("policy", "Evaluate", resp.StatusCode, string(body)) diff --git a/gen/http/policy/client/types.go b/gen/http/policy/client/types.go index 770fa2c41568d70efaee163ca48d02f4cdfe910b..56bdc28cd9abc17145f6ce1d11d7481b939f97ad 100644 --- a/gen/http/policy/client/types.go +++ b/gen/http/policy/client/types.go @@ -6,3 +6,19 @@ // $ goa gen code.vereign.com/gaiax/tsa/policy/design package client + +import ( + policy "code.vereign.com/gaiax/tsa/policy/gen/policy" +) + +// NewEvaluateResultOK builds a "policy" service "Evaluate" endpoint result +// from a HTTP "OK" response. +func NewEvaluateResultOK(body interface{}, eTag string) *policy.EvaluateResult { + v := body + res := &policy.EvaluateResult{ + Result: v, + } + res.ETag = eTag + + return res +} diff --git a/gen/http/policy/server/encode_decode.go b/gen/http/policy/server/encode_decode.go index 3bb6113290c5725c435938692e6489b635e2955b..6b7ffabff8b3a70474a43562fbc9850805f3cd95 100644 --- a/gen/http/policy/server/encode_decode.go +++ b/gen/http/policy/server/encode_decode.go @@ -12,6 +12,7 @@ import ( "io" "net/http" + policy "code.vereign.com/gaiax/tsa/policy/gen/policy" goahttp "goa.design/goa/v3/http" goa "goa.design/goa/v3/pkg" ) @@ -20,9 +21,10 @@ import ( // policy Evaluate endpoint. func EncodeEvaluateResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, interface{}) error { return func(ctx context.Context, w http.ResponseWriter, v interface{}) error { - res, _ := v.(interface{}) + res, _ := v.(*policy.EvaluateResult) enc := encoder(ctx, w) - body := res + body := res.Result + w.Header().Set("Etag", res.ETag) w.WriteHeader(http.StatusOK) return enc.Encode(body) } diff --git a/gen/policy/client.go b/gen/policy/client.go index 4543ff51214d584abfe8a3ecaee9d5284cc7b876..554aa783b90015e0836f32342226c7507fc7d3d3 100644 --- a/gen/policy/client.go +++ b/gen/policy/client.go @@ -30,13 +30,13 @@ func NewClient(evaluate, lock, unlock goa.Endpoint) *Client { } // Evaluate calls the "Evaluate" endpoint of the "policy" service. -func (c *Client) Evaluate(ctx context.Context, p *EvaluateRequest) (res interface{}, err error) { +func (c *Client) Evaluate(ctx context.Context, p *EvaluateRequest) (res *EvaluateResult, err error) { var ires interface{} ires, err = c.EvaluateEndpoint(ctx, p) if err != nil { return } - return ires.(interface{}), nil + return ires.(*EvaluateResult), nil } // Lock calls the "Lock" endpoint of the "policy" service. diff --git a/gen/policy/service.go b/gen/policy/service.go index 4abb1bcdcde12ad0b54ddfbc412308a536c131f4..11c7536b0ad4399f3526678b18e5aa451befa31e 100644 --- a/gen/policy/service.go +++ b/gen/policy/service.go @@ -14,7 +14,7 @@ import ( // Policy Service provides evaluation of policies through Open Policy Agent. type Service interface { // Evaluate executes a policy with the given 'data' as input. - Evaluate(context.Context, *EvaluateRequest) (res interface{}, err error) + Evaluate(context.Context, *EvaluateRequest) (res *EvaluateResult, err error) // Lock a policy so that it cannot be evaluated. Lock(context.Context, *LockRequest) (err error) // Unlock a policy so it can be evaluated again. @@ -46,6 +46,15 @@ type EvaluateRequest struct { EvaluationID *string } +// EvaluateResult is the result type of the policy service Evaluate method. +type EvaluateResult struct { + // Arbitrary JSON response. + Result interface{} + // ETag contains unique identifier of the policy evaluation and can be used to + // later retrieve the results from Cache. + ETag string +} + // LockRequest is the payload type of the policy service Lock method. type LockRequest struct { // Policy group. diff --git a/internal/service/policy/service.go b/internal/service/policy/service.go index 605722b503e732b0e9829470ec00b8951991719a..7d90e62b2e00023ac1f95d9b934cf8f6036a276c 100644 --- a/internal/service/policy/service.go +++ b/internal/service/policy/service.go @@ -59,7 +59,7 @@ func New(storage Storage, queryCache RegoCache, cache Cache, logger *zap.Logger) // Evaluating the URL: `.../policies/mygroup/example/1.0/evaluation` will // return results correctly, only if the package declaration inside the policy is: // `package mygroup.example`. -func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (interface{}, error) { +func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (*policy.EvaluateResult, error) { var evaluationID string if req.EvaluationID != nil && *req.EvaluationID != "" { evaluationID = *req.EvaluationID @@ -107,12 +107,24 @@ func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (in return nil, errors.New("error storing policy result in cache") } - result := map[string]interface{}{ - "evaluationID": evaluationID, - "result": resultSet[0].Expressions[0].Value, + // If there is only a single result from the policy evaluation and it was assigned to an empty + // variable, then we'll return a custom response containing only the value of the empty variable + // without any mapping. + result := resultSet[0].Expressions[0].Value + if resultMap, ok := result.(map[string]interface{}); ok { + if len(resultMap) == 1 { + for k, v := range resultMap { + if k == "$0" { + result = v + } + } + } } - return result, nil + return &policy.EvaluateResult{ + Result: result, + ETag: evaluationID, + }, nil } // Lock a policy so that it cannot be evaluated. diff --git a/internal/service/policy/service_test.go b/internal/service/policy/service_test.go index 69c25d3c5694e39221706fcf5c08b4fed7b01ea3..ca5b4db7214c4c49889228c4804e6803fbffce15 100644 --- a/internal/service/policy/service_test.go +++ b/internal/service/policy/service_test.go @@ -23,12 +23,17 @@ func TestNew(t *testing.T) { func TestService_Evaluate(t *testing.T) { // prepare test policy source code that will be evaluated - testPolicy := `package testgroup.example allow { input.msg == "yes" }` + testPolicy := `package testgroup.example default allow = false allow { input.msg == "yes" }` + + // prepare test policy source code for the case when policy result must contain only the + // value of a blank variable assignment + testPolicyBlankAssignment := `package testgroup.example _ = {"hello":"world"}` // prepare test query that can be retrieved from rego queryCache testQuery, err := rego.New( rego.Module("example.rego", testPolicy), rego.Query("data.testgroup.example"), + rego.StrictBuiltinErrors(true), ).PrepareForEval(context.Background()) assert.NoError(t, err) @@ -50,7 +55,7 @@ func TestService_Evaluate(t *testing.T) { regocache policy.RegoCache cache policy.Cache // expected result - res interface{} + res *goapolicy.EvaluateResult errkind errors.Kind errtext string }{ @@ -68,7 +73,9 @@ func TestService_Evaluate(t *testing.T) { return nil }, }, - res: map[string]interface{}{"allow": true}, + res: &goapolicy.EvaluateResult{ + Result: map[string]interface{}{"allow": true}, + }, }, { name: "policy is not found", @@ -146,7 +153,9 @@ func TestService_Evaluate(t *testing.T) { return nil }, }, - res: map[string]interface{}{"allow": true}, + res: &goapolicy.EvaluateResult{ + Result: map[string]interface{}{"allow": true}, + }, }, { name: "policy is executed successfully, but storing the result in cache fails", @@ -176,6 +185,35 @@ func TestService_Evaluate(t *testing.T) { errkind: errors.Unknown, errtext: "error storing policy result in cache", }, + { + name: "policy with blank variable assignment is evaluated successfully", + req: testReq(), + regocache: &policyfakes.FakeRegoCache{ + GetStub: func(key string) (*rego.PreparedEvalQuery, bool) { + return nil, false + }, + }, + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{ + Name: "example", + Group: "testgroup", + Version: "1.0", + Rego: testPolicyBlankAssignment, + Locked: false, + LastUpdate: time.Now(), + }, nil + }, + }, + cache: &policyfakes.FakeCache{ + SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte) error { + return nil + }, + }, + res: &goapolicy.EvaluateResult{ + Result: map[string]interface{}{"hello": "world"}, + }, + }, } for _, test := range tests { @@ -186,10 +224,8 @@ func TestService_Evaluate(t *testing.T) { assert.Empty(t, test.errtext) assert.NotNil(t, res) - result, ok := res.(map[string]interface{}) - assert.True(t, ok) - assert.Equal(t, test.res, result["result"]) - assert.NotEmpty(t, result["evaluationID"]) + assert.Equal(t, test.res.Result, res.Result) + assert.NotEmpty(t, res.ETag) } else { e, ok := err.(*errors.Error) assert.True(t, ok)