Skip to content
Snippets Groups Projects
Commit b6f8ad06 authored by Lyuben Penkovski's avatar Lyuben Penkovski
Browse files

Refactor policy evaluation response to allow different levels of embedding values

This commit changes two things:
* evaluationID is no longer returned as part of the JSON response, but instead is
returned as HTTP ETag header
* if policy source code contains a single blank variable assignment then the result
from the policy evaluation will be only the value of that variable
parent 20a45ccd
No related branches found
No related tags found
No related merge requests found
Pipeline #51922 passed
......@@ -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")
})
})
})
......
......@@ -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() {
......
......@@ -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])
}
{"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
......@@ -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:
......
{"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
......@@ -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.
......
......@@ -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
......
......@@ -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))
......
......@@ -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
}
......@@ -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)
}
......
......@@ -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.
......
......@@ -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.
......
......@@ -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.
......
......@@ -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)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment