From 941059b0fe9413b73c67e5588e59ed6cdc493d15 Mon Sep 17 00:00:00 2001 From: Antoine Pietri Date: Fri, 10 Jan 2025 14:34:37 +0000 Subject: [PATCH] Add optional.unwrap() / .unwrapOpt() function 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. --- cel/cel_test.go | 8 ++++++++ cel/library.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/cel/cel_test.go b/cel/cel_test.go index 250433f1..f1c4385d 100644 --- a/cel/cel_test.go +++ b/cel/cel_test.go @@ -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 { diff --git a/cel/library.go b/cel/library.go index e36590e2..c0aef501 100644 --- a/cel/library.go +++ b/cel/library.go @@ -15,6 +15,7 @@ package cel import ( + "fmt" "math" "strconv" "strings" @@ -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" ) @@ -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 { @@ -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. @@ -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 @@ -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))