mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-15 02:40:23 +00:00
614 lines
20 KiB
Go
614 lines
20 KiB
Go
|
// 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 (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/google/cel-go/checker"
|
||
|
"github.com/google/cel-go/checker/decls"
|
||
|
"github.com/google/cel-go/common"
|
||
|
"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/interpreter"
|
||
|
"github.com/google/cel-go/parser"
|
||
|
|
||
|
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||
|
)
|
||
|
|
||
|
// Source interface representing a user-provided expression.
|
||
|
type Source = common.Source
|
||
|
|
||
|
// Ast representing the checked or unchecked expression, its source, and related metadata such as
|
||
|
// source position information.
|
||
|
type Ast struct {
|
||
|
expr *exprpb.Expr
|
||
|
info *exprpb.SourceInfo
|
||
|
source Source
|
||
|
refMap map[int64]*exprpb.Reference
|
||
|
typeMap map[int64]*exprpb.Type
|
||
|
}
|
||
|
|
||
|
// Expr returns the proto serializable instance of the parsed/checked expression.
|
||
|
func (ast *Ast) Expr() *exprpb.Expr {
|
||
|
return ast.expr
|
||
|
}
|
||
|
|
||
|
// IsChecked returns whether the Ast value has been successfully type-checked.
|
||
|
func (ast *Ast) IsChecked() bool {
|
||
|
return ast.typeMap != nil && len(ast.typeMap) > 0
|
||
|
}
|
||
|
|
||
|
// SourceInfo returns character offset and newline position information about expression elements.
|
||
|
func (ast *Ast) SourceInfo() *exprpb.SourceInfo {
|
||
|
return ast.info
|
||
|
}
|
||
|
|
||
|
// ResultType returns the output type of the expression if the Ast has been type-checked, else
|
||
|
// returns decls.Dyn as the parse step cannot infer the type.
|
||
|
//
|
||
|
// Deprecated: use OutputType
|
||
|
func (ast *Ast) ResultType() *exprpb.Type {
|
||
|
if !ast.IsChecked() {
|
||
|
return decls.Dyn
|
||
|
}
|
||
|
return ast.typeMap[ast.expr.GetId()]
|
||
|
}
|
||
|
|
||
|
// OutputType returns the output type of the expression if the Ast has been type-checked, else
|
||
|
// returns cel.DynType as the parse step cannot infer types.
|
||
|
func (ast *Ast) OutputType() *Type {
|
||
|
t, err := ExprTypeToType(ast.ResultType())
|
||
|
if err != nil {
|
||
|
return DynType
|
||
|
}
|
||
|
return t
|
||
|
}
|
||
|
|
||
|
// Source returns a view of the input used to create the Ast. This source may be complete or
|
||
|
// constructed from the SourceInfo.
|
||
|
func (ast *Ast) Source() Source {
|
||
|
return ast.source
|
||
|
}
|
||
|
|
||
|
// FormatType converts a type message into a string representation.
|
||
|
func FormatType(t *exprpb.Type) string {
|
||
|
return checker.FormatCheckedType(t)
|
||
|
}
|
||
|
|
||
|
// Env encapsulates the context necessary to perform parsing, type checking, or generation of
|
||
|
// evaluable programs for different expressions.
|
||
|
type Env struct {
|
||
|
Container *containers.Container
|
||
|
functions map[string]*functionDecl
|
||
|
declarations []*exprpb.Decl
|
||
|
macros []parser.Macro
|
||
|
adapter ref.TypeAdapter
|
||
|
provider ref.TypeProvider
|
||
|
features map[int]bool
|
||
|
appliedFeatures map[int]bool
|
||
|
|
||
|
// Internal parser representation
|
||
|
prsr *parser.Parser
|
||
|
|
||
|
// Internal checker representation
|
||
|
chk *checker.Env
|
||
|
chkErr error
|
||
|
chkOnce sync.Once
|
||
|
chkOpts []checker.Option
|
||
|
|
||
|
// Program options tied to the environment
|
||
|
progOpts []ProgramOption
|
||
|
}
|
||
|
|
||
|
// NewEnv creates a program environment configured with the standard library of CEL functions and
|
||
|
// macros. The Env value returned can parse and check any CEL program which builds upon the core
|
||
|
// features documented in the CEL specification.
|
||
|
//
|
||
|
// See the EnvOption helper functions for the options that can be used to configure the
|
||
|
// environment.
|
||
|
func NewEnv(opts ...EnvOption) (*Env, error) {
|
||
|
// Extend the statically configured standard environment, disabling eager validation to ensure
|
||
|
// the cost of setup for the environment is still just as cheap as it is in v0.11.x and earlier
|
||
|
// releases. The user provided options can easily re-enable the eager validation as they are
|
||
|
// processed after this default option.
|
||
|
stdOpts := append([]EnvOption{EagerlyValidateDeclarations(false)}, opts...)
|
||
|
env, err := getStdEnv()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return env.Extend(stdOpts...)
|
||
|
}
|
||
|
|
||
|
// NewCustomEnv creates a custom program environment which is not automatically configured with the
|
||
|
// standard library of functions and macros documented in the CEL spec.
|
||
|
//
|
||
|
// The purpose for using a custom environment might be for subsetting the standard library produced
|
||
|
// by the cel.StdLib() function. Subsetting CEL is a core aspect of its design that allows users to
|
||
|
// limit the compute and memory impact of a CEL program by controlling the functions and macros
|
||
|
// that may appear in a given expression.
|
||
|
//
|
||
|
// See the EnvOption helper functions for the options that can be used to configure the
|
||
|
// environment.
|
||
|
func NewCustomEnv(opts ...EnvOption) (*Env, error) {
|
||
|
registry, err := types.NewRegistry()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return (&Env{
|
||
|
declarations: []*exprpb.Decl{},
|
||
|
functions: map[string]*functionDecl{},
|
||
|
macros: []parser.Macro{},
|
||
|
Container: containers.DefaultContainer,
|
||
|
adapter: registry,
|
||
|
provider: registry,
|
||
|
features: map[int]bool{},
|
||
|
appliedFeatures: map[int]bool{},
|
||
|
progOpts: []ProgramOption{},
|
||
|
}).configure(opts)
|
||
|
}
|
||
|
|
||
|
// Check performs type-checking on the input Ast and yields a checked Ast and/or set of Issues.
|
||
|
//
|
||
|
// Checking has failed if the returned Issues value and its Issues.Err() value are non-nil.
|
||
|
// Issues should be inspected if they are non-nil, but may not represent a fatal error.
|
||
|
//
|
||
|
// It is possible to have both non-nil Ast and Issues values returned from this call: however,
|
||
|
// the mere presence of an Ast does not imply that it is valid for use.
|
||
|
func (e *Env) Check(ast *Ast) (*Ast, *Issues) {
|
||
|
// Note, errors aren't currently possible on the Ast to ParsedExpr conversion.
|
||
|
pe, _ := AstToParsedExpr(ast)
|
||
|
|
||
|
// Construct the internal checker env, erroring if there is an issue adding the declarations.
|
||
|
err := e.initChecker()
|
||
|
if err != nil {
|
||
|
errs := common.NewErrors(ast.Source())
|
||
|
errs.ReportError(common.NoLocation, e.chkErr.Error())
|
||
|
return nil, NewIssues(errs)
|
||
|
}
|
||
|
|
||
|
res, errs := checker.Check(pe, ast.Source(), e.chk)
|
||
|
if len(errs.GetErrors()) > 0 {
|
||
|
return nil, NewIssues(errs)
|
||
|
}
|
||
|
// Manually create the Ast to ensure that the Ast source information (which may be more
|
||
|
// detailed than the information provided by Check), is returned to the caller.
|
||
|
return &Ast{
|
||
|
source: ast.Source(),
|
||
|
expr: res.GetExpr(),
|
||
|
info: res.GetSourceInfo(),
|
||
|
refMap: res.GetReferenceMap(),
|
||
|
typeMap: res.GetTypeMap()}, nil
|
||
|
}
|
||
|
|
||
|
// Compile combines the Parse and Check phases CEL program compilation to produce an Ast and
|
||
|
// associated issues.
|
||
|
//
|
||
|
// If an error is encountered during parsing the Compile step will not continue with the Check
|
||
|
// phase. If non-error issues are encountered during Parse, they may be combined with any issues
|
||
|
// discovered during Check.
|
||
|
//
|
||
|
// Note, for parse-only uses of CEL use Parse.
|
||
|
func (e *Env) Compile(txt string) (*Ast, *Issues) {
|
||
|
return e.CompileSource(common.NewTextSource(txt))
|
||
|
}
|
||
|
|
||
|
// CompileSource combines the Parse and Check phases CEL program compilation to produce an Ast and
|
||
|
// associated issues.
|
||
|
//
|
||
|
// If an error is encountered during parsing the CompileSource step will not continue with the
|
||
|
// Check phase. If non-error issues are encountered during Parse, they may be combined with any
|
||
|
// issues discovered during Check.
|
||
|
//
|
||
|
// Note, for parse-only uses of CEL use Parse.
|
||
|
func (e *Env) CompileSource(src Source) (*Ast, *Issues) {
|
||
|
ast, iss := e.ParseSource(src)
|
||
|
if iss.Err() != nil {
|
||
|
return nil, iss
|
||
|
}
|
||
|
checked, iss2 := e.Check(ast)
|
||
|
if iss2.Err() != nil {
|
||
|
return nil, iss2
|
||
|
}
|
||
|
return checked, iss2
|
||
|
}
|
||
|
|
||
|
// Extend the current environment with additional options to produce a new Env.
|
||
|
//
|
||
|
// Note, the extended Env value should not share memory with the original. It is possible, however,
|
||
|
// that a CustomTypeAdapter or CustomTypeProvider options could provide values which are mutable.
|
||
|
// To ensure separation of state between extended environments either make sure the TypeAdapter and
|
||
|
// TypeProvider are immutable, or that their underlying implementations are based on the
|
||
|
// ref.TypeRegistry which provides a Copy method which will be invoked by this method.
|
||
|
func (e *Env) Extend(opts ...EnvOption) (*Env, error) {
|
||
|
if e.chkErr != nil {
|
||
|
return nil, e.chkErr
|
||
|
}
|
||
|
|
||
|
// The type-checker is configured with Declarations. The declarations may either be provided
|
||
|
// as options which have not yet been validated, or may come from a previous checker instance
|
||
|
// whose types have already been validated.
|
||
|
chkOptsCopy := make([]checker.Option, len(e.chkOpts))
|
||
|
copy(chkOptsCopy, e.chkOpts)
|
||
|
|
||
|
// Copy the declarations if needed.
|
||
|
decsCopy := []*exprpb.Decl{}
|
||
|
if e.chk != nil {
|
||
|
// If the type-checker has already been instantiated, then the e.declarations have been
|
||
|
// valdiated within the chk instance.
|
||
|
chkOptsCopy = append(chkOptsCopy, checker.ValidatedDeclarations(e.chk))
|
||
|
} else {
|
||
|
// If the type-checker has not been instantiated, ensure the unvalidated declarations are
|
||
|
// provided to the extended Env instance.
|
||
|
decsCopy = make([]*exprpb.Decl, len(e.declarations))
|
||
|
copy(decsCopy, e.declarations)
|
||
|
}
|
||
|
|
||
|
// Copy macros and program options
|
||
|
macsCopy := make([]parser.Macro, len(e.macros))
|
||
|
progOptsCopy := make([]ProgramOption, len(e.progOpts))
|
||
|
copy(macsCopy, e.macros)
|
||
|
copy(progOptsCopy, e.progOpts)
|
||
|
|
||
|
// Copy the adapter / provider if they appear to be mutable.
|
||
|
adapter := e.adapter
|
||
|
provider := e.provider
|
||
|
adapterReg, isAdapterReg := e.adapter.(ref.TypeRegistry)
|
||
|
providerReg, isProviderReg := e.provider.(ref.TypeRegistry)
|
||
|
// In most cases the provider and adapter will be a ref.TypeRegistry;
|
||
|
// however, in the rare cases where they are not, they are assumed to
|
||
|
// be immutable. Since it is possible to set the TypeProvider separately
|
||
|
// from the TypeAdapter, the possible configurations which could use a
|
||
|
// TypeRegistry as the base implementation are captured below.
|
||
|
if isAdapterReg && isProviderReg {
|
||
|
reg := providerReg.Copy()
|
||
|
provider = reg
|
||
|
// If the adapter and provider are the same object, set the adapter
|
||
|
// to the same ref.TypeRegistry as the provider.
|
||
|
if adapterReg == providerReg {
|
||
|
adapter = reg
|
||
|
} else {
|
||
|
// Otherwise, make a copy of the adapter.
|
||
|
adapter = adapterReg.Copy()
|
||
|
}
|
||
|
} else if isProviderReg {
|
||
|
provider = providerReg.Copy()
|
||
|
} else if isAdapterReg {
|
||
|
adapter = adapterReg.Copy()
|
||
|
}
|
||
|
|
||
|
featuresCopy := make(map[int]bool, len(e.features))
|
||
|
for k, v := range e.features {
|
||
|
featuresCopy[k] = v
|
||
|
}
|
||
|
appliedFeaturesCopy := make(map[int]bool, len(e.appliedFeatures))
|
||
|
for k, v := range e.appliedFeatures {
|
||
|
appliedFeaturesCopy[k] = v
|
||
|
}
|
||
|
funcsCopy := make(map[string]*functionDecl, len(e.functions))
|
||
|
for k, v := range e.functions {
|
||
|
funcsCopy[k] = v
|
||
|
}
|
||
|
|
||
|
// TODO: functions copy needs to happen here.
|
||
|
ext := &Env{
|
||
|
Container: e.Container,
|
||
|
declarations: decsCopy,
|
||
|
functions: funcsCopy,
|
||
|
macros: macsCopy,
|
||
|
progOpts: progOptsCopy,
|
||
|
adapter: adapter,
|
||
|
features: featuresCopy,
|
||
|
appliedFeatures: appliedFeaturesCopy,
|
||
|
provider: provider,
|
||
|
chkOpts: chkOptsCopy,
|
||
|
}
|
||
|
return ext.configure(opts)
|
||
|
}
|
||
|
|
||
|
// HasFeature checks whether the environment enables the given feature
|
||
|
// flag, as enumerated in options.go.
|
||
|
func (e *Env) HasFeature(flag int) bool {
|
||
|
enabled, has := e.features[flag]
|
||
|
return has && enabled
|
||
|
}
|
||
|
|
||
|
// Parse parses the input expression value `txt` to a Ast and/or a set of Issues.
|
||
|
//
|
||
|
// This form of Parse creates a Source value for the input `txt` and forwards to the
|
||
|
// ParseSource method.
|
||
|
func (e *Env) Parse(txt string) (*Ast, *Issues) {
|
||
|
src := common.NewTextSource(txt)
|
||
|
return e.ParseSource(src)
|
||
|
}
|
||
|
|
||
|
// ParseSource parses the input source to an Ast and/or set of Issues.
|
||
|
//
|
||
|
// Parsing has failed if the returned Issues value and its Issues.Err() value is non-nil.
|
||
|
// Issues should be inspected if they are non-nil, but may not represent a fatal error.
|
||
|
//
|
||
|
// It is possible to have both non-nil Ast and Issues values returned from this call; however,
|
||
|
// the mere presence of an Ast does not imply that it is valid for use.
|
||
|
func (e *Env) ParseSource(src Source) (*Ast, *Issues) {
|
||
|
res, errs := e.prsr.Parse(src)
|
||
|
if len(errs.GetErrors()) > 0 {
|
||
|
return nil, &Issues{errs: errs}
|
||
|
}
|
||
|
// Manually create the Ast to ensure that the text source information is propagated on
|
||
|
// subsequent calls to Check.
|
||
|
return &Ast{
|
||
|
source: src,
|
||
|
expr: res.GetExpr(),
|
||
|
info: res.GetSourceInfo()}, nil
|
||
|
}
|
||
|
|
||
|
// Program generates an evaluable instance of the Ast within the environment (Env).
|
||
|
func (e *Env) Program(ast *Ast, opts ...ProgramOption) (Program, error) {
|
||
|
optSet := e.progOpts
|
||
|
if len(opts) != 0 {
|
||
|
mergedOpts := []ProgramOption{}
|
||
|
mergedOpts = append(mergedOpts, e.progOpts...)
|
||
|
mergedOpts = append(mergedOpts, opts...)
|
||
|
optSet = mergedOpts
|
||
|
}
|
||
|
return newProgram(e, ast, optSet)
|
||
|
}
|
||
|
|
||
|
// TypeAdapter returns the `ref.TypeAdapter` configured for the environment.
|
||
|
func (e *Env) TypeAdapter() ref.TypeAdapter {
|
||
|
return e.adapter
|
||
|
}
|
||
|
|
||
|
// TypeProvider returns the `ref.TypeProvider` configured for the environment.
|
||
|
func (e *Env) TypeProvider() ref.TypeProvider {
|
||
|
return e.provider
|
||
|
}
|
||
|
|
||
|
// UnknownVars returns an interpreter.PartialActivation which marks all variables
|
||
|
// declared in the Env as unknown AttributePattern values.
|
||
|
//
|
||
|
// Note, the UnknownVars will behave the same as an interpreter.EmptyActivation
|
||
|
// unless the PartialAttributes option is provided as a ProgramOption.
|
||
|
func (e *Env) UnknownVars() interpreter.PartialActivation {
|
||
|
var unknownPatterns []*interpreter.AttributePattern
|
||
|
for _, d := range e.declarations {
|
||
|
switch d.GetDeclKind().(type) {
|
||
|
case *exprpb.Decl_Ident:
|
||
|
unknownPatterns = append(unknownPatterns,
|
||
|
interpreter.NewAttributePattern(d.GetName()))
|
||
|
}
|
||
|
}
|
||
|
part, _ := PartialVars(
|
||
|
interpreter.EmptyActivation(),
|
||
|
unknownPatterns...)
|
||
|
return part
|
||
|
}
|
||
|
|
||
|
// ResidualAst takes an Ast and its EvalDetails to produce a new Ast which only contains the
|
||
|
// attribute references which are unknown.
|
||
|
//
|
||
|
// Residual expressions are beneficial in a few scenarios:
|
||
|
//
|
||
|
// - Optimizing constant expression evaluations away.
|
||
|
// - Indexing and pruning expressions based on known input arguments.
|
||
|
// - Surfacing additional requirements that are needed in order to complete an evaluation.
|
||
|
// - Sharing the evaluation of an expression across multiple machines/nodes.
|
||
|
//
|
||
|
// For example, if an expression targets a 'resource' and 'request' attribute and the possible
|
||
|
// values for the resource are known, a PartialActivation could mark the 'request' as an unknown
|
||
|
// interpreter.AttributePattern and the resulting ResidualAst would be reduced to only the parts
|
||
|
// of the expression that reference the 'request'.
|
||
|
//
|
||
|
// Note, the expression ids within the residual AST generated through this method have no
|
||
|
// correlation to the expression ids of the original AST.
|
||
|
//
|
||
|
// See the PartialVars helper for how to construct a PartialActivation.
|
||
|
//
|
||
|
// TODO: Consider adding an option to generate a Program.Residual to avoid round-tripping to an
|
||
|
// Ast format and then Program again.
|
||
|
func (e *Env) ResidualAst(a *Ast, details *EvalDetails) (*Ast, error) {
|
||
|
pruned := interpreter.PruneAst(a.Expr(), details.State())
|
||
|
expr, err := AstToString(ParsedExprToAst(&exprpb.ParsedExpr{Expr: pruned}))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
parsed, iss := e.Parse(expr)
|
||
|
if iss != nil && iss.Err() != nil {
|
||
|
return nil, iss.Err()
|
||
|
}
|
||
|
if !a.IsChecked() {
|
||
|
return parsed, nil
|
||
|
}
|
||
|
checked, iss := e.Check(parsed)
|
||
|
if iss != nil && iss.Err() != nil {
|
||
|
return nil, iss.Err()
|
||
|
}
|
||
|
return checked, nil
|
||
|
}
|
||
|
|
||
|
// EstimateCost estimates the cost of a type checked CEL expression using the length estimates of input data and
|
||
|
// extension functions provided by estimator.
|
||
|
func (e *Env) EstimateCost(ast *Ast, estimator checker.CostEstimator) (checker.CostEstimate, error) {
|
||
|
checked, err := AstToCheckedExpr(ast)
|
||
|
if err != nil {
|
||
|
return checker.CostEstimate{}, fmt.Errorf("EsimateCost could not inspect Ast: %v", err)
|
||
|
}
|
||
|
return checker.Cost(checked, estimator), nil
|
||
|
}
|
||
|
|
||
|
// configure applies a series of EnvOptions to the current environment.
|
||
|
func (e *Env) configure(opts []EnvOption) (*Env, error) {
|
||
|
// Customized the environment using the provided EnvOption values. If an error is
|
||
|
// generated at any step this, will be returned as a nil Env with a non-nil error.
|
||
|
var err error
|
||
|
for _, opt := range opts {
|
||
|
e, err = opt(e)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If the default UTC timezone fix has been enabled, make sure the library is configured
|
||
|
if e.HasFeature(featureDefaultUTCTimeZone) {
|
||
|
if _, found := e.appliedFeatures[featureDefaultUTCTimeZone]; !found {
|
||
|
e, err = Lib(timeUTCLibrary{})(e)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
// record that the feature has been applied since it will generate declarations
|
||
|
// and functions which will be propagated on Extend() calls and which should only
|
||
|
// be registered once.
|
||
|
e.appliedFeatures[featureDefaultUTCTimeZone] = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Initialize all of the functions configured within the environment.
|
||
|
for _, fn := range e.functions {
|
||
|
err = fn.init()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Configure the parser.
|
||
|
prsrOpts := []parser.Option{parser.Macros(e.macros...)}
|
||
|
if e.HasFeature(featureEnableMacroCallTracking) {
|
||
|
prsrOpts = append(prsrOpts, parser.PopulateMacroCalls(true))
|
||
|
}
|
||
|
e.prsr, err = parser.NewParser(prsrOpts...)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Ensure that the checker init happens eagerly rather than lazily.
|
||
|
if e.HasFeature(featureEagerlyValidateDeclarations) {
|
||
|
err := e.initChecker()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return e, nil
|
||
|
}
|
||
|
|
||
|
func (e *Env) initChecker() error {
|
||
|
e.chkOnce.Do(func() {
|
||
|
chkOpts := []checker.Option{}
|
||
|
chkOpts = append(chkOpts, e.chkOpts...)
|
||
|
chkOpts = append(chkOpts,
|
||
|
checker.HomogeneousAggregateLiterals(
|
||
|
e.HasFeature(featureDisableDynamicAggregateLiterals)),
|
||
|
checker.CrossTypeNumericComparisons(
|
||
|
e.HasFeature(featureCrossTypeNumericComparisons)))
|
||
|
|
||
|
ce, err := checker.NewEnv(e.Container, e.provider, chkOpts...)
|
||
|
if err != nil {
|
||
|
e.chkErr = err
|
||
|
return
|
||
|
}
|
||
|
// Add the statically configured declarations.
|
||
|
err = ce.Add(e.declarations...)
|
||
|
if err != nil {
|
||
|
e.chkErr = err
|
||
|
return
|
||
|
}
|
||
|
// Add the function declarations which are derived from the FunctionDecl instances.
|
||
|
for _, fn := range e.functions {
|
||
|
fnDecl, err := functionDeclToExprDecl(fn)
|
||
|
if err != nil {
|
||
|
e.chkErr = err
|
||
|
return
|
||
|
}
|
||
|
err = ce.Add(fnDecl)
|
||
|
if err != nil {
|
||
|
e.chkErr = err
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
// Add function declarations here separately.
|
||
|
e.chk = ce
|
||
|
})
|
||
|
return e.chkErr
|
||
|
}
|
||
|
|
||
|
// Issues defines methods for inspecting the error details of parse and check calls.
|
||
|
//
|
||
|
// Note: in the future, non-fatal warnings and notices may be inspectable via the Issues struct.
|
||
|
type Issues struct {
|
||
|
errs *common.Errors
|
||
|
}
|
||
|
|
||
|
// NewIssues returns an Issues struct from a common.Errors object.
|
||
|
func NewIssues(errs *common.Errors) *Issues {
|
||
|
return &Issues{
|
||
|
errs: errs,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Err returns an error value if the issues list contains one or more errors.
|
||
|
func (i *Issues) Err() error {
|
||
|
if i == nil {
|
||
|
return nil
|
||
|
}
|
||
|
if len(i.Errors()) > 0 {
|
||
|
return errors.New(i.String())
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Errors returns the collection of errors encountered in more granular detail.
|
||
|
func (i *Issues) Errors() []common.Error {
|
||
|
if i == nil {
|
||
|
return []common.Error{}
|
||
|
}
|
||
|
return i.errs.GetErrors()
|
||
|
}
|
||
|
|
||
|
// Append collects the issues from another Issues struct into a new Issues object.
|
||
|
func (i *Issues) Append(other *Issues) *Issues {
|
||
|
if i == nil {
|
||
|
return other
|
||
|
}
|
||
|
if other == nil {
|
||
|
return i
|
||
|
}
|
||
|
return NewIssues(i.errs.Append(other.errs.GetErrors()))
|
||
|
}
|
||
|
|
||
|
// String converts the issues to a suitable display string.
|
||
|
func (i *Issues) String() string {
|
||
|
if i == nil {
|
||
|
return ""
|
||
|
}
|
||
|
return i.errs.ToDisplayString()
|
||
|
}
|
||
|
|
||
|
// getStdEnv lazy initializes the CEL standard environment.
|
||
|
func getStdEnv() (*Env, error) {
|
||
|
stdEnvInit.Do(func() {
|
||
|
stdEnv, stdEnvErr = NewCustomEnv(StdLib(), EagerlyValidateDeclarations(true))
|
||
|
})
|
||
|
return stdEnv, stdEnvErr
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
stdEnvInit sync.Once
|
||
|
stdEnv *Env
|
||
|
stdEnvErr error
|
||
|
)
|