Skip to content

Commit

Permalink
Support for optMap() optional value macro (#717)
Browse files Browse the repository at this point in the history
* Support for optMap() optional value macro
  • Loading branch information
TristonianJones authored May 30, 2023
1 parent f8fc7b9 commit afe0a6c
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 5 deletions.
29 changes: 29 additions & 0 deletions cel/cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,20 @@ func TestOptionalValues(t *testing.T) {
},
out: 42,
},
{
expr: `x.optMap(y, y + 1)`,
in: map[string]any{
"x": types.OptionalNone,
},
out: types.OptionalNone,
},
{
expr: `x.optMap(y, y + 1)`,
in: map[string]any{
"x": types.OptionalOf(types.Int(42)),
},
out: types.OptionalOf(types.Int(43)),
},
{
expr: `optional.ofNonZeroValue(z).or(optional.of(10)).value() == 42`,
in: map[string]any{
Expand Down Expand Up @@ -2322,6 +2336,21 @@ func TestOptionalValues(t *testing.T) {
}
}

func TestOptionalMacroError(t *testing.T) {
env, err := NewEnv(
OptionalTypes(),
// Test variables.
Variable("x", OptionalType(IntType)),
)
if err != nil {
t.Fatalf("NewEnv() failed: %v", err)
}
_, iss := env.Compile("x.optMap(y.z, y.z + 1)")
if iss.Err() == nil || !strings.Contains(iss.Err().Error(), "variable name must be a simple identifier") {
t.Errorf("optMap() got an unexpected result: %v", iss.Err())
}
}

func BenchmarkOptionalValues(b *testing.B) {
env, err := NewEnv(
OptionalTypes(),
Expand Down
57 changes: 52 additions & 5 deletions cel/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"time"

"github.com/google/cel-go/checker"
"github.com/google/cel-go/common"
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/overloads"
"github.com/google/cel-go/common/types"
Expand All @@ -28,6 +29,18 @@ import (
"github.com/google/cel-go/interpreter"
"github.com/google/cel-go/interpreter/functions"
"github.com/google/cel-go/parser"

exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)

const (
optMapMacro = "optMap"
hasValueFunc = "hasValue"
optionalNoneFunc = "optional.none"
optionalOfFunc = "optional.of"
optionalOfNonZeroValueFunc = "optional.ofNonZeroValue"
valueFunc = "value"
unusedIterVar = "#unused"
)

// Library provides a collection of EnvOption and ProgramOption values used to configure a CEL
Expand Down Expand Up @@ -130,13 +143,16 @@ func (optionalLibrary) CompileOptions() []EnvOption {
// Introduce the optional type.
Types(types.OptionalType),

// Configure the optMap macro.
Macros(NewReceiverMacro(optMapMacro, 2, optMap)),

// Global and member functions for working with optional values.
Function("optional.of",
Function(optionalOfFunc,
Overload("optional_of", []*Type{paramTypeV}, optionalTypeV,
UnaryBinding(func(value ref.Val) ref.Val {
return types.OptionalOf(value)
}))),
Function("optional.ofNonZeroValue",
Function(optionalOfNonZeroValueFunc,
Overload("optional_ofNonZeroValue", []*Type{paramTypeV}, optionalTypeV,
UnaryBinding(func(value ref.Val) ref.Val {
v, isZeroer := value.(traits.Zeroer)
Expand All @@ -145,18 +161,18 @@ func (optionalLibrary) CompileOptions() []EnvOption {
}
return types.OptionalNone
}))),
Function("optional.none",
Function(optionalNoneFunc,
Overload("optional_none", []*Type{}, optionalTypeV,
FunctionBinding(func(values ...ref.Val) ref.Val {
return types.OptionalNone
}))),
Function("value",
Function(valueFunc,
MemberOverload("optional_value", []*Type{optionalTypeV}, paramTypeV,
UnaryBinding(func(value ref.Val) ref.Val {
opt := value.(*types.Optional)
return opt.GetValue()
}))),
Function("hasValue",
Function(hasValueFunc,
MemberOverload("optional_hasValue", []*Type{optionalTypeV}, BoolType,
UnaryBinding(func(value ref.Val) ref.Val {
opt := value.(*types.Optional)
Expand Down Expand Up @@ -190,6 +206,37 @@ func (optionalLibrary) CompileOptions() []EnvOption {
}
}

func optMap(meh MacroExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
varIdent := args[0]
varName := ""
switch varIdent.GetExprKind().(type) {
case *exprpb.Expr_IdentExpr:
varName = varIdent.GetIdentExpr().GetName()
default:
return nil, &common.Error{
Message: "optMap() variable name must be a simple identifier",
Location: meh.OffsetLocation(varIdent.GetId()),
}
}
mapExpr := args[1]
return meh.GlobalCall(
operators.Conditional,
meh.ReceiverCall(hasValueFunc, target),
meh.GlobalCall(optionalOfFunc,
meh.Fold(
unusedIterVar,
meh.NewList(),
varName,
meh.ReceiverCall(valueFunc, target),
meh.LiteralBool(false),
meh.Ident(varName),
mapExpr,
),
),
meh.GlobalCall(optionalNoneFunc),
), nil
}

// ProgramOptions implements the Library interface method.
func (optionalLibrary) ProgramOptions() []ProgramOption {
return []ProgramOption{
Expand Down

0 comments on commit afe0a6c

Please sign in to comment.