| // Copyright 2020 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" |
| "math" |
| |
| "github.com/google/cel-go/common" |
| "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" |
| "github.com/google/cel-go/common/types" |
| "github.com/google/cel-go/common/types/ref" |
| "github.com/google/cel-go/common/types/traits" |
| "github.com/google/cel-go/interpreter" |
| "github.com/google/cel-go/parser" |
| ) |
| |
| const ( |
| optMapMacro = "optMap" |
| optFlatMapMacro = "optFlatMap" |
| hasValueFunc = "hasValue" |
| unwrapOptFunc = "unwrapOpt" |
| optionalNoneFunc = "optional.none" |
| optionalOfFunc = "optional.of" |
| optionalOfNonZeroValueFunc = "optional.ofNonZeroValue" |
| optionalUnwrapFunc = "optional.unwrap" |
| valueFunc = "value" |
| unusedIterVar = "#unused" |
| ) |
| |
| // Library provides a collection of EnvOption and ProgramOption values used to configure a CEL |
| // environment for a particular use case or with a related set of functionality. |
| // |
| // Note, the ProgramOption values provided by a library are expected to be static and not vary |
| // between calls to Env.Program(). If there is a need for such dynamic configuration, prefer to |
| // configure these options outside the Library and within the Env.Program() call directly. |
| type Library interface { |
| // CompileOptions returns a collection of functional options for configuring the Parse / Check |
| // environment. |
| CompileOptions() []EnvOption |
| |
| // ProgramOptions returns a collection of functional options which should be included in every |
| // Program generated from the Env.Program() call. |
| ProgramOptions() []ProgramOption |
| } |
| |
| // SingletonLibrary refines the Library interface to ensure that libraries in this format are only |
| // configured once within the environment. |
| type SingletonLibrary interface { |
| Library |
| |
| // LibraryName provides a namespaced name which is used to check whether the library has already |
| // been configured in the environment. |
| LibraryName() string |
| } |
| |
| // LibraryAliaser generates a simple named alias for the library, for use during environment serialization. |
| type LibraryAliaser interface { |
| LibraryAlias() string |
| } |
| |
| // LibrarySubsetter provides the subset description associated with the library, nil if not subset. |
| type LibrarySubsetter interface { |
| LibrarySubset() *env.LibrarySubset |
| } |
| |
| // LibraryVersioner provides a version number for the library. |
| // |
| // If not implemented, the library version will be flagged as 'latest' during environment serialization. |
| type LibraryVersioner interface { |
| LibraryVersion() uint32 |
| } |
| |
| // Lib creates an EnvOption out of a Library, allowing libraries to be provided as functional args, |
| // and to be linked to each other. |
| func Lib(l Library) EnvOption { |
| singleton, isSingleton := l.(SingletonLibrary) |
| return func(e *Env) (*Env, error) { |
| if isSingleton { |
| if e.HasLibrary(singleton.LibraryName()) { |
| return e, nil |
| } |
| e.libraries[singleton.LibraryName()] = singleton |
| } |
| var err error |
| for _, opt := range l.CompileOptions() { |
| e, err = opt(e) |
| if err != nil { |
| return nil, err |
| } |
| } |
| e.progOpts = append(e.progOpts, l.ProgramOptions()...) |
| return e, nil |
| } |
| } |
| |
| // 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. |
| // |
| // Since the StdLib is a singleton library, only the first instance of the StdLib() environment options |
| // will be configured on the environment which means only the StdLibSubset() initially configured with |
| // the library will be used. |
| 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(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 { |
| subset *env.LibrarySubset |
| } |
| |
| // LibraryName implements the SingletonLibrary interface method. |
| func (*stdLibrary) LibraryName() string { |
| return "cel.lib.std" |
| } |
| |
| // LibraryAlias returns the simple name of the library. |
| func (*stdLibrary) LibraryAlias() string { |
| return "stdlib" |
| } |
| |
| // LibrarySubset returns the env.LibrarySubset definition associated with the CEL Library. |
| func (lib *stdLibrary) LibrarySubset() *env.LibrarySubset { |
| return lib.subset |
| } |
| |
| // CompileOptions returns options for the standard CEL function declarations and macros. |
| 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 |
| if err = lib.subset.Validate(); err != nil { |
| return nil, err |
| } |
| for _, fn := range funcs { |
| existing, found := e.functions[fn.Name()] |
| if found { |
| fn, err = existing.Merge(fn) |
| if err != nil { |
| return nil, err |
| } |
| } |
| e.functions[fn.Name()] = fn |
| } |
| return e, nil |
| }, |
| Macros(macros...), |
| } |
| } |
| |
| // ProgramOptions returns function implementations for the standard CEL functions. |
| func (*stdLibrary) ProgramOptions() []ProgramOption { |
| return []ProgramOption{} |
| } |
| |
| // OptionalTypes enable support for optional syntax and types in CEL. |
| // |
| // The optional value type makes it possible to express whether variables have |
| // been provided, whether a result has been computed, and in the future whether |
| // an object field path, map key value, or list index has a value. |
| // |
| // # Syntax Changes |
| // |
| // OptionalTypes are unlike other CEL extensions because they modify the CEL |
| // syntax itself, notably through the use of a `?` preceding a field name or |
| // index value. |
| // |
| // ## Field Selection |
| // |
| // The optional syntax in field selection is denoted as `obj.?field`. In other |
| // words, if a field is set, return `optional.of(obj.field)“, else |
| // `optional.none()`. The optional field selection is viral in the sense that |
| // after the first optional selection all subsequent selections or indices |
| // are treated as optional, i.e. the following expressions are equivalent: |
| // |
| // obj.?field.subfield |
| // obj.?field.?subfield |
| // |
| // ## Indexing |
| // |
| // Similar to field selection, the optional syntax can be used in index |
| // expressions on maps and lists: |
| // |
| // list[?0] |
| // map[?key] |
| // |
| // ## Optional Field Setting |
| // |
| // When creating map or message literals, if a field may be optionally set |
| // based on its presence, then placing a `?` before the field name or key |
| // will ensure the type on the right-hand side must be optional(T) where T |
| // is the type of the field or key-value. |
| // |
| // The following returns a map with the key expression set only if the |
| // subfield is present, otherwise an empty map is created: |
| // |
| // {?key: obj.?field.subfield} |
| // |
| // ## Optional Element Setting |
| // |
| // When creating list literals, an element in the list may be optionally added |
| // when the element expression is preceded by a `?`: |
| // |
| // [a, ?b, ?c] // return a list with either [a], [a, b], [a, b, c], or [a, c] |
| // |
| // # Optional.Of |
| // |
| // Create an optional(T) value of a given value with type T. |
| // |
| // optional.of(10) |
| // |
| // # Optional.OfNonZeroValue |
| // |
| // Create an optional(T) value of a given value with type T if it is not a |
| // zero-value. A zero-value the default empty value for any given CEL type, |
| // including empty protobuf message types. If the value is empty, the result |
| // of this call will be optional.none(). |
| // |
| // optional.ofNonZeroValue([1, 2, 3]) // optional(list(int)) |
| // optional.ofNonZeroValue([]) // optional.none() |
| // optional.ofNonZeroValue(0) // optional.none() |
| // optional.ofNonZeroValue("") // optional.none() |
| // |
| // # Optional.None |
| // |
| // Create an empty optional value. |
| // |
| // # HasValue |
| // |
| // Determine whether the optional contains a value. |
| // |
| // optional.of(b'hello').hasValue() // true |
| // optional.ofNonZeroValue({}).hasValue() // false |
| // |
| // # Value |
| // |
| // Get the value contained by the optional. If the optional does not have a |
| // value, the result will be a CEL error. |
| // |
| // optional.of(b'hello').value() // b'hello' |
| // optional.ofNonZeroValue({}).value() // error |
| // |
| // # Or |
| // |
| // If the value on the left-hand side is optional.none(), the optional value |
| // on the right hand side is returned. If the value on the left-hand set is |
| // valued, then it is returned. This operation is short-circuiting and will |
| // only evaluate as many links in the `or` chain as are needed to return a |
| // non-empty optional value. |
| // |
| // obj.?field.or(m[?key]) |
| // l[?index].or(obj.?field.subfield).or(obj.?other) |
| // |
| // # OrValue |
| // |
| // Either return the value contained within the optional on the left-hand side |
| // or return the alternative value on the right hand side. |
| // |
| // m[?key].orValue("none") |
| // |
| // # OptMap |
| // |
| // Apply a transformation to the optional's underlying value if it is not empty |
| // and return an optional typed result based on the transformation. The |
| // transformation expression type must return a type T which is wrapped into |
| // an optional. |
| // |
| // msg.?elements.optMap(e, e.size()).orValue(0) |
| // |
| // # OptFlatMap |
| // |
| // Introduced in version: 1 |
| // |
| // Apply a transformation to the optional's underlying value if it is not empty |
| // and return the result. The transform expression must return an optional(T) |
| // rather than type T. This can be useful when dealing with zero values and |
| // conditionally generating an empty or non-empty result in ways which cannot |
| // be expressed with `optMap`. |
| // |
| // msg.?elements.optFlatMap(e, e[?0]) // return the first element if present. |
| // |
| // # First |
| // |
| // Introduced in version: 2 |
| // |
| // Returns an optional with the first value from the right hand list, or |
| // optional.None. |
| // |
| // [1, 2, 3].first().value() == 1 |
| // |
| // # Last |
| // |
| // Introduced in version: 2 |
| // |
| // Returns an optional with the last value from the right hand list, or |
| // optional.None. |
| // |
| // [1, 2, 3].last().value() == 3 |
| // |
| // This is syntactic sugar for msg.elements[msg.elements.size()-1]. |
| // |
| // # Unwrap / UnwrapOpt |
| // |
| // Introduced in version: 2 |
| // |
| // Returns a list of all the values that are not none in the input list of optional values. |
| // Can be used as optional.unwrap(List[T]) or with postfix notation: List[T].unwrapOpt() |
| // |
| // optional.unwrap([optional.of(42), optional.none()]) == [42] |
| // [optional.of(42), optional.none()].unwrapOpt() == [42] |
| func OptionalTypes(opts ...OptionalTypesOption) EnvOption { |
| lib := &optionalLib{version: math.MaxUint32} |
| for _, opt := range opts { |
| lib = opt(lib) |
| } |
| return Lib(lib) |
| } |
| |
| type optionalLib struct { |
| version uint32 |
| } |
| |
| // OptionalTypesOption is a functional interface for configuring the strings library. |
| type OptionalTypesOption func(*optionalLib) *optionalLib |
| |
| // OptionalTypesVersion configures the version of the optional type library. |
| // |
| // The version limits which functions are available. Only functions introduced |
| // below or equal to the given version included in the library. If this option |
| // is not set, all functions are available. |
| // |
| // See the library documentation to determine which version a function was introduced. |
| // If the documentation does not state which version a function was introduced, it can |
| // be assumed to be introduced at version 0, when the library was first created. |
| func OptionalTypesVersion(version uint32) OptionalTypesOption { |
| return func(lib *optionalLib) *optionalLib { |
| lib.version = version |
| return lib |
| } |
| } |
| |
| // LibraryName implements the SingletonLibrary interface method. |
| func (*optionalLib) LibraryName() string { |
| return "cel.lib.optional" |
| } |
| |
| // LibraryAlias returns the simple name of the library. |
| func (*optionalLib) LibraryAlias() string { |
| return "optional" |
| } |
| |
| // LibraryVersion returns the version of the library. |
| func (lib *optionalLib) LibraryVersion() uint32 { |
| return lib.version |
| } |
| |
| // CompileOptions implements the Library interface method. |
| func (lib *optionalLib) CompileOptions() []EnvOption { |
| paramTypeK := TypeParamType("K") |
| paramTypeV := TypeParamType("V") |
| optionalTypeV := OptionalType(paramTypeV) |
| listTypeV := ListType(paramTypeV) |
| mapTypeKV := MapType(paramTypeK, paramTypeV) |
| listOptionalTypeV := ListType(optionalTypeV) |
| |
| opts := []EnvOption{ |
| // Enable the optional syntax in the parser. |
| enableOptionalSyntax(), |
| |
| // Introduce the optional type. |
| Types(types.OptionalType), |
| |
| // Configure the optMap and optFlatMap macros. |
| Macros(ReceiverMacro(optMapMacro, 2, optMap, |
| MacroDocs(`perform computation on the value if present and return the result as an optional`), |
| MacroExamples( |
| common.MultilineDescription( |
| `// sub with the prefix 'dev.cel' or optional.none()`, |
| `request.auth.tokens.?sub.optMap(id, 'dev.cel.' + id)`), |
| `optional.none().optMap(i, i * 2) // optional.none()`))), |
| |
| // Global and member functions for working with optional values. |
| Function(optionalOfFunc, |
| FunctionDocs(`create a new optional_type(T) with a value where any value is considered valid`), |
| Overload("optional_of", []*Type{paramTypeV}, optionalTypeV, |
| OverloadExamples(`optional.of(1) // optional(1)`), |
| UnaryBinding(func(value ref.Val) ref.Val { |
| return types.OptionalOf(value) |
| }))), |
| Function(optionalOfNonZeroValueFunc, |
| FunctionDocs(`create a new optional_type(T) with a value, if the value is not a zero or empty value`), |
| Overload("optional_ofNonZeroValue", []*Type{paramTypeV}, optionalTypeV, |
| OverloadExamples( |
| `optional.ofNonZeroValue(null) // optional.none()`, |
| `optional.ofNonZeroValue("") // optional.none()`, |
| `optional.ofNonZeroValue("hello") // optional.of('hello')`), |
| UnaryBinding(func(value ref.Val) ref.Val { |
| v, isZeroer := value.(traits.Zeroer) |
| if !isZeroer || !v.IsZeroValue() { |
| return types.OptionalOf(value) |
| } |
| return types.OptionalNone |
| }))), |
| Function(optionalNoneFunc, |
| FunctionDocs(`singleton value representing an optional without a value`), |
| Overload("optional_none", []*Type{}, optionalTypeV, |
| OverloadExamples(`optional.none()`), |
| FunctionBinding(func(values ...ref.Val) ref.Val { |
| return types.OptionalNone |
| }))), |
| Function(valueFunc, |
| FunctionDocs(`obtain the value contained by the optional, error if optional.none()`), |
| MemberOverload("optional_value", []*Type{optionalTypeV}, paramTypeV, |
| OverloadExamples( |
| `optional.of(1).value() // 1`, |
| `optional.none().value() // error`), |
| UnaryBinding(func(value ref.Val) ref.Val { |
| opt := value.(*types.Optional) |
| return opt.GetValue() |
| }))), |
| Function(hasValueFunc, |
| FunctionDocs(`determine whether the optional contains a value`), |
| MemberOverload("optional_hasValue", []*Type{optionalTypeV}, BoolType, |
| OverloadExamples(`optional.of({1: 2}).hasValue() // true`), |
| UnaryBinding(func(value ref.Val) ref.Val { |
| opt := value.(*types.Optional) |
| return types.Bool(opt.HasValue()) |
| }))), |
| |
| // Implementation of 'or' and 'orValue' are special-cased to support short-circuiting in the |
| // evaluation chain. |
| Function("or", |
| FunctionDocs(`chain optional expressions together, picking the first valued optional expression`), |
| MemberOverload("optional_or_optional", []*Type{optionalTypeV, optionalTypeV}, optionalTypeV, |
| OverloadExamples( |
| `optional.none().or(optional.of(1)) // optional.of(1)`, |
| common.MultilineDescription( |
| `// either a value from the first list, a value from the second, or optional.none()`, |
| `[1, 2, 3][?x].or([3, 4, 5][?y])`)))), |
| Function("orValue", |
| FunctionDocs(`chain optional expressions together picking the first valued optional or the default value`), |
| MemberOverload("optional_orValue_value", []*Type{optionalTypeV, paramTypeV}, paramTypeV, |
| OverloadExamples( |
| common.MultilineDescription( |
| `// pick the value for the given key if the key exists, otherwise return 'you'`, |
| `{'hello': 'world', 'goodbye': 'cruel world'}[?greeting].orValue('you')`)))), |
| |
| // OptSelect is handled specially by the type-checker, so the receiver's field type is used to determine the |
| // optput type. |
| Function(operators.OptSelect, |
| FunctionDocs(`if the field is present create an optional of the field value, otherwise return optional.none()`), |
| Overload("select_optional_field", []*Type{DynType, StringType}, optionalTypeV, |
| OverloadExamples( |
| `msg.?field // optional.of(field) if non-empty, otherwise optional.none()`, |
| `msg.?field.?nested_field // optional.of(nested_field) if both field and nested_field are non-empty.`))), |
| |
| // OptIndex is handled mostly like any other indexing operation on a list or map, so the type-checker can use |
| // these signatures to determine type-agreement without any special handling. |
| Function(operators.OptIndex, |
| FunctionDocs(`if the index is present create an optional of the field value, otherwise return optional.none()`), |
| Overload("list_optindex_optional_int", []*Type{listTypeV, IntType}, optionalTypeV, |
| OverloadExamples(`[1, 2, 3][?x] // element value if x is in the list size, else optional.none()`)), |
| Overload("optional_list_optindex_optional_int", []*Type{OptionalType(listTypeV), IntType}, optionalTypeV), |
| Overload("map_optindex_optional_value", []*Type{mapTypeKV, paramTypeK}, optionalTypeV, |
| OverloadExamples( |
| `map_value[?key] // value at the key if present, else optional.none()`, |
| common.MultilineDescription( |
| `// map key-value if index is a valid map key, else optional.none()`, |
| `{0: 2, 2: 4, 6: 8}[?index]`))), |
| Overload("optional_map_optindex_optional_value", []*Type{OptionalType(mapTypeKV), paramTypeK}, optionalTypeV)), |
| |
| // Index overloads to accommodate using an optional value as the operand. |
| Function(operators.Index, |
| Overload("optional_list_index_int", []*Type{OptionalType(listTypeV), IntType}, optionalTypeV), |
| Overload("optional_map_index_value", []*Type{OptionalType(mapTypeKV), paramTypeK}, optionalTypeV)), |
| } |
| if lib.version >= 1 { |
| opts = append(opts, Macros(ReceiverMacro(optFlatMapMacro, 2, optFlatMap, |
| MacroDocs(`perform computation on the value if present and produce an optional value within the computation`), |
| MacroExamples( |
| common.MultilineDescription( |
| `// m = {'key': {}}`, |
| `m.?key.optFlatMap(k, k.?subkey) // optional.none()`), |
| common.MultilineDescription( |
| `// m = {'key': {'subkey': 'value'}}`, |
| `m.?key.optFlatMap(k, k.?subkey) // optional.of('value')`), |
| )))) |
| } |
| |
| if lib.version >= 2 { |
| opts = append(opts, Function("last", |
| FunctionDocs(`return the last value in a list if present, otherwise optional.none()`), |
| MemberOverload("list_last", []*Type{listTypeV}, optionalTypeV, |
| OverloadExamples( |
| `[].last() // optional.none()`, |
| `[1, 2, 3].last() ? optional.of(3)`), |
| UnaryBinding(func(v ref.Val) ref.Val { |
| list := v.(traits.Lister) |
| sz := list.Size().(types.Int) |
| if sz == types.IntZero { |
| return types.OptionalNone |
| } |
| return types.OptionalOf(list.Get(types.Int(sz - 1))) |
| }), |
| ), |
| )) |
| |
| opts = append(opts, Function("first", |
| FunctionDocs(`return the first value in a list if present, otherwise optional.none()`), |
| MemberOverload("list_first", []*Type{listTypeV}, optionalTypeV, |
| OverloadExamples( |
| `[].first() // optional.none()`, |
| `[1, 2, 3].first() ? optional.of(1)`), |
| UnaryBinding(func(v ref.Val) ref.Val { |
| list := v.(traits.Lister) |
| sz := list.Size().(types.Int) |
| if sz == types.IntZero { |
| return types.OptionalNone |
| } |
| return types.OptionalOf(list.Get(types.Int(0))) |
| }), |
| ), |
| )) |
| |
| opts = append(opts, Function(optionalUnwrapFunc, |
| FunctionDocs(`convert a list of optional values to a list containing only value which are not optional.none()`), |
| Overload("optional_unwrap", []*Type{listOptionalTypeV}, listTypeV, |
| OverloadExamples(`optional.unwrap([optional.of(1), optional.none()]) // [1]`), |
| UnaryBinding(optUnwrap)))) |
| opts = append(opts, Function(unwrapOptFunc, |
| FunctionDocs(`convert a list of optional values to a list containing only value which are not optional.none()`), |
| MemberOverload("optional_unwrapOpt", []*Type{listOptionalTypeV}, listTypeV, |
| OverloadExamples(`[optional.of(1), optional.none()].unwrapOpt() // [1]`), |
| UnaryBinding(optUnwrap)))) |
| } |
| |
| return opts |
| } |
| |
| // ProgramOptions implements the Library interface method. |
| func (lib *optionalLib) ProgramOptions() []ProgramOption { |
| return []ProgramOption{ |
| CustomDecorator(decorateOptionalOr), |
| } |
| } |
| |
| // Version returns the current version of the library. |
| func (lib *optionalLib) Version() uint32 { |
| return lib.version |
| } |
| |
| func optMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *Error) { |
| varIdent := args[0] |
| varName := "" |
| switch varIdent.Kind() { |
| case ast.IdentKind: |
| varName = varIdent.AsIdent() |
| default: |
| return nil, meh.NewError(varIdent.ID(), "optMap() variable name must be a simple identifier") |
| } |
| mapExpr := args[1] |
| return meh.NewCall( |
| operators.Conditional, |
| meh.NewMemberCall(hasValueFunc, target), |
| meh.NewCall(optionalOfFunc, |
| meh.NewComprehension( |
| meh.NewList(), |
| unusedIterVar, |
| varName, |
| meh.NewMemberCall(valueFunc, meh.Copy(target)), |
| meh.NewLiteral(types.False), |
| meh.NewIdent(varName), |
| mapExpr, |
| ), |
| ), |
| meh.NewCall(optionalNoneFunc), |
| ), nil |
| } |
| |
| func optFlatMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *Error) { |
| varIdent := args[0] |
| varName := "" |
| switch varIdent.Kind() { |
| case ast.IdentKind: |
| varName = varIdent.AsIdent() |
| default: |
| return nil, meh.NewError(varIdent.ID(), "optFlatMap() variable name must be a simple identifier") |
| } |
| mapExpr := args[1] |
| return meh.NewCall( |
| operators.Conditional, |
| meh.NewMemberCall(hasValueFunc, target), |
| meh.NewComprehension( |
| meh.NewList(), |
| unusedIterVar, |
| varName, |
| meh.NewMemberCall(valueFunc, meh.Copy(target)), |
| meh.NewLiteral(types.False), |
| meh.NewIdent(varName), |
| mapExpr, |
| ), |
| meh.NewCall(optionalNoneFunc), |
| ), nil |
| } |
| |
| func optUnwrap(value ref.Val) ref.Val { |
| list := value.(traits.Lister) |
| var unwrappedList []ref.Val |
| iter := list.Iterator() |
| for iter.HasNext() == types.True { |
| val := iter.Next() |
| opt, isOpt := val.(*types.Optional) |
| if !isOpt { |
| return types.WrapErr(fmt.Errorf("value %v is not optional", val)) |
| } |
| if opt.HasValue() { |
| unwrappedList = append(unwrappedList, opt.GetValue()) |
| } |
| } |
| return types.DefaultTypeAdapter.NativeToValue(unwrappedList) |
| } |
| |
| func enableOptionalSyntax() EnvOption { |
| return func(e *Env) (*Env, error) { |
| e.prsrOpts = append(e.prsrOpts, parser.EnableOptionalSyntax(true)) |
| return e, nil |
| } |
| } |
| |
| // EnableErrorOnBadPresenceTest enables error generation when a presence test or optional field |
| // selection is performed on a primitive type. |
| func EnableErrorOnBadPresenceTest(value bool) EnvOption { |
| return features(featureEnableErrorOnBadPresenceTest, value) |
| } |
| |
| func decorateOptionalOr(i interpreter.Interpretable) (interpreter.Interpretable, error) { |
| call, ok := i.(interpreter.InterpretableCall) |
| if !ok { |
| return i, nil |
| } |
| args := call.Args() |
| if len(args) != 2 { |
| return i, nil |
| } |
| switch call.Function() { |
| case "or": |
| if call.OverloadID() != "" && call.OverloadID() != "optional_or_optional" { |
| return i, nil |
| } |
| return &evalOptionalOr{ |
| id: call.ID(), |
| lhs: args[0], |
| rhs: args[1], |
| }, nil |
| case "orValue": |
| if call.OverloadID() != "" && call.OverloadID() != "optional_orValue_value" { |
| return i, nil |
| } |
| return &evalOptionalOrValue{ |
| id: call.ID(), |
| lhs: args[0], |
| rhs: args[1], |
| }, nil |
| default: |
| return i, nil |
| } |
| } |
| |
| // evalOptionalOr selects between two optional values, either the first if it has a value, or |
| // the second optional expression is evaluated and returned. |
| type evalOptionalOr struct { |
| id int64 |
| lhs interpreter.Interpretable |
| rhs interpreter.Interpretable |
| } |
| |
| // ID implements the Interpretable interface method. |
| func (opt *evalOptionalOr) ID() int64 { |
| return opt.id |
| } |
| |
| // Eval evaluates the left-hand side optional to determine whether it contains a value, else |
| // proceeds with the right-hand side evaluation. |
| func (opt *evalOptionalOr) Eval(ctx interpreter.Activation) ref.Val { |
| // short-circuit lhs. |
| optLHS := opt.lhs.Eval(ctx) |
| switch val := optLHS.(type) { |
| case *types.Err, *types.Unknown: |
| return optLHS |
| case *types.Optional: |
| if val.HasValue() { |
| return optLHS |
| } |
| return opt.rhs.Eval(ctx) |
| default: |
| return types.NoSuchOverloadErr() |
| } |
| } |
| |
| // evalOptionalOrValue selects between an optional or a concrete value. If the optional has a value, |
| // its value is returned, otherwise the alternative value expression is evaluated and returned. |
| type evalOptionalOrValue struct { |
| id int64 |
| lhs interpreter.Interpretable |
| rhs interpreter.Interpretable |
| } |
| |
| // ID implements the Interpretable interface method. |
| func (opt *evalOptionalOrValue) ID() int64 { |
| return opt.id |
| } |
| |
| // Eval evaluates the left-hand side optional to determine whether it contains a value, else |
| // proceeds with the right-hand side evaluation. |
| func (opt *evalOptionalOrValue) Eval(ctx interpreter.Activation) ref.Val { |
| // short-circuit lhs. |
| optLHS := opt.lhs.Eval(ctx) |
| |
| switch val := optLHS.(type) { |
| case *types.Err, *types.Unknown: |
| return optLHS |
| case *types.Optional: |
| if val.HasValue() { |
| return val.GetValue() |
| } |
| return opt.rhs.Eval(ctx) |
| default: |
| return types.NoSuchOverloadErr() |
| } |
| } |
| |
| type timeLegacyLibrary struct{} |
| |
| func (timeLegacyLibrary) CompileOptions() []EnvOption { |
| return timeOverloadDeclarations |
| } |
| |
| func (timeLegacyLibrary) ProgramOptions() []ProgramOption { |
| return []ProgramOption{} |
| } |
| |
| // Declarations and functions which enable using UTC on time.Time inputs when the timezone is unspecified |
| // in the CEL expression. |
| var ( |
| timeOverloadDeclarations = []EnvOption{ |
| Function(overloads.TimeGetFullYear, |
| MemberOverload(overloads.TimestampToYear, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetFullYear, overloads.TimestampToYear, []ref.Val{}) |
| }), |
| ), |
| ), |
| Function(overloads.TimeGetMonth, |
| MemberOverload(overloads.TimestampToMonth, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetMonth, overloads.TimestampToMonth, []ref.Val{}) |
| }), |
| ), |
| ), |
| Function(overloads.TimeGetDayOfYear, |
| MemberOverload(overloads.TimestampToDayOfYear, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetDayOfYear, overloads.TimestampToDayOfYear, []ref.Val{}) |
| }), |
| ), |
| ), |
| Function(overloads.TimeGetDayOfMonth, |
| MemberOverload(overloads.TimestampToDayOfMonthZeroBased, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetDayOfMonth, overloads.TimestampToDayOfMonthZeroBased, []ref.Val{}) |
| }), |
| ), |
| ), |
| Function(overloads.TimeGetDate, |
| MemberOverload(overloads.TimestampToDayOfMonthOneBased, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetDate, overloads.TimestampToDayOfMonthOneBased, []ref.Val{}) |
| }), |
| ), |
| ), |
| Function(overloads.TimeGetDayOfWeek, |
| MemberOverload(overloads.TimestampToDayOfWeek, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetDayOfWeek, overloads.TimestampToDayOfWeek, []ref.Val{}) |
| }), |
| ), |
| ), |
| Function(overloads.TimeGetHours, |
| MemberOverload(overloads.TimestampToHours, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetHours, overloads.TimestampToHours, []ref.Val{}) |
| }), |
| ), |
| ), |
| Function(overloads.TimeGetMinutes, |
| MemberOverload(overloads.TimestampToMinutes, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetMinutes, overloads.TimestampToMinutes, []ref.Val{}) |
| }), |
| ), |
| ), |
| Function(overloads.TimeGetSeconds, |
| MemberOverload(overloads.TimestampToSeconds, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetSeconds, overloads.TimestampToSeconds, []ref.Val{}) |
| }), |
| ), |
| ), |
| Function(overloads.TimeGetMilliseconds, |
| MemberOverload(overloads.TimestampToMilliseconds, []*Type{TimestampType}, IntType, |
| UnaryBinding(func(ts ref.Val) ref.Val { |
| t := ts.(types.Timestamp) |
| return t.Receive(overloads.TimeGetMilliseconds, overloads.TimestampToMilliseconds, []ref.Val{}) |
| }), |
| ), |
| ), |
| } |
| ) |