| // 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 interpreter |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "github.com/google/cel-go/common/containers" |
| "github.com/google/cel-go/common/types" |
| "github.com/google/cel-go/common/types/ref" |
| "github.com/google/cel-go/common/types/traits" |
| ) |
| |
| // AttributeFactory provides methods creating Attribute and Qualifier values. |
| type AttributeFactory interface { |
| // AbsoluteAttribute creates an attribute that refers to a top-level variable name. |
| // |
| // Checked expressions generate absolute attribute with a single name. |
| // Parse-only expressions may have more than one possible absolute identifier when the |
| // expression is created within a container, e.g. package or namespace. |
| // |
| // When there is more than one name supplied to the AbsoluteAttribute call, the names |
| // must be in CEL's namespace resolution order. The name arguments provided here are |
| // returned in the same order as they were provided by the NamespacedAttribute |
| // CandidateVariableNames method. |
| AbsoluteAttribute(id int64, names ...string) NamespacedAttribute |
| |
| // ConditionalAttribute creates an attribute with two Attribute branches, where the Attribute |
| // that is resolved depends on the boolean evaluation of the input 'expr'. |
| ConditionalAttribute(id int64, expr Interpretable, t, f Attribute) Attribute |
| |
| // MaybeAttribute creates an attribute that refers to either a field selection or a namespaced |
| // variable name. |
| // |
| // Only expressions which have not been type-checked may generate oneof attributes. |
| MaybeAttribute(id int64, name string) Attribute |
| |
| // RelativeAttribute creates an attribute whose value is a qualification of a dynamic |
| // computation rather than a static variable reference. |
| RelativeAttribute(id int64, operand Interpretable) Attribute |
| |
| // NewQualifier creates a qualifier on the target object with a given value. |
| // |
| // The 'val' may be an Attribute or any proto-supported map key type: bool, int, string, uint. |
| // |
| // The qualifier may consider the object type being qualified, if present. If absent, the |
| // qualification should be considered dynamic and the qualification should still work, though |
| // it may be sub-optimal. |
| NewQualifier(objType *types.Type, qualID int64, val any, opt bool) (Qualifier, error) |
| } |
| |
| // Qualifier marker interface for designating different qualifier values and where they appear |
| // within field selections and index call expressions (`_[_]`). |
| type Qualifier interface { |
| // ID where the qualifier appears within an expression. |
| ID() int64 |
| |
| // IsOptional specifies whether the qualifier is optional. |
| // Instead of a direct qualification, an optional qualifier will be resolved via QualifyIfPresent |
| // rather than Qualify. A non-optional qualifier may also be resolved through QualifyIfPresent if |
| // the object to qualify is itself optional. |
| IsOptional() bool |
| |
| // Qualify performs a qualification, e.g. field selection, on the input object and returns |
| // the value of the access and whether the value was set. A non-nil value with a false presence |
| // test result indicates that the value being returned is the default value. |
| Qualify(vars Activation, obj any) (any, error) |
| |
| // QualifyIfPresent qualifies the object if the qualifier is declared or defined on the object. |
| // The 'presenceOnly' flag indicates that the value is not necessary, just a boolean status as |
| // to whether the qualifier is present. |
| QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) |
| } |
| |
| // ConstantQualifier interface embeds the Qualifier interface and provides an option to inspect the |
| // qualifier's constant value. |
| // |
| // Non-constant qualifiers are of Attribute type. |
| type ConstantQualifier interface { |
| Qualifier |
| |
| // Value returns the constant value associated with the qualifier. |
| Value() ref.Val |
| } |
| |
| // Attribute values are a variable or value with an optional set of qualifiers, such as field, key, |
| // or index accesses. |
| type Attribute interface { |
| Qualifier |
| |
| // AddQualifier adds a qualifier on the Attribute or error if the qualification is not a valid qualifier type. |
| AddQualifier(Qualifier) (Attribute, error) |
| |
| // Resolve returns the value of the Attribute and whether it was present given an Activation. |
| // For objects which support safe traversal, the value may be non-nil and the presence flag be false. |
| // |
| // If an error is encountered during attribute resolution, it will be returned immediately. |
| // If the attribute cannot be resolved within the Activation, the result must be: `nil`, `error` |
| // with the error indicating which variable was missing. |
| Resolve(Activation) (any, error) |
| } |
| |
| // NamespacedAttribute values are a variable within a namespace, and an optional set of qualifiers |
| // such as field, key, or index accesses. |
| type NamespacedAttribute interface { |
| Attribute |
| |
| // CandidateVariableNames returns the possible namespaced variable names for this Attribute in |
| // the CEL namespace resolution order. |
| CandidateVariableNames() []string |
| |
| // Qualifiers returns the list of qualifiers associated with the Attribute. |
| Qualifiers() []Qualifier |
| } |
| |
| // AttrFactoryOption specifies a functional option for configuring an attribute factory. |
| type AttrFactoryOption func(*attrFactory) *attrFactory |
| |
| // EnableErrorOnBadPresenceTest error generation when a presence test or optional field selection |
| // is performed on a primitive type. |
| func EnableErrorOnBadPresenceTest(value bool) AttrFactoryOption { |
| return func(fac *attrFactory) *attrFactory { |
| fac.errorOnBadPresenceTest = value |
| return fac |
| } |
| } |
| |
| // NewAttributeFactory returns a default AttributeFactory which is produces Attribute values |
| // capable of resolving types by simple names and qualify the values using the supported qualifier |
| // types: bool, int, string, and uint. |
| func NewAttributeFactory(cont *containers.Container, a types.Adapter, p types.Provider, opts ...AttrFactoryOption) AttributeFactory { |
| fac := &attrFactory{ |
| container: cont, |
| adapter: a, |
| provider: p, |
| } |
| for _, o := range opts { |
| fac = o(fac) |
| } |
| return fac |
| } |
| |
| type attrFactory struct { |
| container *containers.Container |
| adapter types.Adapter |
| provider types.Provider |
| |
| errorOnBadPresenceTest bool |
| } |
| |
| // AbsoluteAttribute refers to a variable value and an optional qualifier path. |
| // |
| // The namespaceNames represent the names the variable could have based on namespace |
| // resolution rules. |
| func (r *attrFactory) AbsoluteAttribute(id int64, names ...string) NamespacedAttribute { |
| disambiguateNames := false |
| for idx, name := range names { |
| if strings.HasPrefix(name, ".") { |
| disambiguateNames = true |
| names[idx] = strings.TrimPrefix(name, ".") |
| } |
| } |
| return &absoluteAttribute{ |
| id: id, |
| namespaceNames: names, |
| disambiguateNames: disambiguateNames, |
| qualifiers: []Qualifier{}, |
| adapter: r.adapter, |
| provider: r.provider, |
| fac: r, |
| errorOnBadPresenceTest: r.errorOnBadPresenceTest, |
| } |
| } |
| |
| // ConditionalAttribute supports the case where an attribute selection may occur on a conditional |
| // expression, e.g. (cond ? a : b).c |
| func (r *attrFactory) ConditionalAttribute(id int64, expr Interpretable, t, f Attribute) Attribute { |
| return &conditionalAttribute{ |
| id: id, |
| expr: expr, |
| truthy: t, |
| falsy: f, |
| adapter: r.adapter, |
| fac: r, |
| } |
| } |
| |
| // MaybeAttribute collects variants of unchecked AbsoluteAttribute values which could either be |
| // direct variable accesses or some combination of variable access with qualification. |
| func (r *attrFactory) MaybeAttribute(id int64, name string) Attribute { |
| var names []string |
| // When there's a single name with a dot prefix, it indicates that the 'maybe' attribute is a |
| // globally namespaced identifier. |
| if strings.HasPrefix(name, ".") { |
| names = append(names, name) |
| } else { |
| // In all other cases, the candidate names should be inferred. |
| names = r.container.ResolveCandidateNames(name) |
| } |
| return &maybeAttribute{ |
| id: id, |
| attrs: []NamespacedAttribute{ |
| r.AbsoluteAttribute(id, names...), |
| }, |
| adapter: r.adapter, |
| provider: r.provider, |
| fac: r, |
| } |
| } |
| |
| // RelativeAttribute refers to an expression and an optional qualifier path. |
| func (r *attrFactory) RelativeAttribute(id int64, operand Interpretable) Attribute { |
| return &relativeAttribute{ |
| id: id, |
| operand: operand, |
| qualifiers: []Qualifier{}, |
| adapter: r.adapter, |
| fac: r, |
| errorOnBadPresenceTest: r.errorOnBadPresenceTest, |
| } |
| } |
| |
| // NewQualifier is an implementation of the AttributeFactory interface. |
| func (r *attrFactory) NewQualifier(objType *types.Type, qualID int64, val any, opt bool) (Qualifier, error) { |
| // Before creating a new qualifier check to see if this is a protobuf message field access. |
| // If so, use the precomputed GetFrom qualification method rather than the standard |
| // stringQualifier. |
| str, isStr := val.(string) |
| if isStr && objType != nil && objType.Kind() == types.StructKind { |
| ft, found := r.provider.FindStructFieldType(objType.TypeName(), str) |
| if found && ft.IsSet != nil && ft.GetFrom != nil { |
| return &fieldQualifier{ |
| id: qualID, |
| Name: str, |
| FieldType: ft, |
| adapter: r.adapter, |
| optional: opt, |
| }, nil |
| } |
| } |
| return newQualifier(r.adapter, qualID, val, opt, r.errorOnBadPresenceTest) |
| } |
| |
| type absoluteAttribute struct { |
| id int64 |
| // namespaceNames represent the names the variable could have based on declared container |
| // (package) of the expression. |
| namespaceNames []string |
| // disambiguateNames indicates whether the namespaceNames require disambiguation with local variables. |
| disambiguateNames bool |
| |
| qualifiers []Qualifier |
| adapter types.Adapter |
| provider types.Provider |
| fac AttributeFactory |
| |
| errorOnBadPresenceTest bool |
| } |
| |
| // ID implements the Attribute interface method. |
| func (a *absoluteAttribute) ID() int64 { |
| qualCount := len(a.qualifiers) |
| if qualCount == 0 { |
| return a.id |
| } |
| return a.qualifiers[qualCount-1].ID() |
| } |
| |
| // IsOptional returns trivially false for an attribute as the attribute represents a fully |
| // qualified variable name. If the attribute is used in an optional manner, then an attrQualifier |
| // is created and marks the attribute as optional. |
| func (a *absoluteAttribute) IsOptional() bool { |
| return false |
| } |
| |
| // AddQualifier implements the Attribute interface method. |
| func (a *absoluteAttribute) AddQualifier(qual Qualifier) (Attribute, error) { |
| a.qualifiers = append(a.qualifiers, qual) |
| return a, nil |
| } |
| |
| // CandidateVariableNames implements the NamespaceAttribute interface method. |
| func (a *absoluteAttribute) CandidateVariableNames() []string { |
| return a.namespaceNames |
| } |
| |
| // Qualifiers returns the list of Qualifier instances associated with the namespaced attribute. |
| func (a *absoluteAttribute) Qualifiers() []Qualifier { |
| return a.qualifiers |
| } |
| |
| // Qualify is an implementation of the Qualifier interface method. |
| func (a *absoluteAttribute) Qualify(vars Activation, obj any) (any, error) { |
| return attrQualify(a.fac, vars, obj, a) |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (a *absoluteAttribute) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return attrQualifyIfPresent(a.fac, vars, obj, a, presenceOnly) |
| } |
| |
| // String implements the Stringer interface method. |
| func (a *absoluteAttribute) String() string { |
| return fmt.Sprintf("id: %v, names: %v", a.id, a.namespaceNames) |
| } |
| |
| // Resolve returns the resolved Attribute value given the Activation, or error if the Attribute |
| // variable is not found, or if its Qualifiers cannot be applied successfully. |
| // |
| // If the variable name cannot be found as an Activation variable or in the TypeProvider as |
| // a type, then the result is `nil`, `error` with the error indicating the name of the first |
| // variable searched as missing. |
| func (a *absoluteAttribute) Resolve(vars Activation) (any, error) { |
| // unwrap any local activations to ensure that we reach the variables provided as input |
| // to the expression in the event that we need to disambiguate between global and local |
| // variables. |
| // |
| // Presently, only dynamic and constant slot activations created during comprehensions |
| // support 'unwrapping', which is consistent with how local variables are introduced into CEL. |
| var inputVars Activation |
| if a.disambiguateNames { |
| inputVars = vars |
| wrapped, ok := inputVars.(activationWrapper) |
| for ok { |
| inputVars = wrapped.Unwrap() |
| wrapped, ok = inputVars.(activationWrapper) |
| } |
| } |
| for _, nm := range a.namespaceNames { |
| // If the variable is found, process it. Otherwise, wait until the checks to |
| // determine whether the type is unknown before returning. |
| v := vars |
| if a.disambiguateNames { |
| v = inputVars |
| } |
| obj, found := v.ResolveName(nm) |
| if found { |
| if celErr, ok := obj.(*types.Err); ok { |
| return nil, celErr |
| } |
| obj, isOpt, err := applyQualifiers(v, obj, a.qualifiers) |
| if err != nil { |
| return nil, err |
| } |
| if isOpt { |
| val := a.adapter.NativeToValue(obj) |
| if types.IsUnknown(val) { |
| return val, nil |
| } |
| return types.OptionalOf(val), nil |
| } |
| return obj, nil |
| } |
| // Attempt to resolve the qualified type name if the name is not a variable identifier. |
| typ, found := a.provider.FindIdent(nm) |
| if found { |
| if len(a.qualifiers) == 0 { |
| return typ, nil |
| } |
| } |
| } |
| var attrNames strings.Builder |
| for i, nm := range a.namespaceNames { |
| if i != 0 { |
| attrNames.WriteString(", ") |
| } |
| attrNames.WriteString(nm) |
| } |
| return nil, missingAttribute(attrNames.String()) |
| } |
| |
| type conditionalAttribute struct { |
| id int64 |
| expr Interpretable |
| truthy Attribute |
| falsy Attribute |
| adapter types.Adapter |
| fac AttributeFactory |
| } |
| |
| // ID is an implementation of the Attribute interface method. |
| func (a *conditionalAttribute) ID() int64 { |
| // There's a field access after the conditional. |
| if a.truthy.ID() == a.falsy.ID() { |
| return a.truthy.ID() |
| } |
| // Otherwise return the conditional id as the consistent id being tracked. |
| return a.id |
| } |
| |
| // IsOptional returns trivially false for an attribute as the attribute represents a fully |
| // qualified variable name. If the attribute is used in an optional manner, then an attrQualifier |
| // is created and marks the attribute as optional. |
| func (a *conditionalAttribute) IsOptional() bool { |
| return false |
| } |
| |
| // AddQualifier appends the same qualifier to both sides of the conditional, in effect managing |
| // the qualification of alternate attributes. |
| func (a *conditionalAttribute) AddQualifier(qual Qualifier) (Attribute, error) { |
| _, err := a.truthy.AddQualifier(qual) |
| if err != nil { |
| return nil, err |
| } |
| _, err = a.falsy.AddQualifier(qual) |
| if err != nil { |
| return nil, err |
| } |
| return a, nil |
| } |
| |
| // Qualify is an implementation of the Qualifier interface method. |
| func (a *conditionalAttribute) Qualify(vars Activation, obj any) (any, error) { |
| return attrQualify(a.fac, vars, obj, a) |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (a *conditionalAttribute) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return attrQualifyIfPresent(a.fac, vars, obj, a, presenceOnly) |
| } |
| |
| // Resolve evaluates the condition, and then resolves the truthy or falsy branch accordingly. |
| func (a *conditionalAttribute) Resolve(vars Activation) (any, error) { |
| val := a.expr.Eval(vars) |
| if val == types.True { |
| return a.truthy.Resolve(vars) |
| } |
| if val == types.False { |
| return a.falsy.Resolve(vars) |
| } |
| if types.IsUnknown(val) { |
| return val, nil |
| } |
| return nil, types.MaybeNoSuchOverloadErr(val).(*types.Err) |
| } |
| |
| // String is an implementation of the Stringer interface method. |
| func (a *conditionalAttribute) String() string { |
| return fmt.Sprintf("id: %v, truthy attribute: %v, falsy attribute: %v", a.id, a.truthy, a.falsy) |
| } |
| |
| type maybeAttribute struct { |
| id int64 |
| attrs []NamespacedAttribute |
| adapter types.Adapter |
| provider types.Provider |
| fac AttributeFactory |
| } |
| |
| // ID is an implementation of the Attribute interface method. |
| func (a *maybeAttribute) ID() int64 { |
| return a.attrs[0].ID() |
| } |
| |
| // IsOptional returns trivially false for an attribute as the attribute represents a fully |
| // qualified variable name. If the attribute is used in an optional manner, then an attrQualifier |
| // is created and marks the attribute as optional. |
| func (a *maybeAttribute) IsOptional() bool { |
| return false |
| } |
| |
| // AddQualifier adds a qualifier to each possible attribute variant, and also creates |
| // a new namespaced variable from the qualified value. |
| // |
| // The algorithm for building the maybe attribute is as follows: |
| // |
| // 1. Create a maybe attribute from a simple identifier when it occurs in a parsed-only expression |
| // |
| // mb = MaybeAttribute(<id>, "a") |
| // |
| // Initializing the maybe attribute creates an absolute attribute internally which includes the |
| // possible namespaced names of the attribute. In this example, let's assume we are in namespace |
| // 'ns', then the maybe is either one of the following variable names: |
| // |
| // possible variables names -- ns.a, a |
| // |
| // 2. Adding a qualifier to the maybe means that the variable name could be a longer qualified |
| // name, or a field selection on one of the possible variable names produced earlier: |
| // |
| // mb.AddQualifier("b") |
| // |
| // possible variables names -- ns.a.b, a.b |
| // possible field selection -- ns.a['b'], a['b'] |
| // |
| // If none of the attributes within the maybe resolves a value, the result is an error. |
| func (a *maybeAttribute) AddQualifier(qual Qualifier) (Attribute, error) { |
| str := "" |
| isStr := false |
| cq, isConst := qual.(ConstantQualifier) |
| if isConst { |
| str, isStr = cq.Value().Value().(string) |
| } |
| var augmentedNames []string |
| // First add the qualifier to all existing attributes in the oneof. |
| for _, attr := range a.attrs { |
| if isStr && len(attr.Qualifiers()) == 0 { |
| candidateVars := attr.CandidateVariableNames() |
| augmentedNames = make([]string, len(candidateVars)) |
| for i, name := range candidateVars { |
| augmentedNames[i] = fmt.Sprintf("%s.%s", name, str) |
| } |
| } |
| _, err := attr.AddQualifier(qual) |
| if err != nil { |
| return nil, err |
| } |
| } |
| // Next, ensure the most specific variable / type reference is searched first. |
| if len(augmentedNames) != 0 { |
| a.attrs = append([]NamespacedAttribute{a.fac.AbsoluteAttribute(qual.ID(), augmentedNames...)}, a.attrs...) |
| } |
| return a, nil |
| } |
| |
| // Qualify is an implementation of the Qualifier interface method. |
| func (a *maybeAttribute) Qualify(vars Activation, obj any) (any, error) { |
| return attrQualify(a.fac, vars, obj, a) |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (a *maybeAttribute) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return attrQualifyIfPresent(a.fac, vars, obj, a, presenceOnly) |
| } |
| |
| // Resolve follows the variable resolution rules to determine whether the attribute is a variable |
| // or a field selection. |
| func (a *maybeAttribute) Resolve(vars Activation) (any, error) { |
| var maybeErr error |
| for _, attr := range a.attrs { |
| obj, err := attr.Resolve(vars) |
| // Return an error if one is encountered. |
| if err != nil { |
| resErr, ok := err.(*resolutionError) |
| if !ok { |
| return nil, err |
| } |
| // If this was not a missing variable error, return it. |
| if !resErr.isMissingAttribute() { |
| return nil, err |
| } |
| // When the variable is missing in a maybe attribute we defer erroring. |
| if maybeErr == nil { |
| maybeErr = resErr |
| } |
| // Continue attempting to resolve possible variables. |
| continue |
| } |
| return obj, nil |
| } |
| // Else, produce a no such attribute error. |
| return nil, maybeErr |
| } |
| |
| // String is an implementation of the Stringer interface method. |
| func (a *maybeAttribute) String() string { |
| return fmt.Sprintf("id: %v, attributes: %v", a.id, a.attrs) |
| } |
| |
| type relativeAttribute struct { |
| id int64 |
| operand Interpretable |
| qualifiers []Qualifier |
| adapter types.Adapter |
| fac AttributeFactory |
| |
| errorOnBadPresenceTest bool |
| } |
| |
| // ID is an implementation of the Attribute interface method. |
| func (a *relativeAttribute) ID() int64 { |
| qualCount := len(a.qualifiers) |
| if qualCount == 0 { |
| return a.id |
| } |
| return a.qualifiers[qualCount-1].ID() |
| } |
| |
| // IsOptional returns trivially false for an attribute as the attribute represents a fully |
| // qualified variable name. If the attribute is used in an optional manner, then an attrQualifier |
| // is created and marks the attribute as optional. |
| func (a *relativeAttribute) IsOptional() bool { |
| return false |
| } |
| |
| // AddQualifier implements the Attribute interface method. |
| func (a *relativeAttribute) AddQualifier(qual Qualifier) (Attribute, error) { |
| a.qualifiers = append(a.qualifiers, qual) |
| return a, nil |
| } |
| |
| // Qualify is an implementation of the Qualifier interface method. |
| func (a *relativeAttribute) Qualify(vars Activation, obj any) (any, error) { |
| return attrQualify(a.fac, vars, obj, a) |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (a *relativeAttribute) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return attrQualifyIfPresent(a.fac, vars, obj, a, presenceOnly) |
| } |
| |
| // Resolve expression value and qualifier relative to the expression result. |
| func (a *relativeAttribute) Resolve(vars Activation) (any, error) { |
| // First, evaluate the operand. |
| v := a.operand.Eval(vars) |
| if types.IsError(v) { |
| return nil, v.(*types.Err) |
| } |
| if types.IsUnknown(v) { |
| return v, nil |
| } |
| obj, isOpt, err := applyQualifiers(vars, v, a.qualifiers) |
| if err != nil { |
| return nil, err |
| } |
| if isOpt { |
| val := a.adapter.NativeToValue(obj) |
| if types.IsUnknown(val) { |
| return val, nil |
| } |
| return types.OptionalOf(val), nil |
| } |
| return obj, nil |
| } |
| |
| // String is an implementation of the Stringer interface method. |
| func (a *relativeAttribute) String() string { |
| return fmt.Sprintf("id: %v, operand: %v", a.id, a.operand) |
| } |
| |
| func newQualifier(adapter types.Adapter, id int64, v any, opt, errorOnBadPresenceTest bool) (Qualifier, error) { |
| var qual Qualifier |
| switch val := v.(type) { |
| case Attribute: |
| // Note, attributes are initially identified as non-optional since they represent a top-level |
| // field access; however, when used as a relative qualifier, e.g. a[?b.c], then an attrQualifier |
| // is created which intercepts the IsOptional check for the attribute in order to return the |
| // correct result. |
| return &attrQualifier{ |
| id: id, |
| Attribute: val, |
| optional: opt, |
| }, nil |
| case string: |
| qual = &stringQualifier{ |
| id: id, |
| value: val, |
| celValue: types.String(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case int: |
| qual = &intQualifier{ |
| id: id, |
| value: int64(val), |
| celValue: types.Int(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case int32: |
| qual = &intQualifier{ |
| id: id, |
| value: int64(val), |
| celValue: types.Int(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case int64: |
| qual = &intQualifier{ |
| id: id, |
| value: val, |
| celValue: types.Int(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case uint: |
| qual = &uintQualifier{ |
| id: id, |
| value: uint64(val), |
| celValue: types.Uint(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case uint32: |
| qual = &uintQualifier{ |
| id: id, |
| value: uint64(val), |
| celValue: types.Uint(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case uint64: |
| qual = &uintQualifier{ |
| id: id, |
| value: val, |
| celValue: types.Uint(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case bool: |
| qual = &boolQualifier{ |
| id: id, |
| value: val, |
| celValue: types.Bool(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case float32: |
| qual = &doubleQualifier{ |
| id: id, |
| value: float64(val), |
| celValue: types.Double(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case float64: |
| qual = &doubleQualifier{ |
| id: id, |
| value: val, |
| celValue: types.Double(val), |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case types.String: |
| qual = &stringQualifier{ |
| id: id, |
| value: string(val), |
| celValue: val, |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case types.Int: |
| qual = &intQualifier{ |
| id: id, |
| value: int64(val), |
| celValue: val, |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case types.Uint: |
| qual = &uintQualifier{ |
| id: id, |
| value: uint64(val), |
| celValue: val, |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case types.Bool: |
| qual = &boolQualifier{ |
| id: id, |
| value: bool(val), |
| celValue: val, |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case types.Double: |
| qual = &doubleQualifier{ |
| id: id, |
| value: float64(val), |
| celValue: val, |
| adapter: adapter, |
| optional: opt, |
| errorOnBadPresenceTest: errorOnBadPresenceTest, |
| } |
| case *types.Unknown: |
| qual = &unknownQualifier{id: id, value: val} |
| default: |
| if q, ok := v.(Qualifier); ok { |
| return q, nil |
| } |
| return nil, fmt.Errorf("invalid qualifier type: %T", v) |
| } |
| return qual, nil |
| } |
| |
| type attrQualifier struct { |
| id int64 |
| Attribute |
| optional bool |
| } |
| |
| // ID implements the Qualifier interface method and returns the qualification instruction id |
| // rather than the attribute id. |
| func (q *attrQualifier) ID() int64 { |
| return q.id |
| } |
| |
| // IsOptional implements the Qualifier interface method. |
| func (q *attrQualifier) IsOptional() bool { |
| return q.optional |
| } |
| |
| type stringQualifier struct { |
| id int64 |
| value string |
| celValue ref.Val |
| adapter types.Adapter |
| optional bool |
| errorOnBadPresenceTest bool |
| } |
| |
| // ID is an implementation of the Qualifier interface method. |
| func (q *stringQualifier) ID() int64 { |
| return q.id |
| } |
| |
| // IsOptional implements the Qualifier interface method. |
| func (q *stringQualifier) IsOptional() bool { |
| return q.optional |
| } |
| |
| // Qualify implements the Qualifier interface method. |
| func (q *stringQualifier) Qualify(vars Activation, obj any) (any, error) { |
| val, _, err := q.qualifyInternal(vars, obj, false, false) |
| return val, err |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (q *stringQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return q.qualifyInternal(vars, obj, true, presenceOnly) |
| } |
| |
| func (q *stringQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) { |
| s := q.value |
| switch o := obj.(type) { |
| case map[string]any: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]string: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]int: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]int32: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]int64: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]uint: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]uint32: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]uint64: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]float32: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]float64: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[string]bool: |
| obj, isKey := o[s] |
| if isKey { |
| return obj, true, nil |
| } |
| default: |
| return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest) |
| } |
| if presenceTest { |
| return nil, false, nil |
| } |
| return nil, false, missingKey(q.celValue) |
| } |
| |
| // Value implements the ConstantQualifier interface |
| func (q *stringQualifier) Value() ref.Val { |
| return q.celValue |
| } |
| |
| type intQualifier struct { |
| id int64 |
| value int64 |
| celValue ref.Val |
| adapter types.Adapter |
| optional bool |
| errorOnBadPresenceTest bool |
| } |
| |
| // ID is an implementation of the Qualifier interface method. |
| func (q *intQualifier) ID() int64 { |
| return q.id |
| } |
| |
| // IsOptional implements the Qualifier interface method. |
| func (q *intQualifier) IsOptional() bool { |
| return q.optional |
| } |
| |
| // Qualify implements the Qualifier interface method. |
| func (q *intQualifier) Qualify(vars Activation, obj any) (any, error) { |
| val, _, err := q.qualifyInternal(vars, obj, false, false) |
| return val, err |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (q *intQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return q.qualifyInternal(vars, obj, true, presenceOnly) |
| } |
| |
| func (q *intQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) { |
| i := q.value |
| var isMap bool |
| switch o := obj.(type) { |
| // The specialized map types supported by an int qualifier are considerably fewer than the set |
| // of specialized map types supported by string qualifiers since they are less frequently used |
| // than string-based map keys. Additional specializations may be added in the future if |
| // desired. |
| case map[int]any: |
| isMap = true |
| obj, isKey := o[int(i)] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[int32]any: |
| isMap = true |
| obj, isKey := o[int32(i)] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[int64]any: |
| isMap = true |
| obj, isKey := o[i] |
| if isKey { |
| return obj, true, nil |
| } |
| case []any: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []string: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []int: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []int32: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []int64: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []uint: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []uint32: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []uint64: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []float32: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []float64: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| case []bool: |
| isIndex := i >= 0 && i < int64(len(o)) |
| if isIndex { |
| return o[i], true, nil |
| } |
| default: |
| return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest) |
| } |
| if presenceTest { |
| return nil, false, nil |
| } |
| if isMap { |
| return nil, false, missingKey(q.celValue) |
| } |
| return nil, false, missingIndex(q.celValue) |
| } |
| |
| // Value implements the ConstantQualifier interface |
| func (q *intQualifier) Value() ref.Val { |
| return q.celValue |
| } |
| |
| type uintQualifier struct { |
| id int64 |
| value uint64 |
| celValue ref.Val |
| adapter types.Adapter |
| optional bool |
| errorOnBadPresenceTest bool |
| } |
| |
| // ID is an implementation of the Qualifier interface method. |
| func (q *uintQualifier) ID() int64 { |
| return q.id |
| } |
| |
| // IsOptional implements the Qualifier interface method. |
| func (q *uintQualifier) IsOptional() bool { |
| return q.optional |
| } |
| |
| // Qualify implements the Qualifier interface method. |
| func (q *uintQualifier) Qualify(vars Activation, obj any) (any, error) { |
| val, _, err := q.qualifyInternal(vars, obj, false, false) |
| return val, err |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (q *uintQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return q.qualifyInternal(vars, obj, true, presenceOnly) |
| } |
| |
| func (q *uintQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) { |
| u := q.value |
| switch o := obj.(type) { |
| // The specialized map types supported by a uint qualifier are considerably fewer than the set |
| // of specialized map types supported by string qualifiers since they are less frequently used |
| // than string-based map keys. Additional specializations may be added in the future if |
| // desired. |
| case map[uint]any: |
| obj, isKey := o[uint(u)] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[uint32]any: |
| obj, isKey := o[uint32(u)] |
| if isKey { |
| return obj, true, nil |
| } |
| case map[uint64]any: |
| obj, isKey := o[u] |
| if isKey { |
| return obj, true, nil |
| } |
| default: |
| return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest) |
| } |
| if presenceTest { |
| return nil, false, nil |
| } |
| return nil, false, missingKey(q.celValue) |
| } |
| |
| // Value implements the ConstantQualifier interface |
| func (q *uintQualifier) Value() ref.Val { |
| return q.celValue |
| } |
| |
| type boolQualifier struct { |
| id int64 |
| value bool |
| celValue ref.Val |
| adapter types.Adapter |
| optional bool |
| errorOnBadPresenceTest bool |
| } |
| |
| // ID is an implementation of the Qualifier interface method. |
| func (q *boolQualifier) ID() int64 { |
| return q.id |
| } |
| |
| // IsOptional implements the Qualifier interface method. |
| func (q *boolQualifier) IsOptional() bool { |
| return q.optional |
| } |
| |
| // Qualify implements the Qualifier interface method. |
| func (q *boolQualifier) Qualify(vars Activation, obj any) (any, error) { |
| val, _, err := q.qualifyInternal(vars, obj, false, false) |
| return val, err |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (q *boolQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return q.qualifyInternal(vars, obj, true, presenceOnly) |
| } |
| |
| func (q *boolQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) { |
| b := q.value |
| switch o := obj.(type) { |
| case map[bool]any: |
| obj, isKey := o[b] |
| if isKey { |
| return obj, true, nil |
| } |
| default: |
| return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest) |
| } |
| if presenceTest { |
| return nil, false, nil |
| } |
| return nil, false, missingKey(q.celValue) |
| } |
| |
| // Value implements the ConstantQualifier interface |
| func (q *boolQualifier) Value() ref.Val { |
| return q.celValue |
| } |
| |
| // fieldQualifier indicates that the qualification is a well-defined field with a known |
| // field type. When the field type is known this can be used to improve the speed and |
| // efficiency of field resolution. |
| type fieldQualifier struct { |
| id int64 |
| Name string |
| FieldType *types.FieldType |
| adapter types.Adapter |
| optional bool |
| } |
| |
| // ID is an implementation of the Qualifier interface method. |
| func (q *fieldQualifier) ID() int64 { |
| return q.id |
| } |
| |
| // IsOptional implements the Qualifier interface method. |
| func (q *fieldQualifier) IsOptional() bool { |
| return q.optional |
| } |
| |
| // Qualify implements the Qualifier interface method. |
| func (q *fieldQualifier) Qualify(vars Activation, obj any) (any, error) { |
| if rv, ok := obj.(ref.Val); ok { |
| obj = rv.Value() |
| } |
| val, err := q.FieldType.GetFrom(obj) |
| if err != nil { |
| return nil, err |
| } |
| return val, nil |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (q *fieldQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| if rv, ok := obj.(ref.Val); ok { |
| obj = rv.Value() |
| } |
| if !q.FieldType.IsSet(obj) { |
| return nil, false, nil |
| } |
| if presenceOnly { |
| return nil, true, nil |
| } |
| val, err := q.FieldType.GetFrom(obj) |
| if err != nil { |
| return nil, false, err |
| } |
| return val, true, nil |
| } |
| |
| // Value implements the ConstantQualifier interface |
| func (q *fieldQualifier) Value() ref.Val { |
| return types.String(q.Name) |
| } |
| |
| // doubleQualifier qualifies a CEL object, map, or list using a double value. |
| // |
| // This qualifier is used for working with dynamic data like JSON or protobuf.Any where the value |
| // type may not be known ahead of time and may not conform to the standard types supported as valid |
| // protobuf map key types. |
| type doubleQualifier struct { |
| id int64 |
| value float64 |
| celValue ref.Val |
| adapter types.Adapter |
| optional bool |
| errorOnBadPresenceTest bool |
| } |
| |
| // ID is an implementation of the Qualifier interface method. |
| func (q *doubleQualifier) ID() int64 { |
| return q.id |
| } |
| |
| // IsOptional implements the Qualifier interface method. |
| func (q *doubleQualifier) IsOptional() bool { |
| return q.optional |
| } |
| |
| // Qualify implements the Qualifier interface method. |
| func (q *doubleQualifier) Qualify(vars Activation, obj any) (any, error) { |
| val, _, err := q.qualifyInternal(vars, obj, false, false) |
| return val, err |
| } |
| |
| func (q *doubleQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return q.qualifyInternal(vars, obj, true, presenceOnly) |
| } |
| |
| func (q *doubleQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) { |
| return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest) |
| } |
| |
| // Value implements the ConstantQualifier interface |
| func (q *doubleQualifier) Value() ref.Val { |
| return q.celValue |
| } |
| |
| // unknownQualifier is a simple qualifier which always returns a preconfigured set of unknown values |
| // for any value subject to qualification. This is consistent with CEL's unknown handling elsewhere. |
| type unknownQualifier struct { |
| id int64 |
| value *types.Unknown |
| } |
| |
| // ID is an implementation of the Qualifier interface method. |
| func (q *unknownQualifier) ID() int64 { |
| return q.id |
| } |
| |
| // IsOptional returns trivially false as an the unknown value is always returned. |
| func (q *unknownQualifier) IsOptional() bool { |
| return false |
| } |
| |
| // Qualify returns the unknown value associated with this qualifier. |
| func (q *unknownQualifier) Qualify(vars Activation, obj any) (any, error) { |
| return q.value, nil |
| } |
| |
| // QualifyIfPresent is an implementation of the Qualifier interface method. |
| func (q *unknownQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) { |
| return q.value, true, nil |
| } |
| |
| // Value implements the ConstantQualifier interface |
| func (q *unknownQualifier) Value() ref.Val { |
| return q.value |
| } |
| |
| func applyQualifiers(vars Activation, obj any, qualifiers []Qualifier) (any, bool, error) { |
| optObj, isOpt := obj.(*types.Optional) |
| if isOpt { |
| if !optObj.HasValue() { |
| return optObj, false, nil |
| } |
| obj = optObj.GetValue() |
| } |
| |
| var err error |
| for _, qual := range qualifiers { |
| var qualObj any |
| isOpt = isOpt || qual.IsOptional() |
| if isOpt { |
| var present bool |
| qualObj, present, err = qual.QualifyIfPresent(vars, obj, false) |
| if err != nil { |
| return nil, false, err |
| } |
| if !present { |
| // We return optional none here with a presence of 'false' as the layers |
| // above will attempt to call types.OptionalOf() on a present value if any |
| // of the qualifiers is optional. |
| return types.OptionalNone, false, nil |
| } |
| } else { |
| qualObj, err = qual.Qualify(vars, obj) |
| if err != nil { |
| return nil, false, err |
| } |
| } |
| obj = qualObj |
| } |
| return obj, isOpt, nil |
| } |
| |
| // attrQualify performs a qualification using the result of an attribute evaluation. |
| func attrQualify(fac AttributeFactory, vars Activation, obj any, qualAttr Attribute) (any, error) { |
| val, err := qualAttr.Resolve(vars) |
| if err != nil { |
| return nil, err |
| } |
| qual, err := fac.NewQualifier(nil, qualAttr.ID(), val, qualAttr.IsOptional()) |
| if err != nil { |
| return nil, err |
| } |
| return qual.Qualify(vars, obj) |
| } |
| |
| // attrQualifyIfPresent conditionally performs the qualification of the result of attribute is present |
| // on the target object. |
| func attrQualifyIfPresent(fac AttributeFactory, vars Activation, obj any, qualAttr Attribute, |
| presenceOnly bool) (any, bool, error) { |
| val, err := qualAttr.Resolve(vars) |
| if err != nil { |
| return nil, false, err |
| } |
| qual, err := fac.NewQualifier(nil, qualAttr.ID(), val, qualAttr.IsOptional()) |
| if err != nil { |
| return nil, false, err |
| } |
| return qual.QualifyIfPresent(vars, obj, presenceOnly) |
| } |
| |
| // refQualify attempts to convert the value to a CEL value and then uses reflection methods to try and |
| // apply the qualifier with the option to presence test field accesses before retrieving field values. |
| func refQualify(adapter types.Adapter, obj any, idx ref.Val, presenceTest, presenceOnly, errorOnBadPresenceTest bool) (ref.Val, bool, error) { |
| celVal := adapter.NativeToValue(obj) |
| switch v := celVal.(type) { |
| case *types.Unknown: |
| return v, true, nil |
| case *types.Err: |
| return nil, false, v |
| case traits.Mapper: |
| val, found := v.Find(idx) |
| // If the index is of the wrong type for the map, then it is possible |
| // for the Find call to produce an error. |
| if types.IsError(val) { |
| return nil, false, val.(*types.Err) |
| } |
| if found { |
| return val, true, nil |
| } |
| if presenceTest { |
| return nil, false, nil |
| } |
| return nil, false, missingKey(idx) |
| case traits.Lister: |
| // If the index argument is not a valid numeric type, then it is possible |
| // for the index operation to produce an error. |
| i, err := types.IndexOrError(idx) |
| if err != nil { |
| return nil, false, err |
| } |
| celIndex := types.Int(i) |
| if i >= 0 && celIndex < v.Size().(types.Int) { |
| return v.Get(idx), true, nil |
| } |
| if presenceTest { |
| return nil, false, nil |
| } |
| return nil, false, missingIndex(idx) |
| case traits.Indexer: |
| if presenceTest { |
| ft, ok := v.(traits.FieldTester) |
| if ok { |
| presence := ft.IsSet(idx) |
| if types.IsError(presence) { |
| return nil, false, presence.(*types.Err) |
| } |
| // If not found or presence only test, then return. |
| // Otherwise, if found, obtain the value later on. |
| if presenceOnly || presence == types.False { |
| return nil, presence == types.True, nil |
| } |
| } |
| } |
| val := v.Get(idx) |
| if types.IsError(val) { |
| return nil, false, val.(*types.Err) |
| } |
| return val, true, nil |
| default: |
| if presenceTest && !errorOnBadPresenceTest { |
| return nil, false, nil |
| } |
| return nil, false, missingKey(idx) |
| } |
| } |
| |
| // resolutionError is a custom error type which encodes the different error states which may |
| // occur during attribute resolution. |
| type resolutionError struct { |
| missingAttribute string |
| missingIndex ref.Val |
| missingKey ref.Val |
| } |
| |
| func (e *resolutionError) isMissingAttribute() bool { |
| return e.missingAttribute != "" |
| } |
| |
| func missingIndex(missing ref.Val) *resolutionError { |
| return &resolutionError{ |
| missingIndex: missing, |
| } |
| } |
| |
| func missingKey(missing ref.Val) *resolutionError { |
| return &resolutionError{ |
| missingKey: missing, |
| } |
| } |
| |
| func missingAttribute(attr string) *resolutionError { |
| return &resolutionError{ |
| missingAttribute: attr, |
| } |
| } |
| |
| // Error implements the error interface method. |
| func (e *resolutionError) Error() string { |
| if e.missingKey != nil { |
| return fmt.Sprintf("no such key: %v", e.missingKey) |
| } |
| if e.missingIndex != nil { |
| return fmt.Sprintf("index out of bounds: %v", e.missingIndex) |
| } |
| if e.missingAttribute != "" { |
| return fmt.Sprintf("no such attribute(s): %s", e.missingAttribute) |
| } |
| return "invalid attribute" |
| } |
| |
| // Is implements the errors.Is() method used by more recent versions of Go. |
| func (e *resolutionError) Is(err error) bool { |
| return err.Error() == e.Error() |
| } |