-
Notifications
You must be signed in to change notification settings - Fork 132
Expand file tree
/
Copy pathexpression.go
More file actions
183 lines (156 loc) · 5.39 KB
/
expression.go
File metadata and controls
183 lines (156 loc) · 5.39 KB
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cel
import (
"context"
"fmt"
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/ext"
)
// Expression represents a parsed CEL expression.
type Expression struct {
expr string
prog cel.Program
}
// Option is a function that configures the CEL expression.
type Option func(*options)
type options struct {
variables []cel.EnvOption
compile bool
outputType *cel.Type
}
// WithStructVariables declares variables of type google.protobuf.Struct.
func WithStructVariables(vars ...string) Option {
return func(o *options) {
for _, v := range vars {
d := cel.Variable(v, cel.ObjectType("google.protobuf.Struct"))
o.variables = append(o.variables, d)
}
}
}
// WithCompile specifies that the expression should be compiled,
// which provides stricter checks at parse time, before evaluation.
func WithCompile() Option {
return func(o *options) {
o.compile = true
}
}
// WithOutputType specifies the expected output type of the expression.
func WithOutputType(t *cel.Type) Option {
return func(o *options) {
o.outputType = t
}
}
// NewExpression parses the given CEL expression and returns a new Expression.
func NewExpression(expr string, opts ...Option) (*Expression, error) {
var o options
for _, opt := range opts {
opt(&o)
}
if !o.compile && (o.outputType != nil || len(o.variables) > 0) {
return nil, fmt.Errorf("output type and variables can only be set when compiling the expression")
}
envOpts := append([]cel.EnvOption{
cel.HomogeneousAggregateLiterals(),
cel.EagerlyValidateDeclarations(true),
cel.DefaultUTCTimeZone(true),
cel.CrossTypeNumericComparisons(true),
cel.OptionalTypes(),
ext.Strings(),
ext.Sets(),
ext.Encoders(),
}, o.variables...)
env, err := cel.NewEnv(envOpts...)
if err != nil {
return nil, fmt.Errorf("failed to create CEL environment: %w", err)
}
parse := env.Parse
if o.compile {
parse = env.Compile
}
e, issues := parse(expr)
if issues != nil {
return nil, fmt.Errorf("failed to parse the CEL expression '%s': %s", expr, issues.String())
}
if w, g := o.outputType, e.OutputType(); w != nil && w != g {
return nil, fmt.Errorf("CEL expression output type mismatch: expected %s, got %s", w, g)
}
progOpts := []cel.ProgramOption{
cel.EvalOptions(cel.OptOptimize),
// 100 is the kubernetes default:
// https://github.com/kubernetes/kubernetes/blob/3f26d005571dc5903e7cebae33ada67986bc40f3/staging/src/k8s.io/apiserver/pkg/apis/cel/config.go#L33-L35
cel.InterruptCheckFrequency(100),
}
prog, err := env.Program(e, progOpts...)
if err != nil {
return nil, fmt.Errorf("failed to create CEL program: %w", err)
}
return &Expression{
expr: expr,
prog: prog,
}, nil
}
// String returns the original CEL expression string.
func (e *Expression) String() string {
return e.expr
}
// EvaluateBoolean evaluates the expression with the given data and returns the result as a boolean.
func (e *Expression) EvaluateBoolean(ctx context.Context, data map[string]any) (bool, error) {
val, _, err := e.prog.ContextEval(ctx, data)
if err != nil {
return false, fmt.Errorf("failed to evaluate the CEL expression '%s': %w", e.expr, err)
}
result, ok := val.(types.Bool)
if !ok {
return false, fmt.Errorf("failed to evaluate CEL expression as boolean: '%s'", e.expr)
}
return bool(result), nil
}
// EvaluateString evaluates the expression with the given data and returns the result as a string.
func (e *Expression) EvaluateString(ctx context.Context, data map[string]any) (string, error) {
val, _, err := e.prog.ContextEval(ctx, data)
if err != nil {
return "", fmt.Errorf("failed to evaluate the CEL expression '%s': %w", e.expr, err)
}
result, ok := val.(types.String)
if !ok {
return "", fmt.Errorf("failed to evaluate CEL expression as string: '%s'", e.expr)
}
return string(result), nil
}
// EvaluateStringSlice evaluates the expression with the given data and returns the result as []string.
func (e *Expression) EvaluateStringSlice(ctx context.Context, data map[string]any) ([]string, error) {
val, _, err := e.prog.ContextEval(ctx, data)
if err != nil {
return nil, fmt.Errorf("failed to evaluate the CEL expression '%s': %w", e.expr, err)
}
v, err := val.ConvertToNative(reflect.TypeOf([]string{}))
if err != nil {
return nil, fmt.Errorf("failed to evaluate CEL expression '%s' as []string: %w", e.expr, err)
}
result, ok := v.([]string)
if !ok {
return nil, fmt.Errorf("failed to type-assert CEL expression result as []string: '%s'", e.expr)
}
return result, nil
}
// Evaluate evaluates the expression with the given data and returns the result as any.
func (e *Expression) Evaluate(ctx context.Context, data map[string]any) (any, error) {
result, _, err := e.prog.ContextEval(ctx, data)
if err != nil {
return nil, fmt.Errorf("failed to evaluate the CEL expression '%s': %w", e.expr, err)
}
return result.Value(), nil
}