Skip to content

Commit

Permalink
Add optional.unwrap() / .unwrapOpt() function
Browse files Browse the repository at this point in the history
This function takes a list of optional values and only returns the
non-none values from the list, skipping the none values, and returning a
list of the unwrapped values directly.
  • Loading branch information
seirl committed Jan 23, 2025
1 parent 628543b commit 941059b
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 0 deletions.
8 changes: 8 additions & 0 deletions cel/cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2818,6 +2818,14 @@ func TestOptionalValuesEval(t *testing.T) {
{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))},
{expr: `optional.unwrap([])`, out: []any{}},
{expr: `optional.unwrap([optional.none(), optional.none()])`, out: []any{}},
{expr: `optional.unwrap([optional.of(42), optional.none(), optional.of("a")])`, out: []any{types.Int(42), types.String("a")}},
{expr: `optional.unwrap([optional.of(42), optional.of("a")])`, out: []any{types.Int(42), types.String("a")}},
{expr: `[].unwrapOpt()`, out: []any{}},
{expr: `[optional.none(), optional.none()].unwrapOpt()`, out: []any{}},
{expr: `[optional.of(42), optional.none(), optional.of("a")].unwrapOpt()`, out: []any{types.Int(42), types.String("a")}},
{expr: `[optional.of(42), optional.of("a")].unwrapOpt()`, out: []any{types.Int(42), types.String("a")}},
}

for i, tst := range tests {
Expand Down
38 changes: 38 additions & 0 deletions cel/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package cel

import (
"fmt"
"math"
"strconv"
"strings"
Expand All @@ -35,9 +36,11 @@ const (
optMapMacro = "optMap"
optFlatMapMacro = "optFlatMap"
hasValueFunc = "hasValue"
unwrapOptFunc = "unwrapOpt"
optionalNoneFunc = "optional.none"
optionalOfFunc = "optional.of"
optionalOfNonZeroValueFunc = "optional.ofNonZeroValue"
optionalUnwrapFunc = "optional.unwrap"
valueFunc = "value"
unusedIterVar = "#unused"
)
Expand Down Expand Up @@ -281,6 +284,16 @@ func (stdLibrary) ProgramOptions() []ProgramOption {
//
// This is syntactic sugar for msg.elements[msg.elements.size()-1].

// # Unwrap / UnwrapOpt
//
// Introduced in version: 2
//
// Returns a list of all the values that are not none in the input list of optional values.
// Can be used as optional.unwrap(List[T]) or with postfix notation: List[T].unwrapOpt()
//
// optional.unwrap([optional.of(42), optional.none()]) == [42]
// [optional.of(42), optional.none()].unwrapOpt() == [42]

func OptionalTypes(opts ...OptionalTypesOption) EnvOption {
lib := &optionalLib{version: math.MaxUint32}
for _, opt := range opts {
Expand Down Expand Up @@ -324,6 +337,7 @@ func (lib *optionalLib) CompileOptions() []EnvOption {
optionalTypeV := OptionalType(paramTypeV)
listTypeV := ListType(paramTypeV)
mapTypeKV := MapType(paramTypeK, paramTypeV)
listOptionalTypeV := ListType(optionalTypeV)

opts := []EnvOption{
// Enable the optional syntax in the parser.
Expand Down Expand Up @@ -427,6 +441,13 @@ func (lib *optionalLib) CompileOptions() []EnvOption {
}),
),
))

opts = append(opts, Function(optionalUnwrapFunc,
Overload("optional_unwrap", []*Type{listOptionalTypeV}, listTypeV,
UnaryBinding(optUnwrap))))
opts = append(opts, Function(unwrapOptFunc,
MemberOverload("optional_unwrapOpt", []*Type{listOptionalTypeV}, listTypeV,
UnaryBinding(optUnwrap))))
}

return opts
Expand Down Expand Up @@ -493,6 +514,23 @@ func optFlatMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Exp
), nil
}

func optUnwrap(value ref.Val) ref.Val {
list := value.(traits.Lister)
var unwrappedList []ref.Val
iter := list.Iterator()
for iter.HasNext() == types.True {
val := iter.Next()
opt, isOpt := val.(*types.Optional)
if !isOpt {
return types.WrapErr(fmt.Errorf("value %v is not optional", val))
}
if opt.HasValue() {
unwrappedList = append(unwrappedList, opt.GetValue())
}
}
return types.DefaultTypeAdapter.NativeToValue(unwrappedList)
}

func enableOptionalSyntax() EnvOption {
return func(e *Env) (*Env, error) {
e.prsrOpts = append(e.prsrOpts, parser.EnableOptionalSyntax(true))
Expand Down

0 comments on commit 941059b

Please sign in to comment.