| // Copyright 2018 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 interpreter |
| |
| import ( |
| "errors" |
| "fmt" |
| |
| "github.com/google/cel-go/common/types/ref" |
| ) |
| |
| // Activation used to resolve identifiers by name and references by id. |
| // |
| // An Activation is the primary mechanism by which a caller supplies input into a CEL program. |
| type Activation interface { |
| // ResolveName returns a value from the activation by qualified name, or false if the name |
| // could not be found. |
| ResolveName(name string) (any, bool) |
| |
| // Parent returns the parent of the current activation, may be nil. |
| // If non-nil, the parent will be searched during resolve calls. |
| Parent() Activation |
| } |
| |
| // EmptyActivation returns a variable-free activation. |
| func EmptyActivation() Activation { |
| return emptyActivation{} |
| } |
| |
| // emptyActivation is a variable-free activation. |
| type emptyActivation struct{} |
| |
| func (emptyActivation) ResolveName(string) (any, bool) { return nil, false } |
| func (emptyActivation) Parent() Activation { return nil } |
| |
| // NewActivation returns an activation based on a map-based binding where the map keys are |
| // expected to be qualified names used with ResolveName calls. |
| // |
| // The input `bindings` may either be of type `Activation` or `map[string]any`. |
| // |
| // Lazy bindings may be supplied within the map-based input in either of the following forms: |
| // - func() any |
| // - func() ref.Val |
| // |
| // The output of the lazy binding will overwrite the variable reference in the internal map. |
| // |
| // Values which are not represented as ref.Val types on input may be adapted to a ref.Val using |
| // the types.Adapter configured in the environment. |
| func NewActivation(bindings any) (Activation, error) { |
| if bindings == nil { |
| return nil, errors.New("bindings must be non-nil") |
| } |
| a, isActivation := bindings.(Activation) |
| if isActivation { |
| return a, nil |
| } |
| m, isMap := bindings.(map[string]any) |
| if !isMap { |
| return nil, fmt.Errorf( |
| "activation input must be an activation or map[string]interface: got %T", |
| bindings) |
| } |
| return &mapActivation{bindings: m}, nil |
| } |
| |
| // mapActivation which implements Activation and maps of named values. |
| // |
| // Named bindings may lazily supply values by providing a function which accepts no arguments and |
| // produces an interface value. |
| type mapActivation struct { |
| bindings map[string]any |
| } |
| |
| // Parent implements the Activation interface method. |
| func (a *mapActivation) Parent() Activation { |
| return nil |
| } |
| |
| // ResolveName implements the Activation interface method. |
| func (a *mapActivation) ResolveName(name string) (any, bool) { |
| obj, found := a.bindings[name] |
| if !found { |
| return nil, false |
| } |
| fn, isLazy := obj.(func() ref.Val) |
| if isLazy { |
| obj = fn() |
| a.bindings[name] = obj |
| } |
| fnRaw, isLazy := obj.(func() any) |
| if isLazy { |
| obj = fnRaw() |
| a.bindings[name] = obj |
| } |
| return obj, found |
| } |
| |
| // hierarchicalActivation which implements Activation and contains a parent and |
| // child activation. |
| type hierarchicalActivation struct { |
| parent Activation |
| child Activation |
| } |
| |
| // Parent implements the Activation interface method. |
| func (a *hierarchicalActivation) Parent() Activation { |
| return a.parent |
| } |
| |
| // ResolveName implements the Activation interface method. |
| func (a *hierarchicalActivation) ResolveName(name string) (any, bool) { |
| if object, found := a.child.ResolveName(name); found { |
| return object, found |
| } |
| return a.parent.ResolveName(name) |
| } |
| |
| // NewHierarchicalActivation takes two activations and produces a new one which prioritizes |
| // resolution in the child first and parent(s) second. |
| func NewHierarchicalActivation(parent Activation, child Activation) Activation { |
| return &hierarchicalActivation{parent, child} |
| } |
| |
| // NewPartialActivation returns an Activation which contains a list of AttributePattern values |
| // representing field and index operations that should result in a 'types.Unknown' result. |
| // |
| // The `bindings` value may be any value type supported by the interpreter.NewActivation call, |
| // but is typically either an existing Activation or map[string]any. |
| func NewPartialActivation(bindings any, |
| unknowns ...*AttributePattern) (PartialActivation, error) { |
| a, err := NewActivation(bindings) |
| if err != nil { |
| return nil, err |
| } |
| return &partActivation{Activation: a, unknowns: unknowns}, nil |
| } |
| |
| // PartialActivation extends the Activation interface with a set of UnknownAttributePatterns. |
| type PartialActivation interface { |
| Activation |
| |
| // UnknownAttributePaths returns a set of AttributePattern values which match Attribute |
| // expressions for data accesses whose values are not yet known. |
| UnknownAttributePatterns() []*AttributePattern |
| } |
| |
| // partialActivationConverter indicates whether an Activation implementation supports conversion to a PartialActivation |
| type partialActivationConverter interface { |
| // AsPartialActivation converts the current activation to a PartialActivation |
| AsPartialActivation() (PartialActivation, bool) |
| } |
| |
| // partActivation is the default implementations of the PartialActivation interface. |
| type partActivation struct { |
| Activation |
| unknowns []*AttributePattern |
| } |
| |
| // UnknownAttributePatterns implements the PartialActivation interface method. |
| func (a *partActivation) UnknownAttributePatterns() []*AttributePattern { |
| return a.unknowns |
| } |
| |
| // AsPartialActivation returns the partActivation as a PartialActivation interface. |
| func (a *partActivation) AsPartialActivation() (PartialActivation, bool) { |
| return a, true |
| } |
| |
| // AsPartialActivation walks the activation hierarchy and returns the first PartialActivation, if found. |
| func AsPartialActivation(vars Activation) (PartialActivation, bool) { |
| // Only internal activation instances may implement this interface |
| if pv, ok := vars.(partialActivationConverter); ok { |
| return pv.AsPartialActivation() |
| } |
| // Since Activations may be hierarchical, test whether a parent converts to a PartialActivation |
| if vars.Parent() != nil { |
| return AsPartialActivation(vars.Parent()) |
| } |
| return nil, false |
| } |