Skip to content

Commit

Permalink
First and last (#1067)
Browse files Browse the repository at this point in the history
* Add first and last functions to the lists extension.

This adds .first() and .last() to the list extension library which can
return the first and last elements from a list.

The results are returned as Optional values.

---------

Signed-off-by: Kevin McDermott <[email protected]>
  • Loading branch information
bigkevmcd authored Nov 25, 2024
1 parent 933f926 commit 5d18e93
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cel/cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
54 changes: 54 additions & 0 deletions cel/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down
30 changes: 30 additions & 0 deletions ext/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(T)>.list() -> <Optional(T)>

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.

<list(T)>.first() -> <Optional(T)>

Examples:

[1, 2, 3].first().value() == 1
[].first().orValue('test') == 'test'

## Sets

Sets provides set relationship tests.
Expand Down

0 comments on commit 5d18e93

Please sign in to comment.