-
Notifications
You must be signed in to change notification settings - Fork 257
/
eval.go
148 lines (123 loc) · 4.39 KB
/
eval.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package caveats
import (
"fmt"
"google.golang.org/protobuf/types/known/structpb"
"github.com/authzed/cel-go/cel"
"github.com/authzed/cel-go/common/types"
"github.com/authzed/cel-go/common/types/ref"
"github.com/authzed/cel-go/interpreter"
)
// EvaluationConfig is configuration given to an EvaluateCaveatWithConfig call.
type EvaluationConfig struct {
// MaxCost is the max cost of the caveat to be executed.
MaxCost uint64
}
// CaveatResult holds the result of evaluating a caveat.
type CaveatResult struct {
val ref.Val
details *cel.EvalDetails
parentCaveat *CompiledCaveat
contextValues map[string]any
missingVarNames []string
isPartial bool
}
// Value returns the computed value for the result.
func (cr CaveatResult) Value() bool {
if cr.isPartial {
return false
}
return cr.val.Value().(bool)
}
// IsPartial returns true if the caveat was only partially evaluated.
func (cr CaveatResult) IsPartial() bool {
return cr.isPartial
}
// PartialValue returns the partially evaluated caveat. Only applies if IsPartial is true.
func (cr CaveatResult) PartialValue() (*CompiledCaveat, error) {
if !cr.isPartial {
return nil, fmt.Errorf("result is fully evaluated")
}
expr := interpreter.PruneAst(cr.parentCaveat.ast.Expr(), cr.parentCaveat.ast.SourceInfo().GetMacroCalls(), cr.details.State())
return &CompiledCaveat{cr.parentCaveat.celEnv, cel.ParsedExprToAst(expr), cr.parentCaveat.name}, nil
}
// ContextValues returns the context values used when computing this result.
func (cr CaveatResult) ContextValues() map[string]any {
return cr.contextValues
}
// ContextStruct returns the context values used when computing this result as
// a structpb.
func (cr CaveatResult) ContextStruct() (*structpb.Struct, error) {
return ConvertContextToStruct(cr.contextValues)
}
// ExpressionString returns the human-readable expression string for the evaluated expression.
func (cr CaveatResult) ExpressionString() (string, error) {
return cr.parentCaveat.ExprString()
}
// MissingVarNames returns the name(s) of the missing variables.
func (cr CaveatResult) MissingVarNames() ([]string, error) {
if !cr.isPartial {
return nil, fmt.Errorf("result is fully evaluated")
}
return cr.missingVarNames, nil
}
// EvaluateCaveat evaluates the compiled caveat with the specified values, and returns
// the result or an error.
func EvaluateCaveat(caveat *CompiledCaveat, contextValues map[string]any) (*CaveatResult, error) {
return EvaluateCaveatWithConfig(caveat, contextValues, nil)
}
// EvaluateCaveatWithConfig evaluates the compiled caveat with the specified values, and returns
// the result or an error.
func EvaluateCaveatWithConfig(caveat *CompiledCaveat, contextValues map[string]any, config *EvaluationConfig) (*CaveatResult, error) {
env := caveat.celEnv
celopts := make([]cel.ProgramOption, 0, 3)
// Option: enables partial evaluation and state tracking for partial evaluation.
celopts = append(celopts, cel.EvalOptions(cel.OptTrackState))
celopts = append(celopts, cel.EvalOptions(cel.OptPartialEval))
// Option: Cost limit on the evaluation.
if config != nil && config.MaxCost > 0 {
celopts = append(celopts, cel.CostLimit(config.MaxCost))
}
prg, err := env.Program(caveat.ast, celopts...)
if err != nil {
return nil, err
}
// Mark any unspecified variables as unknown, to ensure that partial application
// will result in producing a type of Unknown.
activation, err := env.PartialVars(contextValues)
if err != nil {
return nil, err
}
val, details, err := prg.Eval(activation)
if err != nil {
return nil, EvaluationErr{err}
}
// If the value produced has Unknown type, then it means required context was missing.
if types.IsUnknown(val) {
unknownVal := val.(*types.Unknown)
missingVarNames := make([]string, 0, len(unknownVal.IDs()))
for _, id := range unknownVal.IDs() {
trails, ok := unknownVal.GetAttributeTrails(id)
if ok {
for _, attributeTrail := range trails {
missingVarNames = append(missingVarNames, attributeTrail.String())
}
}
}
return &CaveatResult{
val: val,
details: details,
parentCaveat: caveat,
contextValues: contextValues,
missingVarNames: missingVarNames,
isPartial: true,
}, nil
}
return &CaveatResult{
val: val,
details: details,
parentCaveat: caveat,
contextValues: contextValues,
missingVarNames: nil,
isPartial: false,
}, nil
}