Skip to content

Commit ca33616

Browse files
support string evaluation in CEL expressions
Signed-off-by: kathleen french <[email protected]>
1 parent 8b1f852 commit ca33616

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

runtime/cel/expression.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,16 @@ func (e *Expression) EvaluateBoolean(ctx context.Context, data map[string]any) (
136136
}
137137
return bool(result), nil
138138
}
139+
140+
// EvaluateString evaluates the expression with the given data and returns the result as a string.
141+
func (e *Expression) EvaluateString(ctx context.Context, data map[string]any) (string, error) {
142+
val, _, err := e.prog.ContextEval(ctx, data)
143+
if err != nil {
144+
return "", fmt.Errorf("failed to evaluate the CEL expression '%s': %w", e.expr, err)
145+
}
146+
result, ok := val.(types.String)
147+
if !ok {
148+
return "", fmt.Errorf("failed to evaluate CEL expression as string: '%s'", e.expr)
149+
}
150+
return string(result), nil
151+
}

runtime/cel/expression_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,79 @@ func TestExpression_EvaluateBoolean(t *testing.T) {
219219
})
220220
}
221221
}
222+
223+
func TestExpression_EvaluateString(t *testing.T) {
224+
for _, tt := range []struct {
225+
name string
226+
expr string
227+
opts []cel.Option
228+
data map[string]any
229+
result string
230+
err string
231+
}{
232+
{
233+
name: "non-existent field",
234+
expr: "foo",
235+
data: map[string]any{},
236+
err: "failed to evaluate the CEL expression 'foo': no such attribute(s): foo",
237+
},
238+
{
239+
name: "string field",
240+
expr: "foo",
241+
data: map[string]any{"foo": "some-value"},
242+
result: "some-value",
243+
},
244+
{
245+
name: "non-string field",
246+
expr: "foo",
247+
data: map[string]any{"foo": 123},
248+
err: "failed to evaluate CEL expression as string: 'foo'",
249+
},
250+
{
251+
name: "nested string field",
252+
expr: "foo.bar",
253+
data: map[string]any{"foo": map[string]any{"bar": "some-value"}},
254+
result: "some-value",
255+
},
256+
{
257+
name: "compiled expression returning string",
258+
expr: "foo.bar",
259+
opts: []cel.Option{cel.WithCompile(), cel.WithStructVariables("foo")},
260+
data: map[string]any{"foo": map[string]any{"bar": "some-value"}},
261+
result: "some-value",
262+
},
263+
{
264+
name: "compiled expression returning string multiple variables",
265+
expr: "foo.bar + '/' + foo.baz + '/' + bar.biz",
266+
opts: []cel.Option{cel.WithCompile(), cel.WithStructVariables("foo", "bar")},
267+
data: map[string]any{"foo": map[string]any{"bar": "some-value", "baz": "some-other-value"}, "bar": map[string]any{"biz": "some-third-value"}},
268+
result: "some-value/some-other-value/some-third-value",
269+
},
270+
{
271+
name: "compiled expression with string manipulation",
272+
expr: "foo.bar + '/' + foo.baz + '/' + bar.uid.split('-')[0].lowerAscii()",
273+
opts: []cel.Option{cel.WithCompile(), cel.WithStructVariables("foo", "bar")},
274+
data: map[string]any{"foo": map[string]any{"bar": "some-value", "baz": "some-other-value"}, "bar": map[string]any{"uid": "AKS2J23-DAFLSDD-123J5LS"}},
275+
result: "some-value/some-other-value/aks2j23",
276+
},
277+
} {
278+
t.Run(tt.name, func(t *testing.T) {
279+
t.Parallel()
280+
281+
g := NewWithT(t)
282+
283+
e, err := cel.NewExpression(tt.expr, tt.opts...)
284+
g.Expect(err).NotTo(HaveOccurred())
285+
286+
result, err := e.EvaluateString(context.Background(), tt.data)
287+
288+
if tt.err != "" {
289+
g.Expect(err).To(HaveOccurred())
290+
g.Expect(err.Error()).To(ContainSubstring(tt.err))
291+
} else {
292+
g.Expect(err).NotTo(HaveOccurred())
293+
g.Expect(result).To(Equal(tt.result))
294+
}
295+
})
296+
}
297+
}

0 commit comments

Comments
 (0)