diff --git a/cel/env.go b/cel/env.go index bb3014464..58819e872 100644 --- a/cel/env.go +++ b/cel/env.go @@ -27,6 +27,7 @@ import ( "github.com/google/cel-go/common/containers" "github.com/google/cel-go/common/decls" "github.com/google/cel-go/common/env" + "github.com/google/cel-go/common/functions" "github.com/google/cel-go/common/stdlib" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" @@ -142,6 +143,9 @@ type Env struct { validators []ASTValidator costOptions []checker.CostOption + funcBindOnce sync.Once + functionBindings []*functions.Overload + // Internal parser representation prsr *parser.Parser prsrOpts []parser.Option @@ -320,18 +324,19 @@ func NewCustomEnv(opts ...EnvOption) (*Env, error) { return nil, err } return (&Env{ - variables: []*decls.VariableDecl{}, - functions: map[string]*decls.FunctionDecl{}, - macros: []parser.Macro{}, - Container: containers.DefaultContainer, - adapter: registry, - provider: registry, - features: map[int]bool{}, - appliedFeatures: map[int]bool{}, - libraries: map[string]SingletonLibrary{}, - validators: []ASTValidator{}, - progOpts: []ProgramOption{}, - costOptions: []checker.CostOption{}, + variables: []*decls.VariableDecl{}, + functions: map[string]*decls.FunctionDecl{}, + functionBindings: []*functions.Overload{}, + macros: []parser.Macro{}, + Container: containers.DefaultContainer, + adapter: registry, + provider: registry, + features: map[int]bool{}, + appliedFeatures: map[int]bool{}, + libraries: map[string]SingletonLibrary{}, + validators: []ASTValidator{}, + progOpts: []ProgramOption{}, + costOptions: []checker.CostOption{}, }).configure(opts) } diff --git a/cel/env_test.go b/cel/env_test.go index 325fad3bf..cdbd50f81 100644 --- a/cel/env_test.go +++ b/cel/env_test.go @@ -877,11 +877,17 @@ func TestEnvFromConfigErrors(t *testing.T) { want: errors.New("invalid validator"), }, { - name: "invalid validator config type", + name: "invalid validator config type - unsupported type", conf: env.NewConfig("invalid validator config"). - AddValidators(env.NewValidator("cel.validator.comprehension_nesting_limit").SetConfig(map[string]any{"limit": 2.0})), + AddValidators(env.NewValidator("cel.validator.comprehension_nesting_limit").SetConfig(map[string]any{"limit": "2"})), want: errors.New("invalid validator"), }, + { + name: "invalid validator config type - fractional", + conf: env.NewConfig("invalid validator config"). + AddValidators(env.NewValidator("cel.validator.comprehension_nesting_limit").SetConfig(map[string]any{"limit": 2.5})), + want: errors.New("invalid validator: cel.validator.comprehension_nesting_limit, limit value is not a whole number: 2.5"), + }, } for _, tst := range tests { tc := tst diff --git a/cel/folding.go b/cel/folding.go index 40d843ece..d1ea6b19d 100644 --- a/cel/folding.go +++ b/cel/folding.go @@ -38,7 +38,7 @@ func MaxConstantFoldIterations(limit int) ConstantFoldingOption { } } -// Adds an Activation which provides known values for the folding evaluator +// FoldKnownValues adds an Activation which provides known values for the folding evaluator // // Any values the activation provides will be used by the constant folder and turned into // literals in the AST. diff --git a/cel/folding_test.go b/cel/folding_test.go index 0e65bb86c..aa671badc 100644 --- a/cel/folding_test.go +++ b/cel/folding_test.go @@ -332,7 +332,9 @@ func TestConstantFoldingOptimizer(t *testing.T) { EnableMacroCallTracking(), Types(&proto3pb.TestAllTypes{}), Variable("x", DynType), - Constant("c", IntType, types.Int(proto3pb.ImportedGlobalEnum_IMPORT_BAZ)), + // work around different package convention in piper vs github. + // google.expr.proto3.test.ImportedGlobalEnum.IMPORT_BAZ + Constant("c", IntType, types.Int(2)), ) if err != nil { t.Fatalf("NewEnv() failed: %v", err) diff --git a/cel/program.go b/cel/program.go index 24f41a4a7..ec3869bdb 100644 --- a/cel/program.go +++ b/cel/program.go @@ -20,6 +20,7 @@ import ( "sync" "github.com/google/cel-go/common/ast" + "github.com/google/cel-go/common/functions" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/interpreter" @@ -191,16 +192,25 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) { } } - // Add the function bindings created via Function() options. - for _, fn := range e.functions { - bindings, err := fn.Bindings() - if err != nil { - return nil, err - } - err = disp.Add(bindings...) - if err != nil { - return nil, err + e.funcBindOnce.Do(func() { + var bindings []*functions.Overload + e.functionBindings = []*functions.Overload{} + for _, fn := range e.functions { + bindings, err = fn.Bindings() + if err != nil { + return + } + e.functionBindings = append(e.functionBindings, bindings...) } + }) + if err != nil { + return nil, err + } + + // Add the function bindings created via Function() options. + err = disp.Add(e.functionBindings...) + if err != nil { + return nil, err } // Set the attribute factory after the options have been set. diff --git a/cel/templates/authoring.tmpl b/cel/templates/authoring.tmpl index d6b3da5c6..d0b0133f1 100644 --- a/cel/templates/authoring.tmpl +++ b/cel/templates/authoring.tmpl @@ -1,4 +1,8 @@ -{{define "variable"}}{{.Name}} is a {{.Type}} +{{define "variable"}}{{.Name}} is a {{.Type}}{{if .Description}} + +{{range split .Description}} {{.}} +{{end}} +{{- end -}} {{- end -}} {{define "macro" -}} diff --git a/cel/validator.go b/cel/validator.go index 5f06b2dd5..952f88f41 100644 --- a/cel/validator.go +++ b/cel/validator.go @@ -45,6 +45,14 @@ var ( astValidatorFactories = map[string]ASTValidatorFactory{ nestingLimitValidatorName: func(val *env.Validator) (ASTValidator, error) { if limit, found := val.ConfigValue("limit"); found { + // In case of protos, config value is of type by google.protobuf.Value, which numeric values are always a double. + if val, isDouble := limit.(float64); isDouble { + if val != float64(int64(val)) { + return nil, fmt.Errorf("invalid validator: %s, limit value is not a whole number: %v", nestingLimitValidatorName, limit) + } + return ValidateComprehensionNestingLimit(int(val)), nil + } + if val, isInt := limit.(int); isInt { return ValidateComprehensionNestingLimit(val), nil } diff --git a/common/env/env_test.go b/common/env/env_test.go index cc9be0334..5a78645a2 100644 --- a/common/env/env_test.go +++ b/common/env/env_test.go @@ -106,7 +106,7 @@ func TestConfig(t *testing.T) { NewValidator("cel.validator.duration"), NewValidator("cel.validator.matches"), NewValidator("cel.validator.timestamp"), - NewValidator("cel.validator.nesting_comprehension_limit"). + NewValidator("cel.validator.comprehension_nesting_limit"). SetConfig(map[string]any{"limit": 2}), ), }, diff --git a/common/env/testdata/extended_env.yaml b/common/env/testdata/extended_env.yaml index 5e9e6fcda..fed70b16c 100644 --- a/common/env/testdata/extended_env.yaml +++ b/common/env/testdata/extended_env.yaml @@ -53,7 +53,7 @@ validators: - name: cel.validator.duration - name: cel.validator.matches - name: cel.validator.timestamp - - name: cel.validator.nesting_comprehension_limit + - name: cel.validator.comprehension_nesting_limit config: limit: 2 features: diff --git a/common/types/pb/type.go b/common/types/pb/type.go index bdd474c95..171494f07 100644 --- a/common/types/pb/type.go +++ b/common/types/pb/type.go @@ -472,7 +472,7 @@ func unwrap(desc description, msg proto.Message) (any, bool, error) { } return v.GetValue(), true, nil } - return msg, false, nil + return unwrapDynamic(desc, msg.ProtoReflect()) } // unwrapDynamic unwraps a reflected protobuf Message value. diff --git a/common/types/pb/type_test.go b/common/types/pb/type_test.go index 6c7b8c8fd..bf2ad58d6 100644 --- a/common/types/pb/type_test.go +++ b/common/types/pb/type_test.go @@ -430,6 +430,10 @@ func TestTypeDescriptionMaybeUnwrap(t *testing.T) { in: dpb.New(time.Duration(345)), out: time.Duration(345), }, + { + in: struct{ proto.Message }{wrapperspb.Int32(1234)}, + out: int32(1234), + }, } for _, tc := range tests { typeName := string(tc.in.ProtoReflect().Descriptor().FullName()) diff --git a/ext/native.go b/ext/native.go index 661984cbb..ceaa274b7 100644 --- a/ext/native.go +++ b/ext/native.go @@ -609,7 +609,8 @@ func newNativeTypes(fieldNameHandler NativeTypesFieldNameHandler, rawType reflec var iterateStructMembers func(reflect.Type) iterateStructMembers = func(t reflect.Type) { if k := t.Kind(); k == reflect.Pointer || k == reflect.Slice || k == reflect.Array || k == reflect.Map { - t = t.Elem() + iterateStructMembers(t.Elem()) + return } if t.Kind() != reflect.Struct { return diff --git a/ext/native_test.go b/ext/native_test.go index 2c0ce3d36..8450a633e 100644 --- a/ext/native_test.go +++ b/ext/native_test.go @@ -941,6 +941,67 @@ func TestNativeStructEmbedded(t *testing.T) { } } +type TestNestedStruct struct { + ListVal []*TestNestedType +} + +func TestNativeNestedStruct(t *testing.T) { + var nativeTests = []struct { + expr string + in any + }{ + { + expr: `test.ListVal.exists(x, x.custom_name == "name")`, + in: map[string]any{ + "test": &TestNestedStruct{ListVal: []*TestNestedType{{NestedCustomName: "name"}}}, + }, + }, + } + + envOpts := []cel.EnvOption{ + NativeTypes( + reflect.ValueOf(&TestNestedStruct{}), + ParseStructTag("json"), + ), + cel.Variable("test", cel.ObjectType("ext.TestNestedStruct")), + } + + env, err := cel.NewEnv(envOpts...) + if err != nil { + t.Fatalf("cel.NewEnv(NativeTypes()) failed: %v", err) + } + + for i, tst := range nativeTests { + tc := tst + t.Run(fmt.Sprintf("[%d]", i), func(t *testing.T) { + var asts []*cel.Ast + pAst, iss := env.Parse(tc.expr) + if iss.Err() != nil { + t.Fatalf("env.Parse(%v) failed: %v", tc.expr, iss.Err()) + } + asts = append(asts, pAst) + cAst, iss := env.Check(pAst) + if iss.Err() != nil { + t.Fatalf("env.Check(%v) failed: %v", tc.expr, iss.Err()) + } + asts = append(asts, cAst) + for _, ast := range asts { + prg, err := env.Program(ast) + if err != nil { + t.Fatal(err) + } + out, _, err := prg.Eval(tc.in) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(out.Value(), true) { + t.Errorf("got %v, wanted true for expr: %s", out.Value(), tc.expr) + } + } + }) + } +} + func TestNativeTypesVersion(t *testing.T) { _, err := cel.NewEnv(NativeTypes(NativeTypesVersion(0))) if err != nil { diff --git a/repl/appengine/web/package-lock.json b/repl/appengine/web/package-lock.json index ec1ae6ef1..e5837b6dc 100644 --- a/repl/appengine/web/package-lock.json +++ b/repl/appengine/web/package-lock.json @@ -7478,9 +7478,9 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { @@ -7488,7 +7488,7 @@ "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -11940,9 +11940,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { diff --git a/tools/compiler/compiler.go b/tools/compiler/compiler.go index b8fd70ea3..89a1c1d4d 100644 --- a/tools/compiler/compiler.go +++ b/tools/compiler/compiler.go @@ -323,7 +323,7 @@ func envToValidators(pbEnv *configpb.Environment) ([]*env.Validator, error) { config := map[string]any{} for k, v := range pbValidator.GetConfig() { val := types.DefaultTypeAdapter.NativeToValue(v) - config[k] = val + config[k] = val.Value() } validator.SetConfig(config) validators = append(validators, validator) diff --git a/tools/compiler/compiler_test.go b/tools/compiler/compiler_test.go index 5513919e3..d528eed56 100644 --- a/tools/compiler/compiler_test.go +++ b/tools/compiler/compiler_test.go @@ -321,9 +321,9 @@ func testEnvProto() *configpb.Environment { Validators: []*configpb.Validator{ {Name: "cel.validator.duration"}, { - Name: "cel.validator.nesting_comprehension_limit", + Name: "cel.validator.comprehension_nesting_limit", Config: map[string]*structpb.Value{ - "limits": structpb.NewNumberValue(2), + "limit": structpb.NewNumberValue(2), }, }, }, diff --git a/tools/compiler/testdata/config.yaml b/tools/compiler/testdata/config.yaml index 5ba153bbc..d70e2a3f1 100644 --- a/tools/compiler/testdata/config.yaml +++ b/tools/compiler/testdata/config.yaml @@ -74,7 +74,7 @@ functions: type_name: "string" validators: - name: cel.validator.duration - - name: cel.validator.nesting_comprehension_limit + - name: cel.validator.comprehension_nesting_limit config: limit: 2 features: