Skip to content

Commit 8955696

Browse files
authored
Merge pull request #97 from jaredzhou/expose-partial-functions
expose partial eval functions
2 parents 432607e + 0f289d7 commit 8955696

File tree

6 files changed

+431
-4
lines changed

6 files changed

+431
-4
lines changed

internal/eval/compile.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ func (e *BoolEvaler) Eval(env Env) (types.Boolean, error) {
2525

2626
func Compile(p *ast.Policy) BoolEvaler {
2727
p = foldPolicy(p)
28-
node := policyToNode(p).AsIsNode()
28+
node := PolicyToNode(p).AsIsNode()
2929
return BoolEvaler{eval: ToEval(node)}
3030
}
3131

32-
func policyToNode(p *ast.Policy) ast.Node {
32+
func PolicyToNode(p *ast.Policy) ast.Node {
3333
var nodes []ast.Node
3434
_, principalAll := p.Principal.(ast.ScopeTypeAll)
3535
_, actionAll := p.Action.(ast.ScopeTypeAll)

internal/eval/compile_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ func TestPolicyToNode(t *testing.T) {
5858
ast.Permit(),
5959
ast.True(),
6060
},
61+
{
62+
"forbid",
63+
ast.Forbid(),
64+
ast.True(),
65+
},
6166
{
6267
"eqs",
6368

@@ -87,7 +92,7 @@ func TestPolicyToNode(t *testing.T) {
8792
tt := tt
8893
t.Run(tt.name, func(t *testing.T) {
8994
t.Parallel()
90-
out := policyToNode(tt.in)
95+
out := PolicyToNode(tt.in)
9196
testutil.Equals(t, out, tt.out)
9297
})
9398
}

internal/eval/partial.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,35 @@ func extError(err error) ast.NodeTypeExtensionCall {
508508
return ast.NodeTypeExtensionCall{Name: partialErrorName, Args: []ast.IsNode{ast.NodeValue{Value: types.String(err.Error())}}}
509509
}
510510

511+
// PartialError returns a node that represents a partial error.
512+
func PartialError(err error) ast.IsNode {
513+
return ast.NodeTypeExtensionCall{Name: partialErrorName, Args: []ast.IsNode{ast.NodeValue{Value: types.String(err.Error())}}}
514+
}
515+
516+
// ToPartialError returns the error if the node is a partial error.
517+
func ToPartialError(n ast.IsNode) (error, bool) {
518+
ec, ok := n.(ast.NodeTypeExtensionCall)
519+
if !ok {
520+
return nil, false
521+
}
522+
if ec.Name != partialErrorName {
523+
return nil, false
524+
}
525+
if len(ec.Args) != 1 {
526+
return nil, false
527+
}
528+
evaler := ToEval(ec.Args[0])
529+
v, err := evaler.Eval(Env{})
530+
if err != nil {
531+
return nil, false
532+
}
533+
strVal, err := ValueToString(v)
534+
if err != nil {
535+
return nil, false
536+
}
537+
return errors.New(string(strVal)), true
538+
}
539+
511540
// partialHasEval
512541
type partialHasEval struct {
513542
object Evaler

internal/eval/partial_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,3 +1568,29 @@ func TestToVariable(t *testing.T) {
15681568
})
15691569
}
15701570
}
1571+
1572+
func TestToPartialError(t *testing.T) {
1573+
t.Parallel()
1574+
tests := []struct {
1575+
name string
1576+
in ast.IsNode
1577+
out error
1578+
ok bool
1579+
}{
1580+
{"ok", PartialError(errors.New("err")), errors.New("err"), true},
1581+
{"otherexternalcall", ast.NodeTypeExtensionCall{Name: "x", Args: []ast.IsNode{ast.NodeValue{Value: types.String("err")}}}, nil, false},
1582+
{"multipleargs", ast.NodeTypeExtensionCall{Name: partialErrorName, Args: []ast.IsNode{ast.NodeValue{Value: types.String("err")}, ast.NodeValue{Value: types.String("err2")}}}, nil, false},
1583+
{"notStringVal", ast.NodeTypeExtensionCall{Name: partialErrorName, Args: []ast.IsNode{ast.NodeValue{Value: types.Long(42)}}}, nil, false},
1584+
{"evalArgError", ast.NodeTypeExtensionCall{Name: partialErrorName, Args: []ast.IsNode{ast.Long(42).Add(ast.False()).AsIsNode()}}, nil, false},
1585+
{"othernode", ast.NodeValue{Value: types.String("err")}, nil, false},
1586+
}
1587+
for _, tt := range tests {
1588+
tt := tt
1589+
t.Run(tt.name, func(t *testing.T) {
1590+
t.Parallel()
1591+
out, ok := ToPartialError(tt.in)
1592+
testutil.Equals(t, out, tt.out)
1593+
testutil.Equals(t, ok, tt.ok)
1594+
})
1595+
}
1596+
}

x/exp/eval/eval.go

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Package eval provides a simple interface for evaluating a policy node in a given environment.
1+
// Package eval provides a simple interface for evaluating or partially evaluating a policy node in a given environment.
22
package eval
33

44
import (
@@ -15,3 +15,68 @@ func Eval(n ast.IsNode, env Env) (types.Value, error) {
1515
evaler := eval.ToEval(n)
1616
return evaler.Eval(env)
1717
}
18+
19+
// PartialPolicy returns a partially evaluated version of the policy and a boolean indicating if the policy should be kept.
20+
// (Policies that are determined to evaluate to false are not kept.)
21+
//
22+
// it is supposed to use `PartialPolicy` to partially evaluate a policy, and then use `PolicyToNode` to compile the policy to a node.
23+
// but you can also use `PartialPolicy` directly.
24+
//
25+
// All the env parts (PARC) must be specified, but you can
26+
// specify `Variable` as `Variable("principal")` or `Variable("action")` or `Variable("resource")` or `Variable("context")`.
27+
// also you can specify part of Context to be a `Variable`, such as `key` in `Context` could be
28+
// `
29+
//
30+
// context := types.NewRecord(types.RecordMap{
31+
// "key": Variable("key"),
32+
// })
33+
//
34+
// `
35+
//
36+
// when the node is kept, it can be one of three kinds:
37+
// 1. it is a `ValueNode`, and Must be `ast.True()` (e.g. `ast.True()`)
38+
// 2. it is a `Node` contains `Variable` (e.g. `ast.Permit().When(ast.Context().Access("key").Equal(ast.Long(42)))`)
39+
// 3. it is a `Node` contains `PartialError` (e.g. `ast.ExtensionCall(partialErrorName, ast.String("type error: expected comparable value, got string"))`)
40+
//
41+
// you can use the partial evaluation result `ast.Node` to do any additional work you want
42+
// for example, you can convert it to an sql query.
43+
// in which case the variable should be a column name and binary node should be an sql expression.
44+
func PartialPolicy(env Env, p *ast.Policy) (policy *ast.Policy, keep bool) {
45+
return eval.PartialPolicy(env, p)
46+
}
47+
48+
// PolicyToNode returns a node compiled from a policy.
49+
func PolicyToNode(p *ast.Policy) ast.Node {
50+
return eval.PolicyToNode(p)
51+
}
52+
53+
// PartialError returns a node that represents a partial error.
54+
func PartialError(err error) ast.IsNode {
55+
return eval.PartialError(err)
56+
}
57+
58+
// ToPartialError returns the error if the node is a partial error.
59+
func ToPartialError(n ast.IsNode) (err error, ok bool) {
60+
return eval.ToPartialError(n)
61+
}
62+
63+
// Variable is a variable in the policy.
64+
func Variable(v types.String) types.Value {
65+
return eval.Variable(v)
66+
}
67+
68+
// ToVariable converts a value to a variable.
69+
func ToVariable(v types.Value) (types.String, bool) {
70+
if ent, ok := v.(types.EntityUID); ok {
71+
return eval.ToVariable(ent)
72+
}
73+
return "", false
74+
}
75+
76+
// TypeName returns the type name of a value.
77+
func TypeName(v types.Value) string {
78+
return eval.TypeName(v)
79+
}
80+
81+
// ErrType is the error type for type errors.
82+
var ErrType = eval.ErrType

0 commit comments

Comments
 (0)