Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cel/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ go_library(
"//common/ast:go_default_library",
"//common/containers:go_default_library",
"//common/decls:go_default_library",
"//common/env:go_default_library",
"//common/functions:go_default_library",
"//common/operators:go_default_library",
"//common/overloads:go_default_library",
Expand Down
84 changes: 84 additions & 0 deletions cel/cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

"github.com/google/cel-go/checker"
celast "github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/env"
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/overloads"
"github.com/google/cel-go/common/types"
Expand Down Expand Up @@ -359,6 +360,89 @@ func TestExtendStdlibFunction(t *testing.T) {
}
}

func TestSubsetStdLib(t *testing.T) {
env, err := NewCustomEnv(StdLib(StdLibSubset(
&env.LibrarySubset{
IncludeMacros: []string{"has"},
IncludeFunctions: []*env.Function{
{Name: operators.Equals},
{Name: operators.NotEquals},
{Name: operators.LogicalAnd},
{Name: operators.LogicalOr},
{Name: operators.LogicalNot},
{Name: overloads.Size, Overloads: []*env.Overload{{ID: "list_size"}}},
},
},
)))
if err != nil {
t.Fatalf("StdLib() subsetting failed: %v", err)
}
tests := []struct {
name string
expr string
compiles bool
want ref.Val
}{
{
name: "has macro",
expr: "!has({}.a)",
compiles: true,
want: types.True,
},
{
name: "not equals",
expr: "has({}.a) != true",
compiles: true,
want: types.True,
},
{
name: "logical operators",
expr: "has({}.a) != true && has({'b': 1}.b) == true",
compiles: true,
want: types.True,
},
{
name: "list size - allowed",
expr: "[1, 2, 3].size()",
compiles: true,
want: types.Int(3),
},
{
name: "excluded macro",
expr: "[1, 2, 3].exists(i, i != 0)",
compiles: false,
},
{
name: "string size - not allowed",
expr: "'hello'.size()",
compiles: false,
},
}
for _, tst := range tests {
tc := tst
t.Run(tc.name, func(t *testing.T) {
ast, iss := env.Compile(tc.expr)
if tc.compiles && iss.Err() != nil {
t.Fatalf("env.Compile(%q) failed: %v", tc.expr, iss.Err())
}
if !tc.compiles && iss.Err() != nil {
return
}
prg, err := env.Program(ast)
if err != nil {
t.Fatalf("env.Program() failed: %v", err)
}
out, _, err := prg.Eval(NoVars())
if err != nil {
t.Fatalf("prg.Eval() failed: %s", err)
}
if out.Equal(tc.want) != types.True {
t.Errorf("prg.Eval() got %v, wanted %v", out, tc.want)
}
})
}
}

func TestCustomTypes(t *testing.T) {
reg := types.NewEmptyRegistry()
env := testEnv(t,
Expand Down
12 changes: 11 additions & 1 deletion cel/decls.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ func Variable(name string, t *Type) EnvOption {
}
}

// VariableDecls configures a set of fully defined cel.VariableDecl instances in the environment.
func VariableDecls(vars ...*decls.VariableDecl) EnvOption {
return func(e *Env) (*Env, error) {
for _, v := range vars {
e.variables = append(e.variables, v)
}
return e, nil
}
}

// Function defines a function and overloads with optional singleton or per-overload bindings.
//
// Using Function is roughly equivalent to calling Declarations() to declare the function signatures
Expand Down Expand Up @@ -209,7 +219,7 @@ func ExcludeOverloads(overloadIDs ...string) OverloadSelector {
return decls.ExcludeOverloads(overloadIDs...)
}

// FunctionDecls provides one or more fully formed function declaration to be added to the environment.
// FunctionDecls provides one or more fully formed function declarations to be added to the environment.
func FunctionDecls(funcs ...*decls.FunctionDecl) EnvOption {
return func(e *Env) (*Env, error) {
var err error
Expand Down
57 changes: 45 additions & 12 deletions cel/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"time"

"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/decls"
"github.com/google/cel-go/common/env"
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/overloads"
"github.com/google/cel-go/common/stdlib"
Expand Down Expand Up @@ -94,26 +96,61 @@ func Lib(l Library) EnvOption {
}
}

// StdLibOption specifies a functional option for configuring the standard CEL library.
type StdLibOption func(*stdLibrary) *stdLibrary

// StdLibSubset configures the standard library to use a subset of its functions and macros.
func StdLibSubset(subset *env.LibrarySubset) StdLibOption {
return func(lib *stdLibrary) *stdLibrary {
lib.subset = subset
return lib
}
}

// StdLib returns an EnvOption for the standard library of CEL functions and macros.
func StdLib() EnvOption {
return Lib(stdLibrary{})
func StdLib(opts ...StdLibOption) EnvOption {
lib := &stdLibrary{}
for _, o := range opts {
lib = o(lib)
}
return Lib(lib)
}

// stdLibrary implements the Library interface and provides functional options for the core CEL
// features documented in the specification.
type stdLibrary struct{}
type stdLibrary struct {
subset *env.LibrarySubset
}

// LibraryName implements the SingletonLibrary interface method.
func (stdLibrary) LibraryName() string {
func (*stdLibrary) LibraryName() string {
return "cel.lib.std"
}

// CompileOptions returns options for the standard CEL function declarations and macros.
func (stdLibrary) CompileOptions() []EnvOption {
func (lib *stdLibrary) CompileOptions() []EnvOption {
funcs := stdlib.Functions()
macros := StandardMacros
if lib.subset != nil {
subMacros := []Macro{}
for _, m := range macros {
if lib.subset.SubsetMacro(m.Function()) {
subMacros = append(subMacros, m)
}
}
macros = subMacros
subFuncs := []*decls.FunctionDecl{}
for _, fn := range funcs {
if f, include := lib.subset.SubsetFunction(fn); include {
subFuncs = append(subFuncs, f)
}
}
funcs = subFuncs
}
return []EnvOption{
func(e *Env) (*Env, error) {
var err error
for _, fn := range stdlib.Functions() {
for _, fn := range funcs {
existing, found := e.functions[fn.Name()]
if found {
fn, err = existing.Merge(fn)
Expand All @@ -125,16 +162,12 @@ func (stdLibrary) CompileOptions() []EnvOption {
}
return e, nil
},
func(e *Env) (*Env, error) {
e.variables = append(e.variables, stdlib.Types()...)
return e, nil
},
Macros(StandardMacros...),
Macros(macros...),
}
}

// ProgramOptions returns function implementations for the standard CEL functions.
func (stdLibrary) ProgramOptions() []ProgramOption {
func (*stdLibrary) ProgramOptions() []ProgramOption {
return []ProgramOption{}
}

Expand Down
3 changes: 0 additions & 3 deletions checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2377,7 +2377,6 @@ func TestCheck(t *testing.T) {
t.Fatalf("NewEnv(cont, reg) failed: %v", err)
}
if !tc.disableStdEnv {
env.AddIdents(stdlib.Types()...)
env.AddFunctions(stdlib.Functions()...)
}
if tc.env.idents != nil {
Expand Down Expand Up @@ -2467,7 +2466,6 @@ func BenchmarkCheck(b *testing.B) {
b.Fatalf("NewEnv(cont, reg) failed: %v", err)
}
if !tc.disableStdEnv {
env.AddIdents(stdlib.Types()...)
env.AddFunctions(stdlib.Functions()...)
}
if tc.env.idents != nil {
Expand Down Expand Up @@ -2583,7 +2581,6 @@ func TestCheckErrorData(t *testing.T) {
if err != nil {
t.Fatalf("NewEnv(cont, reg) failed: %v", err)
}
env.AddIdents(stdlib.Types()...)
env.AddFunctions(stdlib.Functions()...)
_, iss = Check(ast, src, env)
if len(iss.GetErrors()) != 1 {
Expand Down
20 changes: 1 addition & 19 deletions checker/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,6 @@ import (
"github.com/google/cel-go/parser"
)

func TestOverlappingIdentifier(t *testing.T) {
env := newStdEnv(t)
err := env.AddIdents(decls.NewVariable("int", types.TypeType))
if err == nil {
t.Error("Got nil, wanted error")
} else if !strings.Contains(err.Error(), "overlapping identifier") {
t.Errorf("Got %v, wanted overlapping identifier error", err)
}
}

func TestOverlappingMacro(t *testing.T) {
env := newStdEnv(t)
hasFn, err := decls.NewFunction("has",
Expand Down Expand Up @@ -92,10 +82,6 @@ func BenchmarkCopyDeclarations(b *testing.B) {
if err != nil {
b.Fatalf("NewEnv() failed: %v", err)
}
err = env.AddIdents(stdlib.Types()...)
if err != nil {
b.Fatalf("env.AddIdents(stdlib.Types()...) failed: %v", err)
}
err = env.AddFunctions(stdlib.Functions()...)
if err != nil {
b.Fatalf("env.AddFunctions(stdlib.Functions()...) failed: %v", err)
Expand All @@ -111,13 +97,9 @@ func newStdEnv(t *testing.T) *Env {
if err != nil {
t.Fatalf("NewEnv() failed: %v", err)
}
err = env.AddIdents(stdlib.Types()...)
if err != nil {
t.Fatalf("env.Add(stdlib.TypeExprDecls()...) failed: %v", err)
}
err = env.AddFunctions(stdlib.Functions()...)
if err != nil {
t.Fatalf("env.Add(stdlib.FunctionExprDecls()...) failed: %v", err)
t.Fatalf("env.Add(stdlib.Functions()...) failed: %v", err)
}
return env
}
Expand Down
4 changes: 0 additions & 4 deletions common/ast/navigable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,10 +594,6 @@ func newTestEnv(t testing.TB, cont *containers.Container, reg *types.Registry) *
if err != nil {
t.Fatalf("checker.NewEnv(%v, %v) failed: %v", cont, reg, err)
}
err = env.AddIdents(stdlib.Types()...)
if err != nil {
t.Fatalf("env.Add(stdlib.Types()...) failed: %v", err)
}
err = env.AddFunctions(stdlib.Functions()...)
if err != nil {
t.Fatalf("env.Add(stdlib.Functions()...) failed: %v", err)
Expand Down
4 changes: 4 additions & 0 deletions common/decls/decls.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ func (f *FunctionDecl) Merge(other *FunctionDecl) (*FunctionDecl, error) {
return merged, nil
}

// FunctionSubsetter subsets a function declaration or returns nil and false if the function
// subset was empty.
type FunctionSubsetter func(fn *FunctionDecl) (*FunctionDecl, bool)

// OverloadSelector selects an overload associated with a given function when it returns true.
//
// Used in combination with the Subset method.
Expand Down
18 changes: 18 additions & 0 deletions common/env/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")

package(
default_visibility = ["//visibility:public"],
licenses = ["notice"], # Apache 2.0
)

go_library(
name = "go_default_library",
srcs = [
"env.go",
],
importpath = "github.com/google/cel-go/common/env",
deps = [
"//common/decls:go_default_library",
"//common/types:go_default_library",
],
)
Loading