-
Notifications
You must be signed in to change notification settings - Fork 245
/
eval.go
159 lines (133 loc) · 4.82 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
149
150
151
152
153
154
155
156
157
158
159
package caveats
import (
"fmt"
"regexp"
"strings"
"google.golang.org/protobuf/types/known/structpb"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
var noSuchAttributeErrMessage = regexp.MustCompile(`^no such attribute: id: (.+), names: \[(.+)\]$`)
// 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.details.State())
return &CompiledCaveat{cr.parentCaveat.celEnv, cel.ParsedExprToAst(&exprpb.ParsedExpr{Expr: 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
}
pvars, err := cel.PartialVars(contextValues)
if err != nil {
return nil, err
}
val, details, err := prg.Eval(pvars)
if err != nil {
// From program.go:
// * `val`, `details`, `nil` - Successful evaluation of a non-error result.
// * `val`, `details`, `err` - Successful evaluation to an error result.
// * `nil`, `details`, `err` - Unsuccessful evaluation.
//
// NOTE: This is done in this hacky way right now because CEL does not have
// well-typed errors. We should change to a better way to detect partial
// eval if/when CEL adds properly wrapped errors.
// See: https://github.com/google/cel-go/issues/25
if val != nil && strings.Contains(err.Error(), "no such attribute") {
found := noSuchAttributeErrMessage.FindStringSubmatch(err.Error())
if found != nil {
return &CaveatResult{
val: val,
details: details,
parentCaveat: caveat,
contextValues: contextValues,
missingVarNames: strings.Split(found[2], " "),
isPartial: true,
}, nil
}
return &CaveatResult{
val: val,
details: details,
parentCaveat: caveat,
contextValues: contextValues,
missingVarNames: nil,
isPartial: true,
}, nil
}
return nil, EvaluationErr{err}
}
return &CaveatResult{
val: val,
details: details,
parentCaveat: caveat,
contextValues: contextValues,
missingVarNames: nil,
isPartial: false,
}, nil
}