diff --git a/funcs/cel_exports.go b/funcs/cel_exports.go index 80f205565..46db6d3f8 100644 --- a/funcs/cel_exports.go +++ b/funcs/cel_exports.go @@ -11,6 +11,9 @@ var CelEnvOption = []cel.EnvOption{ arnToMap("aws.arnToMap"), arnToMap("arnToMap"), // collSliceGen, + collListFirst, + collListLast, + collHasGen, collDictGen, collKeysGen, diff --git a/funcs/coll_cel.go b/funcs/coll_cel.go new file mode 100644 index 000000000..a81ceab92 --- /dev/null +++ b/funcs/coll_cel.go @@ -0,0 +1,61 @@ +package funcs + +import ( + "reflect" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" +) + +var collListFirst = cel.Function("first", + cel.MemberOverload("slice_first_any", + []*cel.Type{ + cel.ListType(cel.DynType), + }, + cel.DynType, + cel.FunctionBinding(func(args ...ref.Val) ref.Val { + list, err := args[0].ConvertToNative(reflect.TypeOf([]any{})) + if err != nil { + return types.WrapErr(err) + } + + l, ok := list.([]any) + if !ok { + return types.WrapErr(err) + } + + if len(l) == 0 { + return types.DefaultTypeAdapter.NativeToValue("") + } + + return types.DefaultTypeAdapter.NativeToValue(l[0]) + }), + ), +) + +var collListLast = cel.Function("last", + cel.MemberOverload("slice_last_any", + []*cel.Type{ + cel.ListType(cel.DynType), + }, + cel.DynType, + cel.FunctionBinding(func(args ...ref.Val) ref.Val { + list, err := args[0].ConvertToNative(reflect.TypeOf([]any{})) + if err != nil { + return types.WrapErr(err) + } + + l, ok := list.([]any) + if !ok { + return types.WrapErr(err) + } + + if len(l) == 0 { + return types.DefaultTypeAdapter.NativeToValue("") + } + + return types.DefaultTypeAdapter.NativeToValue(l[len(l)-1]) + }), + ), +) diff --git a/tests/cel_test.go b/tests/cel_test.go index 8b6fd68c6..de42ce9e6 100644 --- a/tests/cel_test.go +++ b/tests/cel_test.go @@ -80,6 +80,24 @@ func unstructure(o any) interface{} { return out } +func TestCelCollection(t *testing.T) { + runTests(t, []Test{ + {nil, "[1,2,3].first()", "1"}, + {nil, "['a', 'b', 'c'].first()", "a"}, + {nil, "[].first()", ""}, + {map[string]any{"my_list": []int{5, 6, 7}}, "my_list.first()", "5"}, + {map[string]any{"my_list": []Person{{Name: "John"}, {Name: "Bob"}}}, "my_list.first().name", "John"}, + }) + + runTests(t, []Test{ + {nil, "[1,2,3].last()", "3"}, + {nil, "['a', 'b', 'c'].last()", "c"}, + {nil, "[].last()", ""}, + {map[string]any{"my_list": []int{5, 6, 7}}, "my_list.last()", "7"}, + {map[string]any{"my_list": []Person{{Name: "John"}, {Name: "Bob"}}}, "my_list.last().name", "Bob"}, + }) +} + func TestCelAws(t *testing.T) { runTests(t, []Test{ {nil, "aws.arnToMap('arn:aws:sns:eu-west-1:123:MMS-Topic').account", "123"},