Skip to content

Commit 5985a90

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

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-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: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,99 @@ 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{
267+
cel.WithCompile(),
268+
cel.WithStructVariables("foo", "bar"),
269+
},
270+
data: map[string]any{
271+
"foo": map[string]any{
272+
"bar": "some-value",
273+
"baz": "some-other-value"},
274+
"bar": map[string]any{
275+
"biz": "some-third-value",
276+
},
277+
},
278+
result: "some-value/some-other-value/some-third-value",
279+
},
280+
{
281+
name: "compiled expression with string manipulation",
282+
expr: "foo.bar + '/' + foo.baz + '/' + bar.uid.split('-')[0].lowerAscii()",
283+
opts: []cel.Option{
284+
cel.WithCompile(),
285+
cel.WithStructVariables("foo", "bar"),
286+
},
287+
data: map[string]any{
288+
"foo": map[string]any{
289+
"bar": "some-value",
290+
"baz": "some-other-value"},
291+
"bar": map[string]any{
292+
"uid": "AKS2J23-DAFLSDD-123J5LS",
293+
},
294+
},
295+
result: "some-value/some-other-value/aks2j23",
296+
},
297+
} {
298+
t.Run(tt.name, func(t *testing.T) {
299+
t.Parallel()
300+
301+
g := NewWithT(t)
302+
303+
e, err := cel.NewExpression(tt.expr, tt.opts...)
304+
g.Expect(err).NotTo(HaveOccurred())
305+
306+
result, err := e.EvaluateString(context.Background(), tt.data)
307+
308+
if tt.err != "" {
309+
g.Expect(err).To(HaveOccurred())
310+
g.Expect(err.Error()).To(ContainSubstring(tt.err))
311+
} else {
312+
g.Expect(err).NotTo(HaveOccurred())
313+
g.Expect(result).To(Equal(tt.result))
314+
}
315+
})
316+
}
317+
}

0 commit comments

Comments
 (0)