Skip to content

Commit 7621362

Browse files
authored
Add optional.unwrap() / .unwrapOpt() function (#1103)
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.
1 parent 9f925d8 commit 7621362

File tree

2 files changed

+46
-0
lines changed

2 files changed

+46
-0
lines changed

cel/cel_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2818,6 +2818,14 @@ func TestOptionalValuesEval(t *testing.T) {
28182818
{expr: `['a','b','c'].first()`, out: types.OptionalOf(types.String("a"))},
28192819
{expr: `[].last()`, out: types.OptionalNone},
28202820
{expr: `[1, 2, 3].last()`, out: types.OptionalOf(types.Int(3))},
2821+
{expr: `optional.unwrap([])`, out: []any{}},
2822+
{expr: `optional.unwrap([optional.none(), optional.none()])`, out: []any{}},
2823+
{expr: `optional.unwrap([optional.of(42), optional.none(), optional.of("a")])`, out: []any{types.Int(42), types.String("a")}},
2824+
{expr: `optional.unwrap([optional.of(42), optional.of("a")])`, out: []any{types.Int(42), types.String("a")}},
2825+
{expr: `[].unwrapOpt()`, out: []any{}},
2826+
{expr: `[optional.none(), optional.none()].unwrapOpt()`, out: []any{}},
2827+
{expr: `[optional.of(42), optional.none(), optional.of("a")].unwrapOpt()`, out: []any{types.Int(42), types.String("a")}},
2828+
{expr: `[optional.of(42), optional.of("a")].unwrapOpt()`, out: []any{types.Int(42), types.String("a")}},
28212829
}
28222830

28232831
for i, tst := range tests {

cel/library.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package cel
1616

1717
import (
18+
"fmt"
1819
"math"
1920
"strconv"
2021
"strings"
@@ -35,9 +36,11 @@ const (
3536
optMapMacro = "optMap"
3637
optFlatMapMacro = "optFlatMap"
3738
hasValueFunc = "hasValue"
39+
unwrapOptFunc = "unwrapOpt"
3840
optionalNoneFunc = "optional.none"
3941
optionalOfFunc = "optional.of"
4042
optionalOfNonZeroValueFunc = "optional.ofNonZeroValue"
43+
optionalUnwrapFunc = "optional.unwrap"
4144
valueFunc = "value"
4245
unusedIterVar = "#unused"
4346
)
@@ -281,6 +284,16 @@ func (stdLibrary) ProgramOptions() []ProgramOption {
281284
//
282285
// This is syntactic sugar for msg.elements[msg.elements.size()-1].
283286

287+
// # Unwrap / UnwrapOpt
288+
//
289+
// Introduced in version: 2
290+
//
291+
// Returns a list of all the values that are not none in the input list of optional values.
292+
// Can be used as optional.unwrap(List[T]) or with postfix notation: List[T].unwrapOpt()
293+
//
294+
// optional.unwrap([optional.of(42), optional.none()]) == [42]
295+
// [optional.of(42), optional.none()].unwrapOpt() == [42]
296+
284297
func OptionalTypes(opts ...OptionalTypesOption) EnvOption {
285298
lib := &optionalLib{version: math.MaxUint32}
286299
for _, opt := range opts {
@@ -324,6 +337,7 @@ func (lib *optionalLib) CompileOptions() []EnvOption {
324337
optionalTypeV := OptionalType(paramTypeV)
325338
listTypeV := ListType(paramTypeV)
326339
mapTypeKV := MapType(paramTypeK, paramTypeV)
340+
listOptionalTypeV := ListType(optionalTypeV)
327341

328342
opts := []EnvOption{
329343
// Enable the optional syntax in the parser.
@@ -427,6 +441,13 @@ func (lib *optionalLib) CompileOptions() []EnvOption {
427441
}),
428442
),
429443
))
444+
445+
opts = append(opts, Function(optionalUnwrapFunc,
446+
Overload("optional_unwrap", []*Type{listOptionalTypeV}, listTypeV,
447+
UnaryBinding(optUnwrap))))
448+
opts = append(opts, Function(unwrapOptFunc,
449+
MemberOverload("optional_unwrapOpt", []*Type{listOptionalTypeV}, listTypeV,
450+
UnaryBinding(optUnwrap))))
430451
}
431452

432453
return opts
@@ -493,6 +514,23 @@ func optFlatMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Exp
493514
), nil
494515
}
495516

517+
func optUnwrap(value ref.Val) ref.Val {
518+
list := value.(traits.Lister)
519+
var unwrappedList []ref.Val
520+
iter := list.Iterator()
521+
for iter.HasNext() == types.True {
522+
val := iter.Next()
523+
opt, isOpt := val.(*types.Optional)
524+
if !isOpt {
525+
return types.WrapErr(fmt.Errorf("value %v is not optional", val))
526+
}
527+
if opt.HasValue() {
528+
unwrappedList = append(unwrappedList, opt.GetValue())
529+
}
530+
}
531+
return types.DefaultTypeAdapter.NativeToValue(unwrappedList)
532+
}
533+
496534
func enableOptionalSyntax() EnvOption {
497535
return func(e *Env) (*Env, error) {
498536
e.prsrOpts = append(e.prsrOpts, parser.EnableOptionalSyntax(true))

0 commit comments

Comments
 (0)