diff --git a/cel/cel_test.go b/cel/cel_test.go index b27bd6d1..304007db 100644 --- a/cel/cel_test.go +++ b/cel/cel_test.go @@ -2743,6 +2743,10 @@ func TestOptionalValuesEval(t *testing.T) { RepeatedString: []string{"greetings", "world"}, }, }, + {expr: `[].first()`, out: types.OptionalNone}, + {expr: `['a','b','c'].first()`, out: types.OptionalOf(types.String("a"))}, + {expr: `[].last()`, out: types.OptionalNone}, + {expr: `[1, 2, 3].last()`, out: types.OptionalOf(types.Int(3))}, } for i, tst := range tests { diff --git a/cel/library.go b/cel/library.go index be59f1b0..3dc8594b 100644 --- a/cel/library.go +++ b/cel/library.go @@ -260,6 +260,27 @@ func (stdLibrary) ProgramOptions() []ProgramOption { // be expressed with `optMap`. // // msg.?elements.optFlatMap(e, e[?0]) // return the first element if present. + +// # First +// +// Introduced in version: 2 +// +// Returns an optional with the first value from the right hand list, or +// optional.None. +// +// [1, 2, 3].first().value() == 1 + +// # Last +// +// Introduced in version: 2 +// +// Returns an optional with the last value from the right hand list, or +// optional.None. +// +// [1, 2, 3].last().value() == 3 +// +// This is syntactic sugar for msg.elements[msg.elements.size()-1]. + func OptionalTypes(opts ...OptionalTypesOption) EnvOption { lib := &optionalLib{version: math.MaxUint32} for _, opt := range opts { @@ -375,6 +396,39 @@ func (lib *optionalLib) CompileOptions() []EnvOption { if lib.version >= 1 { opts = append(opts, Macros(ReceiverMacro(optFlatMapMacro, 2, optFlatMap))) } + + if lib.version >= 2 { + opts = append(opts, Function("last", + MemberOverload("list_last", []*Type{listTypeV}, optionalTypeV, + UnaryBinding(func(v ref.Val) ref.Val { + list := v.(traits.Lister) + sz := list.Size().Value().(int64) + + if sz == 0 { + return types.OptionalNone + } + + return types.OptionalOf(list.Get(types.Int(sz - 1))) + }), + ), + )) + + opts = append(opts, Function("first", + MemberOverload("list_first", []*Type{listTypeV}, optionalTypeV, + UnaryBinding(func(v ref.Val) ref.Val { + list := v.(traits.Lister) + sz := list.Size().Value().(int64) + + if sz == 0 { + return types.OptionalNone + } + + return types.OptionalOf(list.Get(types.Int(0))) + }), + ), + )) + } + return opts } diff --git a/ext/README.md b/ext/README.md index 07e544d0..511d0a47 100644 --- a/ext/README.md +++ b/ext/README.md @@ -500,6 +500,36 @@ Examples: ].sortBy(e, e.score).map(e, e.name) == ["bar", "foo", "baz"] +### Last + +**Introduced in the OptionalTypes library version 2** + +Returns an optional with the last value from the list or `optional.None` if the +list is empty. + + .list() -> + +Examples: + + [1, 2, 3].list().value() == 3 + [].last().orValue('test') == 'test' + +This is syntactic sugar for list[list.size()-1]. + +### First + +**Introduced in the OptionalTypes library version 2** + +Returns an optional with the first value from the list or `optional.None` if the +list is empty. + + .first() -> + +Examples: + + [1, 2, 3].first().value() == 1 + [].first().orValue('test') == 'test' + ## Sets Sets provides set relationship tests.