blob: 010f20f51d271974ccf9b6c8b0865fd0122f8617 [file] [edit]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cel
import (
"fmt"
"github.com/golang/protobuf/proto"
"github.com/google/cel-go/common/packages"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter"
"github.com/google/cel-go/interpreter/functions"
"github.com/google/cel-go/parser"
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
// EnvOption is a functional interface for configuring the environment.
type EnvOption func(e *env) (*env, error)
// ClearBuiltIns option removes all standard types, operators, and macros from the environment.
//
// Note: This option must be specified before Declarations and/or Macros if used together.
func ClearBuiltIns() EnvOption {
return func(e *env) (*env, error) {
e.declarations = []*exprpb.Decl{}
e.macros = parser.NoMacros
e.enableBuiltins = false
return e, nil
}
}
// ClearMacros options clears all parser macros.
//
// Clearing macros will ensure CEL expressions can only contain linear evaluation paths, as
// comprehensions such as `all` and `exists` are enabled only via macros.
//
// Note: This option is a no-op when used with ClearBuiltIns, and must be used before Macros
// if used together.
func ClearMacros() EnvOption {
return func(e *env) (*env, error) {
e.macros = parser.NoMacros
return e, nil
}
}
// CustomTypeAdapter swaps the default ref.TypeAdapter implementation with a custom one.
//
// Note: This option must be specified before the Types and TypeDescs options when used together.
func CustomTypeAdapter(adapter ref.TypeAdapter) EnvOption {
return func(e *env) (*env, error) {
e.adapter = adapter
return e, nil
}
}
// CustomTypeProvider swaps the default ref.TypeProvider implementation with a custom one.
//
// Note: This option must be specified before the Types and TypeDescs options when used together.
func CustomTypeProvider(provider ref.TypeProvider) EnvOption {
return func(e *env) (*env, error) {
e.provider = provider
return e, nil
}
}
// Declarations option extends the declaration set configured in the environment.
//
// Note: This option must be specified after ClearBuiltIns if both are used together.
func Declarations(decls ...*exprpb.Decl) EnvOption {
// TODO: provide an alternative means of specifying declarations that doesn't refer
// to the underlying proto implementations.
return func(e *env) (*env, error) {
e.declarations = append(e.declarations, decls...)
return e, nil
}
}
// HomogeneousAggregateLiterals option ensures that list and map literal entry types must agree
// during type-checking.
//
// Note, it is still possible to have heterogeneous aggregates when provided as variables to the
// expression, as well as via conversion of well-known dynamic types, or with unchecked
// expressions.
func HomogeneousAggregateLiterals() EnvOption {
return func(e *env) (*env, error) {
e.enableDynamicAggregateLiterals = false
return e, nil
}
}
// Macros option extends the macro set configured in the environment.
//
// Note: This option must be specified after ClearBuiltIns and/or ClearMacros if used together.
func Macros(macros ...parser.Macro) EnvOption {
return func(e *env) (*env, error) {
e.macros = append(e.macros, macros...)
return e, nil
}
}
// Container sets the container for resolving variable names. Defaults to an empty container.
//
// If all references within an expression are relative to a protocol buffer package, then
// specifying a container of `google.type` would make it possible to write expressions such as
// `Expr{expression: 'a < b'}` instead of having to write `google.type.Expr{...}`.
func Container(pkg string) EnvOption {
return func(e *env) (*env, error) {
e.pkg = packages.NewPackage(pkg)
return e, nil
}
}
// Types adds one or more type declarations to the environment, allowing for construction of
// type-literals whose definitions are included in the common expression built-in set.
//
// The input types may either be instances of `proto.Message` or `ref.Type`. Any other type
// provided to this option will result in an error.
//
// Well-known protobuf types within the `google.protobuf.*` package are included in the standard
// environment by default.
//
// Note: This option must be specified after the CustomTypeProvider option when used together.
func Types(addTypes ...interface{}) EnvOption {
return func(e *env) (*env, error) {
reg, isReg := e.provider.(ref.TypeRegistry)
if !isReg {
return nil, fmt.Errorf("custom types not supported by provider: %T", e.provider)
}
for _, t := range addTypes {
switch t.(type) {
case proto.Message:
err := reg.RegisterMessage(t.(proto.Message))
if err != nil {
return nil, err
}
case ref.Type:
err := reg.RegisterType(t.(ref.Type))
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported type: %T", t)
}
}
return e, nil
}
}
// TypeDescs adds type declarations for one or more protocol buffer
// FileDescriptorProtos or FileDescriptorSets. Note that types added
// via descriptor will not be able to instantiate messages, and so are
// only useful for Check() operations.
func TypeDescs(descs ...interface{}) EnvOption {
return func(e *env) (*env, error) {
reg, isReg := e.provider.(ref.TypeRegistry)
if !isReg {
return nil, fmt.Errorf("custom types not supported by provider: %T", e.provider)
}
for _, d := range descs {
switch p := d.(type) {
case *descpb.FileDescriptorSet:
for _, fd := range p.File {
err := reg.RegisterDescriptor(fd)
if err != nil {
return nil, err
}
}
case *descpb.FileDescriptorProto:
err := reg.RegisterDescriptor(p)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported type descriptor: %T", d)
}
}
return e, nil
}
}
// ProgramOption is a functional interface for configuring evaluation bindings and behaviors.
type ProgramOption func(p *prog) (*prog, error)
// Functions adds function overloads that extend or override the set of CEL built-ins.
func Functions(funcs ...*functions.Overload) ProgramOption {
return func(p *prog) (*prog, error) {
if err := p.dispatcher.Add(funcs...); err != nil {
return nil, err
}
return p, nil
}
}
// Globals sets the global variable values for a given program. These values may be shadowed by
// variables with the same name provided to the Eval() call.
//
// The vars value may either be an `interpreter.Activation` instance or a `map[string]interface{}`.
func Globals(vars interface{}) ProgramOption {
return func(p *prog) (*prog, error) {
defaultVars, err :=
interpreter.NewAdaptingActivation(p.adapter, vars)
if err != nil {
return nil, err
}
p.defaultVars = defaultVars
return p, nil
}
}
// EvalOption indicates an evaluation option that may affect the evaluation behavior or information
// in the output result.
type EvalOption int
const (
// OptTrackState will cause the runtime to return an immutable EvalState value in the Result.
OptTrackState EvalOption = 1 << iota
// OptExhaustiveEval causes the runtime to disable short-circuits and track state.
OptExhaustiveEval EvalOption = 1<<iota | OptTrackState
// OptFoldConstants evaluates functions and operators with constants as arguments at program
// creation time. This flag is useful when the expression will be evaluated repeatedly against
// a series of different inputs.
OptFoldConstants EvalOption = 1 << iota
)
// EvalOptions sets one or more evaluation options which may affect the evaluation or Result.
func EvalOptions(opts ...EvalOption) ProgramOption {
return func(p *prog) (*prog, error) {
for _, opt := range opts {
p.evalOpts |= opt
}
return p, nil
}
}