From 181b4683d1af0044d69d5eda22c22d4f53df7c80 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 8 Mar 2023 20:13:29 +0000 Subject: [PATCH] Implement options for enabling CEL extensions --- repl/BUILD.bazel | 1 + repl/evaluator.go | 65 ++++++++++++++++++++++++++++++---- repl/evaluator_test.go | 74 +++++++++++++++++++++++++++++++++++++++ repl/main/README.md | 4 +++ test/proto2pb/BUILD.bazel | 5 +-- 5 files changed, 141 insertions(+), 8 deletions(-) diff --git a/repl/BUILD.bazel b/repl/BUILD.bazel index ddba2f999..ef44ae994 100644 --- a/repl/BUILD.bazel +++ b/repl/BUILD.bazel @@ -56,6 +56,7 @@ go_test( embed = [":go_default_library"], deps = [ "//cel:go_default_library", + "//test/proto2pb:go_default_library", "@org_golang_google_genproto//googleapis/api/expr/v1alpha1:go_default_library", "@org_golang_google_protobuf//proto:go_default_library", ], diff --git a/repl/evaluator.go b/repl/evaluator.go index 84919f187..65e6eb197 100644 --- a/repl/evaluator.go +++ b/repl/evaluator.go @@ -431,12 +431,7 @@ type Evaluator struct { // NewEvaluator returns an inialized evaluator func NewEvaluator() (*Evaluator, error) { - env, err := cel.NewEnv( - ext.Strings(), - ext.Protos(), - ext.Math(), - ext.Encoders(), - ) + env, err := cel.NewEnv() if err != nil { return nil, err } @@ -685,6 +680,39 @@ func (o *containerOption) Option() cel.EnvOption { return cel.Container(o.container) } +// extensionOption implements optional for loading a specific extension into the environment (String, Math, Proto, Encoder) +type extensionOption struct { + extensionType string + option cel.EnvOption +} + +func (o *extensionOption) String() string { + return fmt.Sprintf("%%option --extension '%s'", o.extensionType) +} + +func (o extensionOption) Option() cel.EnvOption { + return o.option +} + +func newExtensionOption(extType string) (*extensionOption, error) { + var extOption cel.EnvOption + extType = strings.ToLower(extType) + switch op := extType; op { + case "strings": + extOption = ext.Strings() + case "protos": + extOption = ext.Protos() + case "math": + extOption = ext.Math() + case "encoders": + extOption = ext.Encoders() + default: + return nil, fmt.Errorf("Unknown option: %s. Available options are: ['strings', 'protos', 'math', 'encoders']", op) + } + + return &extensionOption{extensionType: extType, option: extOption}, nil +} + // setOption sets a number of options on the environment. returns an error if // any of them fail. func (e *Evaluator) setOption(args []string) error { @@ -702,6 +730,12 @@ func (e *Evaluator) setOption(args []string) error { if err != nil { issues = append(issues, fmt.Sprintf("container: %v", err)) } + } else if arg == "--extension" { + err := e.loadExtensionOption(idx, args) + idx++ + if err != nil { + issues = append(issues, fmt.Sprintf("extension: %v", err)) + } } else { issues = append(issues, fmt.Sprintf("unsupported option '%s'", arg)) } @@ -712,6 +746,25 @@ func (e *Evaluator) setOption(args []string) error { return nil } +func (e *Evaluator) loadExtensionOption(idx int, args []string) error { + if idx >= len(args) { + return fmt.Errorf("not enough args for extension") + } + + extType := args[idx] + extensionOption, err := newExtensionOption(extType) + if err != nil { + return err + } + + err = e.AddOption(extensionOption) + if err != nil { + return err + } + + return nil +} + func loadFileDescriptorSet(path string, textfmt bool) (*descpb.FileDescriptorSet, error) { data, err := ioutil.ReadFile(path) if err != nil { diff --git a/repl/evaluator_test.go b/repl/evaluator_test.go index a1fa290ec..dc865328e 100644 --- a/repl/evaluator_test.go +++ b/repl/evaluator_test.go @@ -19,6 +19,7 @@ import ( "github.com/google/cel-go/cel" + proto2pb "github.com/google/cel-go/test/proto2pb" exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" ) @@ -527,6 +528,78 @@ func TestProcess(t *testing.T) { wantExit: false, wantError: false, }, + { + name: "OptionExtensionStrings", + commands: []Cmder{ + &simpleCmd{ + cmd: "option", + args: []string{ + "--extension", + "strings", + }, + }, + &evalCmd{ + expr: "'test'.substring(2)", + }, + }, + wantText: "st : string", + wantExit: false, + wantError: false, + }, + { + name: "OptionExtensionProtos", + commands: []Cmder{ + &simpleCmd{ + cmd: "option", + args: []string{ + "--extension", + "protos", + }, + }, + &evalCmd{ + expr: "proto.getExt(google.expr.proto2.test.ExampleType{}, google.expr.proto2.test.int32_ext) == 0", + }, + }, + wantText: "true : bool", + wantExit: false, + wantError: false, + }, + { + name: "OptionExtensionMath", + commands: []Cmder{ + &simpleCmd{ + cmd: "option", + args: []string{ + "--extension", + "math", + }, + }, + &evalCmd{ + expr: "math.greatest(1,2)", + }, + }, + wantText: "2 : int", + wantExit: false, + wantError: false, + }, + { + name: "OptionExtensionEncoders", + commands: []Cmder{ + &simpleCmd{ + cmd: "option", + args: []string{ + "--extension", + "encoders", + }, + }, + &evalCmd{ + expr: "base64.encode(b'hello')", + }, + }, + wantText: "aGVsbG8= : string", + wantExit: false, + wantError: false, + }, { name: "LoadDescriptorsError", commands: []Cmder{ @@ -651,6 +724,7 @@ func TestProcess(t *testing.T) { if err != nil { t.Fatalf("NewEvaluator returned error: %v, wanted nil", err) } + eval.env, _ = eval.env.Extend(cel.Types(&proto2pb.ExampleType{}, &proto2pb.ExternalMessageType{})) n := len(tc.commands) for _, cmd := range tc.commands[:n-1] { // only need output of last command diff --git a/repl/main/README.md b/repl/main/README.md index 3cc65d260..87c2b8f6d 100644 --- a/repl/main/README.md +++ b/repl/main/README.md @@ -118,10 +118,14 @@ may take string arguments. `--container ` sets the expression container for name resolution. +`--extension ` enables CEL extensions. Valid options are: `strings`, `protos`, `math`, `encoders`. + example: `%option --container 'google.protobuf'` +`%option --extension 'strings'` + #### reset `%reset` drops all options and let expressions, returning the evaluator to a diff --git a/test/proto2pb/BUILD.bazel b/test/proto2pb/BUILD.bazel index 16077cbc9..2b4329844 100644 --- a/test/proto2pb/BUILD.bazel +++ b/test/proto2pb/BUILD.bazel @@ -9,6 +9,7 @@ package( "//ext:__subpackages__", "//interpreter:__subpackages__", "//parser:__subpackages__", + "//repl:__subpackages__", "//server:__subpackages__", "//test:__subpackages__", ], @@ -24,13 +25,13 @@ go_library( importpath = "github.com/google/cel-go/test/proto2pb", deps = [ "@org_golang_google_protobuf//proto:go_default_library", + "@org_golang_google_protobuf//reflect/protoreflect:go_default_library", + "@org_golang_google_protobuf//runtime/protoimpl:go_default_library", "@org_golang_google_protobuf//types/known/anypb:go_default_library", "@org_golang_google_protobuf//types/known/durationpb:go_default_library", "@org_golang_google_protobuf//types/known/structpb:go_default_library", "@org_golang_google_protobuf//types/known/timestamppb:go_default_library", "@org_golang_google_protobuf//types/known/wrapperspb:go_default_library", - "@org_golang_google_protobuf//reflect/protoreflect:go_default_library", - "@org_golang_google_protobuf//runtime/protoimpl:go_default_library", ], )