build: move e2e dependencies into e2e/go.mod

Several packages are only used while running the e2e suite. These
packages are less important to update, as the they can not influence the
final executable that is part of the Ceph-CSI container-image.

By moving these dependencies out of the main Ceph-CSI go.mod, it is
easier to identify if a reported CVE affects Ceph-CSI, or only the
testing (like most of the Kubernetes CVEs).

Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
Niels de Vos
2025-03-04 08:57:28 +01:00
committed by mergify[bot]
parent 15da101b1b
commit bec6090996
8047 changed files with 1407827 additions and 3453 deletions

72
e2e/vendor/github.com/google/cel-go/ext/BUILD.bazel generated vendored Normal file
View File

@ -0,0 +1,72 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
package(
licenses = ["notice"], # Apache 2.0
)
go_library(
name = "go_default_library",
srcs = [
"bindings.go",
"encoders.go",
"formatting.go",
"guards.go",
"lists.go",
"math.go",
"native.go",
"protos.go",
"sets.go",
"strings.go",
],
importpath = "github.com/google/cel-go/ext",
visibility = ["//visibility:public"],
deps = [
"//cel:go_default_library",
"//checker:go_default_library",
"//common/ast:go_default_library",
"//common/decls:go_default_library",
"//common/overloads:go_default_library",
"//common/operators:go_default_library",
"//common/types:go_default_library",
"//common/types/pb:go_default_library",
"//common/types/ref:go_default_library",
"//common/types/traits:go_default_library",
"//interpreter:go_default_library",
"//parser:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
"@org_golang_google_protobuf//reflect/protoreflect:go_default_library",
"@org_golang_google_protobuf//types/known/structpb",
"@org_golang_x_text//language:go_default_library",
"@org_golang_x_text//message:go_default_library",
],
)
go_test(
name = "go_default_test",
size = "small",
srcs = [
"encoders_test.go",
"lists_test.go",
"math_test.go",
"native_test.go",
"protos_test.go",
"sets_test.go",
"strings_test.go",
],
embed = [
":go_default_library",
],
deps = [
"//cel:go_default_library",
"//checker:go_default_library",
"//common/types:go_default_library",
"//common/types/ref:go_default_library",
"//common/types/traits:go_default_library",
"//test:go_default_library",
"//test/proto2pb:go_default_library",
"//test/proto3pb:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
"@org_golang_google_protobuf//types/known/wrapperspb:go_default_library",
"@org_golang_google_protobuf//encoding/protojson:go_default_library",
],
)

899
e2e/vendor/github.com/google/cel-go/ext/README.md generated vendored Normal file
View File

@ -0,0 +1,899 @@
# Extensions
CEL extensions are a related set of constants, functions, macros, or other
features which may not be covered by the core CEL spec.
## Bindings
Returns a cel.EnvOption to configure support for local variable bindings
in expressions.
### Cel.Bind
Binds a simple identifier to an initialization expression which may be used
in a subsequenct result expression. Bindings may also be nested within each
other.
cel.bind(<varName>, <initExpr>, <resultExpr>)
Examples:
cel.bind(a, 'hello',
cel.bind(b, 'world', a + b + b + a)) // "helloworldworldhello"
// Avoid a list allocation within the exists comprehension.
cel.bind(valid_values, [a, b, c],
[d, e, f].exists(elem, elem in valid_values))
Local bindings are not guaranteed to be evaluated before use.
## Encoders
Encoding utilies for marshalling data into standardized representations.
### Base64.Decode
Decodes base64-encoded string to bytes.
This function will return an error if the string input is not
base64-encoded.
base64.decode(<string>) -> <bytes>
Examples:
base64.decode('aGVsbG8=') // return b'hello'
base64.decode('aGVsbG8') // error
### Base64.Encode
Encodes bytes to a base64-encoded string.
base64.encode(<bytes>) -> <string>
Example:
base64.encode(b'hello') // return 'aGVsbG8='
## Math
Math helper macros and functions.
Note, all macros use the 'math' namespace; however, at the time of macro
expansion the namespace looks just like any other identifier. If you are
currently using a variable named 'math', the macro will likely work just as
intended; however, there is some chance for collision.
### Math.Greatest
Returns the greatest valued number present in the arguments to the macro.
Greatest is a variable argument count macro which must take at least one
argument. Simple numeric and list literals are supported as valid argument
types; however, other literals will be flagged as errors during macro
expansion. If the argument expression does not resolve to a numeric or
list(numeric) type during type-checking, or during runtime then an error
will be produced. If a list argument is empty, this too will produce an
error.
math.greatest(<arg>, ...) -> <double|int|uint>
Examples:
math.greatest(1) // 1
math.greatest(1u, 2u) // 2u
math.greatest(-42.0, -21.5, -100.0) // -21.5
math.greatest([-42.0, -21.5, -100.0]) // -21.5
math.greatest(numbers) // numbers must be list(numeric)
math.greatest() // parse error
math.greatest('string') // parse error
math.greatest(a, b) // check-time error if a or b is non-numeric
math.greatest(dyn('string')) // runtime error
### Math.Least
Returns the least valued number present in the arguments to the macro.
Least is a variable argument count macro which must take at least one
argument. Simple numeric and list literals are supported as valid argument
types; however, other literals will be flagged as errors during macro
expansion. If the argument expression does not resolve to a numeric or
list(numeric) type during type-checking, or during runtime then an error
will be produced. If a list argument is empty, this too will produce an
error.
math.least(<arg>, ...) -> <double|int|uint>
Examples:
math.least(1) // 1
math.least(1u, 2u) // 1u
math.least(-42.0, -21.5, -100.0) // -100.0
math.least([-42.0, -21.5, -100.0]) // -100.0
math.least(numbers) // numbers must be list(numeric)
math.least() // parse error
math.least('string') // parse error
math.least(a, b) // check-time error if a or b is non-numeric
math.least(dyn('string')) // runtime error
### Math.BitOr
Introduced at version: 1
Performs a bitwise-OR operation over two int or uint values.
math.bitOr(<int>, <int>) -> <int>
math.bitOr(<uint>, <uint>) -> <uint>
Examples:
math.bitOr(1u, 2u) // returns 3u
math.bitOr(-2, -4) // returns -2
### Math.BitAnd
Introduced at version: 1
Performs a bitwise-AND operation over two int or uint values.
math.bitAnd(<int>, <int>) -> <int>
math.bitAnd(<uint>, <uint>) -> <uint>
Examples:
math.bitAnd(3u, 2u) // return 2u
math.bitAnd(3, 5) // returns 3
math.bitAnd(-3, -5) // returns -7
### Math.BitXor
Introduced at version: 1
math.bitXor(<int>, <int>) -> <int>
math.bitXor(<uint>, <uint>) -> <uint>
Performs a bitwise-XOR operation over two int or uint values.
Examples:
math.bitXor(3u, 5u) // returns 6u
math.bitXor(1, 3) // returns 2
### Math.BitNot
Introduced at version: 1
Function which accepts a single int or uint and performs a bitwise-NOT
ones-complement of the given binary value.
math.bitNot(<int>) -> <int>
math.bitNot(<uint>) -> <uint>
Examples
math.bitNot(1) // returns -1
math.bitNot(-1) // return 0
math.bitNot(0u) // returns 18446744073709551615u
### Math.BitShiftLeft
Introduced at version: 1
Perform a left shift of bits on the first parameter, by the amount of bits
specified in the second parameter. The first parameter is either a uint or
an int. The second parameter must be an int.
When the second parameter is 64 or greater, 0 will be always be returned
since the number of bits shifted is greater than or equal to the total bit
length of the number being shifted. Negative valued bit shifts will result
in a runtime error.
math.bitShiftLeft(<int>, <int>) -> <int>
math.bitShiftLeft(<uint>, <int>) -> <uint>
Examples
math.bitShiftLeft(1, 2) // returns 4
math.bitShiftLeft(-1, 2) // returns -4
math.bitShiftLeft(1u, 2) // return 4u
math.bitShiftLeft(1u, 200) // returns 0u
### Math.BitShiftRight
Introduced at version: 1
Perform a right shift of bits on the first parameter, by the amount of bits
specified in the second parameter. The first parameter is either a uint or
an int. The second parameter must be an int.
When the second parameter is 64 or greater, 0 will always be returned since
the number of bits shifted is greater than or equal to the total bit length
of the number being shifted. Negative valued bit shifts will result in a
runtime error.
The sign bit extension will not be preserved for this operation: vacant bits
on the left are filled with 0.
math.bitShiftRight(<int>, <int>) -> <int>
math.bitShiftRight(<uint>, <int>) -> <uint>
Examples
math.bitShiftRight(1024, 2) // returns 256
math.bitShiftRight(1024u, 2) // returns 256u
math.bitShiftRight(1024u, 64) // returns 0u
### Math.Ceil
Introduced at version: 1
Compute the ceiling of a double value.
math.ceil(<double>) -> <double>
Examples:
math.ceil(1.2) // returns 2.0
math.ceil(-1.2) // returns -1.0
### Math.Floor
Introduced at version: 1
Compute the floor of a double value.
math.floor(<double>) -> <double>
Examples:
math.floor(1.2) // returns 1.0
math.floor(-1.2) // returns -2.0
### Math.Round
Introduced at version: 1
Rounds the double value to the nearest whole number with ties rounding away
from zero, e.g. 1.5 -> 2.0, -1.5 -> -2.0.
math.round(<double>) -> <double>
Examples:
math.round(1.2) // returns 1.0
math.round(1.5) // returns 2.0
math.round(-1.5) // returns -2.0
### Math.Trunc
Introduced at version: 1
Truncates the fractional portion of the double value.
math.trunc(<double>) -> <double>
Examples:
math.trunc(-1.3) // returns -1.0
math.trunc(1.3) // returns 1.0
### Math.Abs
Introduced at version: 1
Returns the absolute value of the numeric type provided as input. If the
value is NaN, the output is NaN. If the input is int64 min, the function
will result in an overflow error.
math.abs(<double>) -> <double>
math.abs(<int>) -> <int>
math.abs(<uint>) -> <uint>
Examples:
math.abs(-1) // returns 1
math.abs(1) // returns 1
math.abs(-9223372036854775808) // overlflow error
### Math.Sign
Introduced at version: 1
Returns the sign of the numeric type, either -1, 0, 1 as an int, double, or
uint depending on the overload. For floating point values, if NaN is
provided as input, the output is also NaN. The implementation does not
differentiate between positive and negative zero.
math.sign(<double>) -> <double>
math.sign(<int>) -> <int>
math.sign(<uint>) -> <uint>
Examples:
math.sign(-42) // returns -1
math.sign(0) // returns 0
math.sign(42) // returns 1
### Math.IsInf
Introduced at version: 1
Returns true if the input double value is -Inf or +Inf.
math.isInf(<double>) -> <bool>
Examples:
math.isInf(1.0/0.0) // returns true
math.isInf(1.2) // returns false
### Math.IsNaN
Introduced at version: 1
Returns true if the input double value is NaN, false otherwise.
math.isNaN(<double>) -> <bool>
Examples:
math.isNaN(0.0/0.0) // returns true
math.isNaN(1.2) // returns false
### Math.IsFinite
Introduced at version: 1
Returns true if the value is a finite number. Equivalent in behavior to:
!math.isNaN(double) && !math.isInf(double)
math.isFinite(<double>) -> <bool>
Examples:
math.isFinite(0.0/0.0) // returns false
math.isFinite(1.2) // returns true
## Protos
Protos configure extended macros and functions for proto manipulation.
Note, all macros use the 'proto' namespace; however, at the time of macro
expansion the namespace looks just like any other identifier. If you are
currently using a variable named 'proto', the macro will likely work just as
you intend; however, there is some chance for collision.
### Protos.GetExt
Macro which generates a select expression that retrieves an extension field
from the input proto2 syntax message. If the field is not set, the default
value forthe extension field is returned according to safe-traversal semantics.
proto.getExt(<msg>, <fully.qualified.extension.name>) -> <field-type>
Example:
proto.getExt(msg, google.expr.proto2.test.int32_ext) // returns int value
### Protos.HasExt
Macro which generates a test-only select expression that determines whether
an extension field is set on a proto2 syntax message.
proto.hasExt(<msg>, <fully.qualified.extension.name>) -> <bool>
Example:
proto.hasExt(msg, google.expr.proto2.test.int32_ext) // returns true || false
## Lists
Extended functions for list manipulation. As a general note, all indices are
zero-based.
### Distinct
**Introduced in version 2**
Returns the distinct elements of a list.
<list(T)>.distinct() -> <list(T)>
Examples:
[1, 2, 2, 3, 3, 3].distinct() // return [1, 2, 3]
["b", "b", "c", "a", "c"].distinct() // return ["b", "c", "a"]
[1, "b", 2, "b"].distinct() // return [1, "b", 2]
### Flatten
**Introduced in version 1**
Flattens a list recursively.
If an optional depth is provided, the list is flattened to a the specificied level.
A negative depth value will result in an error.
<list>.flatten(<list>) -> <list>
<list>.flatten(<list>, <int>) -> <list>
Examples:
[1,[2,3],[4]].flatten() // return [1, 2, 3, 4]
[1,[2,[3,4]]].flatten() // return [1, 2, [3, 4]]
[1,2,[],[],[3,4]].flatten() // return [1, 2, 3, 4]
[1,[2,[3,[4]]]].flatten(2) // return [1, 2, 3, [4]]
[1,[2,[3,[4]]]].flatten(-1) // error
### Range
**Introduced in version 2**
Returns a list of integers from 0 to n-1.
lists.range(<int>) -> <list(int)>
Examples:
lists.range(5) -> [0, 1, 2, 3, 4]
### Reverse
**Introduced in version 2**
Returns the elements of a list in reverse order.
<list(T)>.reverse() -> <list(T)>
Examples:
[5, 3, 1, 2].reverse() // return [2, 1, 3, 5]
### Slice
Returns a new sub-list using the indexes provided.
<list>.slice(<int>, <int>) -> <list>
Examples:
[1,2,3,4].slice(1, 3) // return [2, 3]
[1,2,3,4].slice(2, 4) // return [3, 4]
### Sort
**Introduced in version 2**
Sorts a list with comparable elements. If the element type is not comparable
or the element types are not the same, the function will produce an error.
<list(T)>.sort() -> <list(T)>
T in {int, uint, double, bool, duration, timestamp, string, bytes}
Examples:
[3, 2, 1].sort() // return [1, 2, 3]
["b", "c", "a"].sort() // return ["a", "b", "c"]
[1, "b"].sort() // error
[[1, 2, 3]].sort() // error
### SortBy
**Introduced in version 2**
Sorts a list by a key value, i.e., the order is determined by the result of
an expression applied to each element of the list.
<list(T)>.sortBy(<bindingName>, <keyExpr>) -> <list(T)>
keyExpr returns a value in {int, uint, double, bool, duration, timestamp, string, bytes}
Examples:
[
Player { name: "foo", score: 0 },
Player { name: "bar", score: -10 },
Player { name: "baz", score: 1000 },
].sortBy(e, e.score).map(e, e.name)
== ["bar", "foo", "baz"]
## Sets
Sets provides set relationship tests.
There is no set type within CEL, and while one may be introduced in the
future, there are cases where a `list` type is known to behave like a set.
For such cases, this library provides some basic functionality for
determining set containment, equivalence, and intersection.
### Sets.Contains
Returns whether the first list argument contains all elements in the second
list argument. The list may contain elements of any type and standard CEL
equality is used to determine whether a value exists in both lists. If the
second list is empty, the result will always return true.
sets.contains(list(T), list(T)) -> bool
Examples:
sets.contains([], []) // true
sets.contains([], [1]) // false
sets.contains([1, 2, 3, 4], [2, 3]) // true
sets.contains([1, 2.0, 3u], [1.0, 2u, 3]) // true
### Sets.Equivalent
Returns whether the first and second list are set equivalent. Lists are set
equivalent if for every item in the first list, there is an element in the
second which is equal. The lists may not be of the same size as they do not
guarantee the elements within them are unique, so size does not factor into
the computation.
sets.equivalent(list(T), list(T)) -> bool
Examples:
sets.equivalent([], []) // true
sets.equivalent([1], [1, 1]) // true
sets.equivalent([1], [1u, 1.0]) // true
sets.equivalent([1, 2, 3], [3u, 2.0, 1]) // true
### Sets.Intersects
Returns whether the first list has at least one element whose value is equal
to an element in the second list. If either list is empty, the result will
be false.
sets.intersects(list(T), list(T)) -> bool
Examples:
sets.intersects([1], []) // false
sets.intersects([1], [1, 2]) // true
sets.intersects([[1], [2, 3]], [[1, 2], [2, 3.0]]) // true
## Strings
Extended functions for string manipulation. As a general note, all indices are
zero-based.
### CharAt
Returns the character at the given position. If the position is negative, or
greater than the length of the string, the function will produce an error:
<string>.charAt(<int>) -> <string>
Examples:
'hello'.charAt(4) // return 'o'
'hello'.charAt(5) // return ''
'hello'.charAt(-1) // error
### IndexOf
Returns the integer index of the first occurrence of the search string. If the
search string is not found the function returns -1.
The function also accepts an optional position from which to begin the
substring search. If the substring is the empty string, the index where the
search starts is returned (zero or custom).
<string>.indexOf(<string>) -> <int>
<string>.indexOf(<string>, <int>) -> <int>
Examples:
'hello mellow'.indexOf('') // returns 0
'hello mellow'.indexOf('ello') // returns 1
'hello mellow'.indexOf('jello') // returns -1
'hello mellow'.indexOf('', 2) // returns 2
'hello mellow'.indexOf('ello', 2) // returns 7
'hello mellow'.indexOf('ello', 20) // returns -1
'hello mellow'.indexOf('ello', -1) // error
### Join
Returns a new string where the elements of string list are concatenated.
The function also accepts an optional separator which is placed between
elements in the resulting string.
<list<string>>.join() -> <string>
<list<string>>.join(<string>) -> <string>
Examples:
['hello', 'mellow'].join() // returns 'hellomellow'
['hello', 'mellow'].join(' ') // returns 'hello mellow'
[].join() // returns ''
[].join('/') // returns ''
### LastIndexOf
Returns the integer index of the last occurrence of the search string. If the
search string is not found the function returns -1.
The function also accepts an optional position which represents the last index
to be considered as the beginning of the substring match. If the substring is
the empty string, the index where the search starts is returned (string length
or custom).
<string>.lastIndexOf(<string>) -> <int>
<string>.lastIndexOf(<string>, <int>) -> <int>
Examples:
'hello mellow'.lastIndexOf('') // returns 12
'hello mellow'.lastIndexOf('ello') // returns 7
'hello mellow'.lastIndexOf('jello') // returns -1
'hello mellow'.lastIndexOf('ello', 6) // returns 1
'hello mellow'.lastIndexOf('ello', 20) // returns -1
'hello mellow'.lastIndexOf('ello', -1) // error
### LowerAscii
Returns a new string where all ASCII characters are lower-cased.
This function does not perform Unicode case-mapping for characters outside the
ASCII range.
<string>.lowerAscii() -> <string>
Examples:
'TacoCat'.lowerAscii() // returns 'tacocat'
'TacoCÆt Xii'.lowerAscii() // returns 'tacocÆt xii'
### Quote
**Introduced in version 1**
Takes the given string and makes it safe to print (without any formatting due to escape sequences).
If any invalid UTF-8 characters are encountered, they are replaced with \uFFFD.
strings.quote(<string>)
Examples:
strings.quote('single-quote with "double quote"') // returns '"single-quote with \"double quote\""'
strings.quote("two escape sequences \a\n") // returns '"two escape sequences \\a\\n"'
### Replace
Returns a new string based on the target, which replaces the occurrences of a
search string with a replacement string if present. The function accepts an
optional limit on the number of substring replacements to be made.
When the replacement limit is 0, the result is the original string. When the
limit is a negative number, the function behaves the same as replace all.
<string>.replace(<string>, <string>) -> <string>
<string>.replace(<string>, <string>, <int>) -> <string>
Examples:
'hello hello'.replace('he', 'we') // returns 'wello wello'
'hello hello'.replace('he', 'we', -1) // returns 'wello wello'
'hello hello'.replace('he', 'we', 1) // returns 'wello hello'
'hello hello'.replace('he', 'we', 0) // returns 'hello hello'
### Split
Returns a list of strings split from the input by the given separator. The
function accepts an optional argument specifying a limit on the number of
substrings produced by the split.
When the split limit is 0, the result is an empty list. When the limit is 1,
the result is the target string to split. When the limit is a negative
number, the function behaves the same as split all.
<string>.split(<string>) -> <list<string>>
<string>.split(<string>, <int>) -> <list<string>>
Examples:
'hello hello hello'.split(' ') // returns ['hello', 'hello', 'hello']
'hello hello hello'.split(' ', 0) // returns []
'hello hello hello'.split(' ', 1) // returns ['hello hello hello']
'hello hello hello'.split(' ', 2) // returns ['hello', 'hello hello']
'hello hello hello'.split(' ', -1) // returns ['hello', 'hello', 'hello']
### Substring
Returns the substring given a numeric range corresponding to character
positions. Optionally may omit the trailing range for a substring from a given
character position until the end of a string.
Character offsets are 0-based with an inclusive start range and exclusive end
range. It is an error to specify an end range that is lower than the start
range, or for either the start or end index to be negative or exceed the string
length.
<string>.substring(<int>) -> <string>
<string>.substring(<int>, <int>) -> <string>
Examples:
'tacocat'.substring(4) // returns 'cat'
'tacocat'.substring(0, 4) // returns 'taco'
'tacocat'.substring(-1) // error
'tacocat'.substring(2, 1) // error
### Trim
Returns a new string which removes the leading and trailing whitespace in the
target string. The trim function uses the Unicode definition of whitespace
which does not include the zero-width spaces. See:
https://en.wikipedia.org/wiki/Whitespace_character#Unicode
<string>.trim() -> <string>
Examples:
' \ttrim\n '.trim() // returns 'trim'
### UpperAscii
Returns a new string where all ASCII characters are upper-cased.
This function does not perform Unicode case-mapping for characters outside the
ASCII range.
<string>.upperAscii() -> <string>
Examples:
'TacoCat'.upperAscii() // returns 'TACOCAT'
'TacoCÆt Xii'.upperAscii() // returns 'TACOCÆT XII'
### Reverse
Returns a new string whose characters are the same as the target string, only formatted in
reverse order.
This function relies on converting strings to rune arrays in order to reverse.
It can be located in Version 3 of strings.
<string>.reverse() -> <string>
Examples:
'gums'.reverse() // returns 'smug'
'John Smith'.reverse() // returns 'htimS nhoJ'
## TwoVarComprehensions
TwoVarComprehensions introduces support for two-variable comprehensions.
The two-variable form of comprehensions looks similar to the one-variable
counterparts. Where possible, the same macro names were used and additional
macro signatures added. The notable distinction for two-variable comprehensions
is the introduction of `transformList`, `transformMap`, and `transformMapEntry`
support for list and map types rather than the more traditional `map` and
`filter` macros.
### All
Comprehension which tests whether all elements in the list or map satisfy a
given predicate. The `all` macro evaluates in a manner consistent with logical
AND and will short-circuit when encountering a `false` value.
<list>.all(indexVar, valueVar, <predicate>) -> bool
<map>.all(keyVar, valueVar, <predicate>) -> bool
Examples:
[1, 2, 3].all(i, j, i < j) // returns true
{'hello': 'world', 'taco': 'taco'}.all(k, v, k != v) // returns false
// Combines two-variable comprehension with single variable
{'h': ['hello', 'hi'], 'j': ['joke', 'jog']}
.all(k, vals, vals.all(v, v.startsWith(k))) // returns true
### Exists
Comprehension which tests whether any element in a list or map exists which
satisfies a given predicate. The `exists` macro evaluates in a manner consistent
with logical OR and will short-circuit when encountering a `true` value.
<list>.exists(indexVar, valueVar, <predicate>) -> bool
<map>.exists(keyVar, valueVar, <predicate>) -> bool
Examples:
{'greeting': 'hello', 'farewell': 'goodbye'}
.exists(k, v, k.startsWith('good') || v.endsWith('bye')) // returns true
[1, 2, 4, 8, 16].exists(i, v, v == 1024 && i == 10) // returns false
### ExistsOne
Comprehension which tests whether exactly one element in a list or map exists
which satisfies a given predicate expression. This comprehension does not
short-circuit in keeping with the one-variable exists one macro semantics.
<list>.existsOne(indexVar, valueVar, <predicate>)
<map>.existsOne(keyVar, valueVar, <predicate>)
This macro may also be used with the `exists_one` function name, for
compatibility with the one-variable macro of the same name.
Examples:
[1, 2, 1, 3, 1, 4].existsOne(i, v, i == 1 || v == 1) // returns false
[1, 1, 2, 2, 3, 3].existsOne(i, v, i == 2 && v == 2) // returns true
{'i': 0, 'j': 1, 'k': 2}.existsOne(i, v, i == 'l' || v == 1) // returns true
### TransformList
Comprehension which converts a map or a list into a list value. The output
expression of the comprehension determines the contents of the output list.
Elements in the list may optionally be filtered according to a predicate
expression, where elements that satisfy the predicate are transformed.
<list>.transformList(indexVar, valueVar, <transform>)
<list>.transformList(indexVar, valueVar, <filter>, <transform>)
<map>.transformList(keyVar, valueVar, <transform>)
<map>.transformList(keyVar, valueVar, <filter>, <transform>)
Examples:
[1, 2, 3].transformList(indexVar, valueVar,
(indexVar * valueVar) + valueVar) // returns [1, 4, 9]
[1, 2, 3].transformList(indexVar, valueVar, indexVar % 2 == 0
(indexVar * valueVar) + valueVar) // returns [1, 9]
{'greeting': 'hello', 'farewell': 'goodbye'}
.transformList(k, _, k) // returns ['greeting', 'farewell']
{'greeting': 'hello', 'farewell': 'goodbye'}
.transformList(_, v, v) // returns ['hello', 'goodbye']
### TransformMap
Comprehension which converts a map or a list into a map value. The output
expression of the comprehension determines the value of the output map entry;
however, the key remains fixed. Elements in the map may optionally be filtered
according to a predicate expression, where elements that satisfy the predicate
are transformed.
<list>.transformMap(indexVar, valueVar, <transform>)
<list>.transformMap(indexVar, valueVar, <filter>, <transform>)
<map>.transformMap(keyVar, valueVar, <transform>)
<map>.transformMap(keyVar, valueVar, <filter>, <transform>)
Examples:
[1, 2, 3].transformMap(indexVar, valueVar,
(indexVar * valueVar) + valueVar) // returns {0: 1, 1: 4, 2: 9}
[1, 2, 3].transformMap(indexVar, valueVar, indexVar % 2 == 0
(indexVar * valueVar) + valueVar) // returns {0: 1, 2: 9}
{'greeting': 'hello'}.transformMap(k, v, v + '!') // returns {'greeting': 'hello!'}
### TransformMapEntry
Comprehension which converts a map or a list into a map value; however, this
transform expects the entry expression be a map literal. If the transform
produces an entry which duplicates a key in the target map, the comprehension
will error. Note, that key equality is determined using CEL equality which
asserts that numeric values which are equal, even if they don't have the same
type will cause a key collision.
Elements in the map may optionally be filtered according to a predicate
expression, where elements that satisfy the predicate are transformed.
<list>.transformMap(indexVar, valueVar, <transform>)
<list>.transformMap(indexVar, valueVar, <filter>, <transform>)
<map>.transformMap(keyVar, valueVar, <transform>)
<map>.transformMap(keyVar, valueVar, <filter>, <transform>)
Examples:
// returns {'hello': 'greeting'}
{'greeting': 'hello'}.transformMapEntry(keyVar, valueVar, {valueVar: keyVar})
// reverse lookup, require all values in list be unique
[1, 2, 3].transformMapEntry(indexVar, valueVar, {valueVar: indexVar})
{'greeting': 'aloha', 'farewell': 'aloha'}
.transformMapEntry(keyVar, valueVar, {valueVar: keyVar}) // error, duplicate key

336
e2e/vendor/github.com/google/cel-go/ext/bindings.go generated vendored Normal file
View File

@ -0,0 +1,336 @@
// Copyright 2023 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 ext
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
"sync"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/ast"
"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"
)
// Bindings returns a cel.EnvOption to configure support for local variable
// bindings in expressions.
//
// # Cel.Bind
//
// Binds a simple identifier to an initialization expression which may be used
// in a subsequenct result expression. Bindings may also be nested within each
// other.
//
// cel.bind(<varName>, <initExpr>, <resultExpr>)
//
// Examples:
//
// cel.bind(a, 'hello',
// cel.bind(b, 'world', a + b + b + a)) // "helloworldworldhello"
//
// // Avoid a list allocation within the exists comprehension.
// cel.bind(valid_values, [a, b, c],
// [d, e, f].exists(elem, elem in valid_values))
//
// Local bindings are not guaranteed to be evaluated before use.
func Bindings(options ...BindingsOption) cel.EnvOption {
b := &celBindings{version: math.MaxUint32}
for _, o := range options {
b = o(b)
}
return cel.Lib(b)
}
const (
celNamespace = "cel"
bindMacro = "bind"
blockFunc = "@block"
unusedIterVar = "#unused"
)
// BindingsOption declares a functional operator for configuring the Bindings library behavior.
type BindingsOption func(*celBindings) *celBindings
// BindingsVersion sets the version of the bindings library to an explicit version.
func BindingsVersion(version uint32) BindingsOption {
return func(lib *celBindings) *celBindings {
lib.version = version
return lib
}
}
type celBindings struct {
version uint32
}
func (*celBindings) LibraryName() string {
return "cel.lib.ext.cel.bindings"
}
func (lib *celBindings) CompileOptions() []cel.EnvOption {
opts := []cel.EnvOption{
cel.Macros(
// cel.bind(var, <init>, <expr>)
cel.ReceiverMacro(bindMacro, 3, celBind),
),
}
if lib.version >= 1 {
// The cel.@block signature takes a list of subexpressions and a typed expression which is
// used as the output type.
paramType := cel.TypeParamType("T")
opts = append(opts,
cel.Function("cel.@block",
cel.Overload("cel_block_list",
[]*cel.Type{cel.ListType(cel.DynType), paramType}, paramType)),
)
opts = append(opts, cel.ASTValidators(blockValidationExemption{}))
}
return opts
}
func (lib *celBindings) ProgramOptions() []cel.ProgramOption {
if lib.version >= 1 {
celBlockPlan := func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
call, ok := i.(interpreter.InterpretableCall)
if !ok {
return i, nil
}
switch call.Function() {
case "cel.@block":
args := call.Args()
if len(args) != 2 {
return nil, fmt.Errorf("cel.@block expects two arguments, but got %d", len(args))
}
expr := args[1]
// Non-empty block
if block, ok := args[0].(interpreter.InterpretableConstructor); ok {
slotExprs := block.InitVals()
return newDynamicBlock(slotExprs, expr), nil
}
// Constant valued block which can happen during runtime optimization.
if cons, ok := args[0].(interpreter.InterpretableConst); ok {
if cons.Value().Type() == types.ListType {
l := cons.Value().(traits.Lister)
if l.Size().Equal(types.IntZero) == types.True {
return args[1], nil
}
return newConstantBlock(l, expr), nil
}
}
return nil, errors.New("cel.@block expects a list constructor as the first argument")
default:
return i, nil
}
}
return []cel.ProgramOption{cel.CustomDecorator(celBlockPlan)}
}
return []cel.ProgramOption{}
}
type blockValidationExemption struct{}
// Name returns the name of the validator.
func (blockValidationExemption) Name() string {
return "cel.lib.ext.validate.functions.cel.block"
}
// Configure implements the ASTValidatorConfigurer interface and augments the list of functions to skip
// during homogeneous aggregate literal type-checks.
func (blockValidationExemption) Configure(config cel.MutableValidatorConfig) error {
functions := config.GetOrDefault(cel.HomogeneousAggregateLiteralExemptFunctions, []string{}).([]string)
functions = append(functions, "cel.@block")
return config.Set(cel.HomogeneousAggregateLiteralExemptFunctions, functions)
}
// Validate is a no-op as the intent is to simply disable strong type-checks for list literals during
// when they occur within cel.@block calls as the arg types have already been validated.
func (blockValidationExemption) Validate(env *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) {
}
func celBind(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
if !macroTargetMatchesNamespace(celNamespace, target) {
return nil, nil
}
varIdent := args[0]
varName := ""
switch varIdent.Kind() {
case ast.IdentKind:
varName = varIdent.AsIdent()
default:
return nil, mef.NewError(varIdent.ID(), "cel.bind() variable names must be simple identifiers")
}
varInit := args[1]
resultExpr := args[2]
return mef.NewComprehension(
mef.NewList(),
unusedIterVar,
varName,
varInit,
mef.NewLiteral(types.False),
mef.NewIdent(varName),
resultExpr,
), nil
}
func newDynamicBlock(slotExprs []interpreter.Interpretable, expr interpreter.Interpretable) interpreter.Interpretable {
bs := &dynamicBlock{
slotExprs: slotExprs,
expr: expr,
}
bs.slotActivationPool = &sync.Pool{
New: func() any {
slotCount := len(slotExprs)
sa := &dynamicSlotActivation{
slotExprs: slotExprs,
slotCount: slotCount,
slotVals: make([]*slotVal, slotCount),
}
for i := 0; i < slotCount; i++ {
sa.slotVals[i] = &slotVal{}
}
return sa
},
}
return bs
}
type dynamicBlock struct {
slotExprs []interpreter.Interpretable
expr interpreter.Interpretable
slotActivationPool *sync.Pool
}
// ID implements the Interpretable interface method.
func (b *dynamicBlock) ID() int64 {
return b.expr.ID()
}
// Eval implements the Interpretable interface method.
func (b *dynamicBlock) Eval(activation interpreter.Activation) ref.Val {
sa := b.slotActivationPool.Get().(*dynamicSlotActivation)
sa.Activation = activation
defer b.clearSlots(sa)
return b.expr.Eval(sa)
}
func (b *dynamicBlock) clearSlots(sa *dynamicSlotActivation) {
sa.reset()
b.slotActivationPool.Put(sa)
}
type slotVal struct {
value *ref.Val
visited bool
}
type dynamicSlotActivation struct {
interpreter.Activation
slotExprs []interpreter.Interpretable
slotCount int
slotVals []*slotVal
}
// ResolveName implements the Activation interface method but handles variables prefixed with `@index`
// as special variables which exist within the slot-based memory of the cel.@block() where each slot
// refers to an expression which must be computed only once.
func (sa *dynamicSlotActivation) ResolveName(name string) (any, bool) {
if idx, found := matchSlot(name, sa.slotCount); found {
v := sa.slotVals[idx]
if v.visited {
// Return not found if the index expression refers to itself
if v.value == nil {
return nil, false
}
return *v.value, true
}
v.visited = true
val := sa.slotExprs[idx].Eval(sa)
v.value = &val
return val, true
}
return sa.Activation.ResolveName(name)
}
func (sa *dynamicSlotActivation) reset() {
sa.Activation = nil
for _, sv := range sa.slotVals {
sv.visited = false
sv.value = nil
}
}
func newConstantBlock(slots traits.Lister, expr interpreter.Interpretable) interpreter.Interpretable {
count := slots.Size().(types.Int)
return &constantBlock{slots: slots, slotCount: int(count), expr: expr}
}
type constantBlock struct {
slots traits.Lister
slotCount int
expr interpreter.Interpretable
}
// ID implements the interpreter.Interpretable interface method.
func (b *constantBlock) ID() int64 {
return b.expr.ID()
}
// Eval implements the interpreter.Interpretable interface method, and will proxy @index prefixed variable
// lookups into a set of constant slots determined from the plan step.
func (b *constantBlock) Eval(activation interpreter.Activation) ref.Val {
vars := constantSlotActivation{Activation: activation, slots: b.slots, slotCount: b.slotCount}
return b.expr.Eval(vars)
}
type constantSlotActivation struct {
interpreter.Activation
slots traits.Lister
slotCount int
}
// ResolveName implements Activation interface method and proxies @index prefixed lookups into the slot
// activation associated with the block scope.
func (sa constantSlotActivation) ResolveName(name string) (any, bool) {
if idx, found := matchSlot(name, sa.slotCount); found {
return sa.slots.Get(types.Int(idx)), true
}
return sa.Activation.ResolveName(name)
}
func matchSlot(name string, slotCount int) (int, bool) {
if idx, found := strings.CutPrefix(name, indexPrefix); found {
idx, err := strconv.Atoi(idx)
// Return not found if the index is not numeric
if err != nil {
return -1, false
}
// Return not found if the index is not a valid slot
if idx < 0 || idx >= slotCount {
return -1, false
}
return idx, true
}
return -1, false
}
var (
indexPrefix = "@index"
)

View File

@ -0,0 +1,410 @@
// Copyright 2024 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 ext
import (
"fmt"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/operators"
"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/parser"
)
const (
mapInsert = "cel.@mapInsert"
mapInsertOverloadMap = "@mapInsert_map_map"
mapInsertOverloadKeyValue = "@mapInsert_map_key_value"
)
// TwoVarComprehensions introduces support for two-variable comprehensions.
//
// The two-variable form of comprehensions looks similar to the one-variable counterparts.
// Where possible, the same macro names were used and additional macro signatures added.
// The notable distinction for two-variable comprehensions is the introduction of
// `transformList`, `transformMap`, and `transformMapEntry` support for list and map types
// rather than the more traditional `map` and `filter` macros.
//
// # All
//
// Comprehension which tests whether all elements in the list or map satisfy a given
// predicate. The `all` macro evaluates in a manner consistent with logical AND and will
// short-circuit when encountering a `false` value.
//
// <list>.all(indexVar, valueVar, <predicate>) -> bool
// <map>.all(keyVar, valueVar, <predicate>) -> bool
//
// Examples:
//
// [1, 2, 3].all(i, j, i < j) // returns true
// {'hello': 'world', 'taco': 'taco'}.all(k, v, k != v) // returns false
//
// // Combines two-variable comprehension with single variable
// {'h': ['hello', 'hi'], 'j': ['joke', 'jog']}
// .all(k, vals, vals.all(v, v.startsWith(k))) // returns true
//
// # Exists
//
// Comprehension which tests whether any element in a list or map exists which satisfies
// a given predicate. The `exists` macro evaluates in a manner consistent with logical OR
// and will short-circuit when encountering a `true` value.
//
// <list>.exists(indexVar, valueVar, <predicate>) -> bool
// <map>.exists(keyVar, valueVar, <predicate>) -> bool
//
// Examples:
//
// {'greeting': 'hello', 'farewell': 'goodbye'}
// .exists(k, v, k.startsWith('good') || v.endsWith('bye')) // returns true
// [1, 2, 4, 8, 16].exists(i, v, v == 1024 && i == 10) // returns false
//
// # ExistsOne
//
// Comprehension which tests whether exactly one element in a list or map exists which
// satisfies a given predicate expression. This comprehension does not short-circuit in
// keeping with the one-variable exists one macro semantics.
//
// <list>.existsOne(indexVar, valueVar, <predicate>)
// <map>.existsOne(keyVar, valueVar, <predicate>)
//
// This macro may also be used with the `exists_one` function name, for compatibility
// with the one-variable macro of the same name.
//
// Examples:
//
// [1, 2, 1, 3, 1, 4].existsOne(i, v, i == 1 || v == 1) // returns false
// [1, 1, 2, 2, 3, 3].existsOne(i, v, i == 2 && v == 2) // returns true
// {'i': 0, 'j': 1, 'k': 2}.existsOne(i, v, i == 'l' || v == 1) // returns true
//
// # TransformList
//
// Comprehension which converts a map or a list into a list value. The output expression
// of the comprehension determines the contents of the output list. Elements in the list
// may optionally be filtered according to a predicate expression, where elements that
// satisfy the predicate are transformed.
//
// <list>.transformList(indexVar, valueVar, <transform>)
// <list>.transformList(indexVar, valueVar, <filter>, <transform>)
// <map>.transformList(keyVar, valueVar, <transform>)
// <map>.transformList(keyVar, valueVar, <filter>, <transform>)
//
// Examples:
//
// [1, 2, 3].transformList(indexVar, valueVar,
// (indexVar * valueVar) + valueVar) // returns [1, 4, 9]
// [1, 2, 3].transformList(indexVar, valueVar, indexVar % 2 == 0
// (indexVar * valueVar) + valueVar) // returns [1, 9]
// {'greeting': 'hello', 'farewell': 'goodbye'}
// .transformList(k, _, k) // returns ['greeting', 'farewell']
// {'greeting': 'hello', 'farewell': 'goodbye'}
// .transformList(_, v, v) // returns ['hello', 'goodbye']
//
// # TransformMap
//
// Comprehension which converts a map or a list into a map value. The output expression
// of the comprehension determines the value of the output map entry; however, the key
// remains fixed. Elements in the map may optionally be filtered according to a predicate
// expression, where elements that satisfy the predicate are transformed.
//
// <list>.transformMap(indexVar, valueVar, <transform>)
// <list>.transformMap(indexVar, valueVar, <filter>, <transform>)
// <map>.transformMap(keyVar, valueVar, <transform>)
// <map>.transformMap(keyVar, valueVar, <filter>, <transform>)
//
// Examples:
//
// [1, 2, 3].transformMap(indexVar, valueVar,
// (indexVar * valueVar) + valueVar) // returns {0: 1, 1: 4, 2: 9}
// [1, 2, 3].transformMap(indexVar, valueVar, indexVar % 2 == 0
// (indexVar * valueVar) + valueVar) // returns {0: 1, 2: 9}
// {'greeting': 'hello'}.transformMap(k, v, v + '!') // returns {'greeting': 'hello!'}
//
// # TransformMapEntry
//
// Comprehension which converts a map or a list into a map value; however, this transform
// expects the entry expression be a map literal. If the tranform produces an entry which
// duplicates a key in the target map, the comprehension will error. Note, that key
// equality is determined using CEL equality which asserts that numeric values which are
// equal, even if they don't have the same type will cause a key collision.
//
// Elements in the map may optionally be filtered according to a predicate expression, where
// elements that satisfy the predicate are transformed.
//
// <list>.transformMap(indexVar, valueVar, <transform>)
// <list>.transformMap(indexVar, valueVar, <filter>, <transform>)
// <map>.transformMap(keyVar, valueVar, <transform>)
// <map>.transformMap(keyVar, valueVar, <filter>, <transform>)
//
// Examples:
//
// // returns {'hello': 'greeting'}
// {'greeting': 'hello'}.transformMapEntry(keyVar, valueVar, {valueVar: keyVar})
// // reverse lookup, require all values in list be unique
// [1, 2, 3].transformMapEntry(indexVar, valueVar, {valueVar: indexVar})
//
// {'greeting': 'aloha', 'farewell': 'aloha'}
// .transformMapEntry(keyVar, valueVar, {valueVar: keyVar}) // error, duplicate key
func TwoVarComprehensions() cel.EnvOption {
return cel.Lib(compreV2Lib{})
}
type compreV2Lib struct{}
// LibraryName implements that SingletonLibrary interface method.
func (compreV2Lib) LibraryName() string {
return "cel.lib.ext.comprev2"
}
// CompileOptions implements the cel.Library interface method.
func (compreV2Lib) CompileOptions() []cel.EnvOption {
kType := cel.TypeParamType("K")
vType := cel.TypeParamType("V")
mapKVType := cel.MapType(kType, vType)
opts := []cel.EnvOption{
cel.Macros(
cel.ReceiverMacro("all", 3, quantifierAll),
cel.ReceiverMacro("exists", 3, quantifierExists),
cel.ReceiverMacro("existsOne", 3, quantifierExistsOne),
cel.ReceiverMacro("exists_one", 3, quantifierExistsOne),
cel.ReceiverMacro("transformList", 3, transformList),
cel.ReceiverMacro("transformList", 4, transformList),
cel.ReceiverMacro("transformMap", 3, transformMap),
cel.ReceiverMacro("transformMap", 4, transformMap),
cel.ReceiverMacro("transformMapEntry", 3, transformMapEntry),
cel.ReceiverMacro("transformMapEntry", 4, transformMapEntry),
),
cel.Function(mapInsert,
cel.Overload(mapInsertOverloadKeyValue, []*cel.Type{mapKVType, kType, vType}, mapKVType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
m := args[0].(traits.Mapper)
k := args[1]
v := args[2]
return types.InsertMapKeyValue(m, k, v)
})),
cel.Overload(mapInsertOverloadMap, []*cel.Type{mapKVType, mapKVType}, mapKVType,
cel.BinaryBinding(func(targetMap, updateMap ref.Val) ref.Val {
tm := targetMap.(traits.Mapper)
um := updateMap.(traits.Mapper)
umIt := um.Iterator()
for umIt.HasNext() == types.True {
k := umIt.Next()
updateOrErr := types.InsertMapKeyValue(tm, k, um.Get(k))
if types.IsError(updateOrErr) {
return updateOrErr
}
tm = updateOrErr.(traits.Mapper)
}
return tm
})),
),
}
return opts
}
// ProgramOptions implements the cel.Library interface method
func (compreV2Lib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func quantifierAll(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
iterVar1, iterVar2, err := extractIterVars(mef, args[0], args[1])
if err != nil {
return nil, err
}
return mef.NewComprehensionTwoVar(
target,
iterVar1,
iterVar2,
parser.AccumulatorName,
/*accuInit=*/ mef.NewLiteral(types.True),
/*condition=*/ mef.NewCall(operators.NotStrictlyFalse, mef.NewAccuIdent()),
/*step=*/ mef.NewCall(operators.LogicalAnd, mef.NewAccuIdent(), args[2]),
/*result=*/ mef.NewAccuIdent(),
), nil
}
func quantifierExists(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
iterVar1, iterVar2, err := extractIterVars(mef, args[0], args[1])
if err != nil {
return nil, err
}
return mef.NewComprehensionTwoVar(
target,
iterVar1,
iterVar2,
parser.AccumulatorName,
/*accuInit=*/ mef.NewLiteral(types.False),
/*condition=*/ mef.NewCall(operators.NotStrictlyFalse, mef.NewCall(operators.LogicalNot, mef.NewAccuIdent())),
/*step=*/ mef.NewCall(operators.LogicalOr, mef.NewAccuIdent(), args[2]),
/*result=*/ mef.NewAccuIdent(),
), nil
}
func quantifierExistsOne(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
iterVar1, iterVar2, err := extractIterVars(mef, args[0], args[1])
if err != nil {
return nil, err
}
return mef.NewComprehensionTwoVar(
target,
iterVar1,
iterVar2,
parser.AccumulatorName,
/*accuInit=*/ mef.NewLiteral(types.Int(0)),
/*condition=*/ mef.NewLiteral(types.True),
/*step=*/ mef.NewCall(operators.Conditional, args[2],
mef.NewCall(operators.Add, mef.NewAccuIdent(), mef.NewLiteral(types.Int(1))),
mef.NewAccuIdent()),
/*result=*/ mef.NewCall(operators.Equals, mef.NewAccuIdent(), mef.NewLiteral(types.Int(1))),
), nil
}
func transformList(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
iterVar1, iterVar2, err := extractIterVars(mef, args[0], args[1])
if err != nil {
return nil, err
}
var transform ast.Expr
var filter ast.Expr
if len(args) == 4 {
filter = args[2]
transform = args[3]
} else {
filter = nil
transform = args[2]
}
// __result__ = __result__ + [transform]
step := mef.NewCall(operators.Add, mef.NewAccuIdent(), mef.NewList(transform))
if filter != nil {
// __result__ = (filter) ? __result__ + [transform] : __result__
step = mef.NewCall(operators.Conditional, filter, step, mef.NewAccuIdent())
}
return mef.NewComprehensionTwoVar(
target,
iterVar1,
iterVar2,
parser.AccumulatorName,
/*accuInit=*/ mef.NewList(),
/*condition=*/ mef.NewLiteral(types.True),
step,
/*result=*/ mef.NewAccuIdent(),
), nil
}
func transformMap(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
iterVar1, iterVar2, err := extractIterVars(mef, args[0], args[1])
if err != nil {
return nil, err
}
var transform ast.Expr
var filter ast.Expr
if len(args) == 4 {
filter = args[2]
transform = args[3]
} else {
filter = nil
transform = args[2]
}
// __result__ = cel.@mapInsert(__result__, iterVar1, transform)
step := mef.NewCall(mapInsert, mef.NewAccuIdent(), mef.NewIdent(iterVar1), transform)
if filter != nil {
// __result__ = (filter) ? cel.@mapInsert(__result__, iterVar1, transform) : __result__
step = mef.NewCall(operators.Conditional, filter, step, mef.NewAccuIdent())
}
return mef.NewComprehensionTwoVar(
target,
iterVar1,
iterVar2,
parser.AccumulatorName,
/*accuInit=*/ mef.NewMap(),
/*condition=*/ mef.NewLiteral(types.True),
step,
/*result=*/ mef.NewAccuIdent(),
), nil
}
func transformMapEntry(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
iterVar1, iterVar2, err := extractIterVars(mef, args[0], args[1])
if err != nil {
return nil, err
}
var transform ast.Expr
var filter ast.Expr
if len(args) == 4 {
filter = args[2]
transform = args[3]
} else {
filter = nil
transform = args[2]
}
// __result__ = cel.@mapInsert(__result__, transform)
step := mef.NewCall(mapInsert, mef.NewAccuIdent(), transform)
if filter != nil {
// __result__ = (filter) ? cel.@mapInsert(__result__, transform) : __result__
step = mef.NewCall(operators.Conditional, filter, step, mef.NewAccuIdent())
}
return mef.NewComprehensionTwoVar(
target,
iterVar1,
iterVar2,
parser.AccumulatorName,
/*accuInit=*/ mef.NewMap(),
/*condition=*/ mef.NewLiteral(types.True),
step,
/*result=*/ mef.NewAccuIdent(),
), nil
}
func extractIterVars(mef cel.MacroExprFactory, arg0, arg1 ast.Expr) (string, string, *cel.Error) {
iterVar1, err := extractIterVar(mef, arg0)
if err != nil {
return "", "", err
}
iterVar2, err := extractIterVar(mef, arg1)
if err != nil {
return "", "", err
}
if iterVar1 == iterVar2 {
return "", "", mef.NewError(arg1.ID(), fmt.Sprintf("duplicate variable name: %s", iterVar1))
}
if iterVar1 == parser.AccumulatorName {
return "", "", mef.NewError(arg0.ID(), "iteration variable overwrites accumulator variable")
}
if iterVar2 == parser.AccumulatorName {
return "", "", mef.NewError(arg1.ID(), "iteration variable overwrites accumulator variable")
}
return iterVar1, iterVar2, nil
}
func extractIterVar(mef cel.MacroExprFactory, target ast.Expr) (string, *cel.Error) {
iterVar, found := extractIdent(target)
if !found {
return "", mef.NewError(target.ID(), "argument must be a simple name")
}
return iterVar, nil
}

94
e2e/vendor/github.com/google/cel-go/ext/encoders.go generated vendored Normal file
View File

@ -0,0 +1,94 @@
// 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 ext
import (
"encoding/base64"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// Encoders returns a cel.EnvOption to configure extended functions for string, byte, and object
// encodings.
//
// # Base64.Decode
//
// Decodes base64-encoded string to bytes.
//
// This function will return an error if the string input is not base64-encoded.
//
// base64.decode(<string>) -> <bytes>
//
// Examples:
//
// base64.decode('aGVsbG8=') // return b'hello'
// base64.decode('aGVsbG8') // return b'hello'
//
// # Base64.Encode
//
// Encodes bytes to a base64-encoded string.
//
// base64.encode(<bytes>) -> <string>
//
// Examples:
//
// base64.encode(b'hello') // return b'aGVsbG8='
func Encoders() cel.EnvOption {
return cel.Lib(encoderLib{})
}
type encoderLib struct{}
func (encoderLib) LibraryName() string {
return "cel.lib.ext.encoders"
}
func (encoderLib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
cel.Function("base64.decode",
cel.Overload("base64_decode_string", []*cel.Type{cel.StringType}, cel.BytesType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return bytesOrError(base64DecodeString(string(s)))
}))),
cel.Function("base64.encode",
cel.Overload("base64_encode_bytes", []*cel.Type{cel.BytesType}, cel.StringType,
cel.UnaryBinding(func(bytes ref.Val) ref.Val {
b := bytes.(types.Bytes)
return stringOrError(base64EncodeBytes([]byte(b)))
}))),
}
}
func (encoderLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func base64DecodeString(str string) ([]byte, error) {
b, err := base64.StdEncoding.DecodeString(str)
if err == nil {
return b, nil
}
if _, tryAltEncoding := err.(base64.CorruptInputError); tryAltEncoding {
return base64.RawStdEncoding.DecodeString(str)
}
return nil, err
}
func base64EncodeBytes(bytes []byte) (string, error) {
return base64.StdEncoding.EncodeToString(bytes), nil
}

904
e2e/vendor/github.com/google/cel-go/ext/formatting.go generated vendored Normal file
View File

@ -0,0 +1,904 @@
// Copyright 2023 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 ext
import (
"errors"
"fmt"
"math"
"sort"
"strconv"
"strings"
"unicode"
"golang.org/x/text/language"
"golang.org/x/text/message"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/overloads"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
)
type clauseImpl func(ref.Val, string) (string, error)
func clauseForType(argType ref.Type) (clauseImpl, error) {
switch argType {
case types.IntType, types.UintType:
return formatDecimal, nil
case types.StringType, types.BytesType, types.BoolType, types.NullType, types.TypeType:
return FormatString, nil
case types.TimestampType, types.DurationType:
// special case to ensure timestamps/durations get printed as CEL literals
return func(arg ref.Val, locale string) (string, error) {
argStrVal := arg.ConvertToType(types.StringType)
argStr := argStrVal.Value().(string)
if arg.Type() == types.TimestampType {
return fmt.Sprintf("timestamp(%q)", argStr), nil
}
if arg.Type() == types.DurationType {
return fmt.Sprintf("duration(%q)", argStr), nil
}
return "", fmt.Errorf("cannot convert argument of type %s to timestamp/duration", arg.Type().TypeName())
}, nil
case types.ListType:
return formatList, nil
case types.MapType:
return formatMap, nil
case types.DoubleType:
// avoid formatFixed so we can output a period as the decimal separator in order
// to always be a valid CEL literal
return func(arg ref.Val, locale string) (string, error) {
argDouble, ok := arg.Value().(float64)
if !ok {
return "", fmt.Errorf("couldn't convert %s to float64", arg.Type().TypeName())
}
fmtStr := fmt.Sprintf("%%.%df", defaultPrecision)
return fmt.Sprintf(fmtStr, argDouble), nil
}, nil
case types.TypeType:
return func(arg ref.Val, locale string) (string, error) {
return fmt.Sprintf("type(%s)", arg.Value().(string)), nil
}, nil
default:
return nil, fmt.Errorf("no formatting function for %s", argType.TypeName())
}
}
func formatList(arg ref.Val, locale string) (string, error) {
argList := arg.(traits.Lister)
argIterator := argList.Iterator()
var listStrBuilder strings.Builder
_, err := listStrBuilder.WriteRune('[')
if err != nil {
return "", fmt.Errorf("error writing to list string: %w", err)
}
for argIterator.HasNext() == types.True {
member := argIterator.Next()
memberFormat, err := clauseForType(member.Type())
if err != nil {
return "", err
}
unquotedStr, err := memberFormat(member, locale)
if err != nil {
return "", err
}
str := quoteForCEL(member, unquotedStr)
_, err = listStrBuilder.WriteString(str)
if err != nil {
return "", fmt.Errorf("error writing to list string: %w", err)
}
if argIterator.HasNext() == types.True {
_, err = listStrBuilder.WriteString(", ")
if err != nil {
return "", fmt.Errorf("error writing to list string: %w", err)
}
}
}
_, err = listStrBuilder.WriteRune(']')
if err != nil {
return "", fmt.Errorf("error writing to list string: %w", err)
}
return listStrBuilder.String(), nil
}
func formatMap(arg ref.Val, locale string) (string, error) {
argMap := arg.(traits.Mapper)
argIterator := argMap.Iterator()
type mapPair struct {
key string
value string
}
argPairs := make([]mapPair, argMap.Size().Value().(int64))
i := 0
for argIterator.HasNext() == types.True {
key := argIterator.Next()
var keyFormat clauseImpl
switch key.Type() {
case types.StringType, types.BoolType:
keyFormat = FormatString
case types.IntType, types.UintType:
keyFormat = formatDecimal
default:
return "", fmt.Errorf("no formatting function for map key of type %s", key.Type().TypeName())
}
unquotedKeyStr, err := keyFormat(key, locale)
if err != nil {
return "", err
}
keyStr := quoteForCEL(key, unquotedKeyStr)
value, found := argMap.Find(key)
if !found {
return "", fmt.Errorf("could not find key: %q", key)
}
valueFormat, err := clauseForType(value.Type())
if err != nil {
return "", err
}
unquotedValueStr, err := valueFormat(value, locale)
if err != nil {
return "", err
}
valueStr := quoteForCEL(value, unquotedValueStr)
argPairs[i] = mapPair{keyStr, valueStr}
i++
}
sort.SliceStable(argPairs, func(x, y int) bool {
return argPairs[x].key < argPairs[y].key
})
var mapStrBuilder strings.Builder
_, err := mapStrBuilder.WriteRune('{')
if err != nil {
return "", fmt.Errorf("error writing to map string: %w", err)
}
for i, entry := range argPairs {
_, err = mapStrBuilder.WriteString(fmt.Sprintf("%s:%s", entry.key, entry.value))
if err != nil {
return "", fmt.Errorf("error writing to map string: %w", err)
}
if i < len(argPairs)-1 {
_, err = mapStrBuilder.WriteString(", ")
if err != nil {
return "", fmt.Errorf("error writing to map string: %w", err)
}
}
}
_, err = mapStrBuilder.WriteRune('}')
if err != nil {
return "", fmt.Errorf("error writing to map string: %w", err)
}
return mapStrBuilder.String(), nil
}
// quoteForCEL takes a formatted, unquoted value and quotes it in a manner suitable
// for embedding directly in CEL.
func quoteForCEL(refVal ref.Val, unquotedValue string) string {
switch refVal.Type() {
case types.StringType:
return fmt.Sprintf("%q", unquotedValue)
case types.BytesType:
return fmt.Sprintf("b%q", unquotedValue)
case types.DoubleType:
// special case to handle infinity/NaN
num := refVal.Value().(float64)
if math.IsInf(num, 1) || math.IsInf(num, -1) || math.IsNaN(num) {
return fmt.Sprintf("%q", unquotedValue)
}
return unquotedValue
default:
return unquotedValue
}
}
// FormatString returns the string representation of a CEL value.
//
// It is used to implement the %s specifier in the (string).format() extension function.
func FormatString(arg ref.Val, locale string) (string, error) {
switch arg.Type() {
case types.ListType:
return formatList(arg, locale)
case types.MapType:
return formatMap(arg, locale)
case types.IntType, types.UintType, types.DoubleType,
types.BoolType, types.StringType, types.TimestampType, types.BytesType, types.DurationType, types.TypeType:
argStrVal := arg.ConvertToType(types.StringType)
argStr, ok := argStrVal.Value().(string)
if !ok {
return "", fmt.Errorf("could not convert argument %q to string", argStrVal)
}
return argStr, nil
case types.NullType:
return "null", nil
default:
return "", stringFormatError(runtimeID, arg.Type().TypeName())
}
}
func formatDecimal(arg ref.Val, locale string) (string, error) {
switch arg.Type() {
case types.IntType:
argInt, ok := arg.ConvertToType(types.IntType).Value().(int64)
if !ok {
return "", fmt.Errorf("could not convert \"%s\" to int64", arg.Value())
}
return fmt.Sprintf("%d", argInt), nil
case types.UintType:
argInt, ok := arg.ConvertToType(types.UintType).Value().(uint64)
if !ok {
return "", fmt.Errorf("could not convert \"%s\" to uint64", arg.Value())
}
return fmt.Sprintf("%d", argInt), nil
default:
return "", decimalFormatError(runtimeID, arg.Type().TypeName())
}
}
func matchLanguage(locale string) (language.Tag, error) {
matcher, err := makeMatcher(locale)
if err != nil {
return language.Und, err
}
tag, _ := language.MatchStrings(matcher, locale)
return tag, nil
}
func makeMatcher(locale string) (language.Matcher, error) {
tags := make([]language.Tag, 0)
tag, err := language.Parse(locale)
if err != nil {
return nil, err
}
tags = append(tags, tag)
return language.NewMatcher(tags), nil
}
type stringFormatter struct{}
func (c *stringFormatter) String(arg ref.Val, locale string) (string, error) {
return FormatString(arg, locale)
}
func (c *stringFormatter) Decimal(arg ref.Val, locale string) (string, error) {
return formatDecimal(arg, locale)
}
func (c *stringFormatter) Fixed(precision *int) func(ref.Val, string) (string, error) {
if precision == nil {
precision = new(int)
*precision = defaultPrecision
}
return func(arg ref.Val, locale string) (string, error) {
strException := false
if arg.Type() == types.StringType {
argStr := arg.Value().(string)
if argStr == "NaN" || argStr == "Infinity" || argStr == "-Infinity" {
strException = true
}
}
if arg.Type() != types.DoubleType && !strException {
return "", fixedPointFormatError(runtimeID, arg.Type().TypeName())
}
argFloatVal := arg.ConvertToType(types.DoubleType)
argFloat, ok := argFloatVal.Value().(float64)
if !ok {
return "", fmt.Errorf("could not convert \"%s\" to float64", argFloatVal.Value())
}
fmtStr := fmt.Sprintf("%%.%df", *precision)
matchedLocale, err := matchLanguage(locale)
if err != nil {
return "", fmt.Errorf("error matching locale: %w", err)
}
return message.NewPrinter(matchedLocale).Sprintf(fmtStr, argFloat), nil
}
}
func (c *stringFormatter) Scientific(precision *int) func(ref.Val, string) (string, error) {
if precision == nil {
precision = new(int)
*precision = defaultPrecision
}
return func(arg ref.Val, locale string) (string, error) {
strException := false
if arg.Type() == types.StringType {
argStr := arg.Value().(string)
if argStr == "NaN" || argStr == "Infinity" || argStr == "-Infinity" {
strException = true
}
}
if arg.Type() != types.DoubleType && !strException {
return "", scientificFormatError(runtimeID, arg.Type().TypeName())
}
argFloatVal := arg.ConvertToType(types.DoubleType)
argFloat, ok := argFloatVal.Value().(float64)
if !ok {
return "", fmt.Errorf("could not convert \"%v\" to float64", argFloatVal.Value())
}
matchedLocale, err := matchLanguage(locale)
if err != nil {
return "", fmt.Errorf("error matching locale: %w", err)
}
fmtStr := fmt.Sprintf("%%%de", *precision)
return message.NewPrinter(matchedLocale).Sprintf(fmtStr, argFloat), nil
}
}
func (c *stringFormatter) Binary(arg ref.Val, locale string) (string, error) {
switch arg.Type() {
case types.IntType:
argInt := arg.Value().(int64)
// locale is intentionally unused as integers formatted as binary
// strings are locale-independent
return fmt.Sprintf("%b", argInt), nil
case types.UintType:
argInt := arg.Value().(uint64)
return fmt.Sprintf("%b", argInt), nil
case types.BoolType:
argBool := arg.Value().(bool)
if argBool {
return "1", nil
}
return "0", nil
default:
return "", binaryFormatError(runtimeID, arg.Type().TypeName())
}
}
func (c *stringFormatter) Hex(useUpper bool) func(ref.Val, string) (string, error) {
return func(arg ref.Val, locale string) (string, error) {
fmtStr := "%x"
if useUpper {
fmtStr = "%X"
}
switch arg.Type() {
case types.StringType, types.BytesType:
if arg.Type() == types.BytesType {
return fmt.Sprintf(fmtStr, arg.Value().([]byte)), nil
}
return fmt.Sprintf(fmtStr, arg.Value().(string)), nil
case types.IntType:
argInt, ok := arg.Value().(int64)
if !ok {
return "", fmt.Errorf("could not convert \"%s\" to int64", arg.Value())
}
return fmt.Sprintf(fmtStr, argInt), nil
case types.UintType:
argInt, ok := arg.Value().(uint64)
if !ok {
return "", fmt.Errorf("could not convert \"%s\" to uint64", arg.Value())
}
return fmt.Sprintf(fmtStr, argInt), nil
default:
return "", hexFormatError(runtimeID, arg.Type().TypeName())
}
}
}
func (c *stringFormatter) Octal(arg ref.Val, locale string) (string, error) {
switch arg.Type() {
case types.IntType:
argInt := arg.Value().(int64)
return fmt.Sprintf("%o", argInt), nil
case types.UintType:
argInt := arg.Value().(uint64)
return fmt.Sprintf("%o", argInt), nil
default:
return "", octalFormatError(runtimeID, arg.Type().TypeName())
}
}
// stringFormatValidator implements the cel.ASTValidator interface allowing for static validation
// of string.format calls.
type stringFormatValidator struct{}
// Name returns the name of the validator.
func (stringFormatValidator) Name() string {
return "cel.lib.ext.validate.functions.string.format"
}
// Configure implements the ASTValidatorConfigurer interface and augments the list of functions to skip
// during homogeneous aggregate literal type-checks.
func (stringFormatValidator) Configure(config cel.MutableValidatorConfig) error {
functions := config.GetOrDefault(cel.HomogeneousAggregateLiteralExemptFunctions, []string{}).([]string)
functions = append(functions, "format")
return config.Set(cel.HomogeneousAggregateLiteralExemptFunctions, functions)
}
// Validate parses all literal format strings and type checks the format clause against the argument
// at the corresponding ordinal within the list literal argument to the function, if one is specified.
func (stringFormatValidator) Validate(env *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) {
root := ast.NavigateAST(a)
formatCallExprs := ast.MatchDescendants(root, matchConstantFormatStringWithListLiteralArgs(a))
for _, e := range formatCallExprs {
call := e.AsCall()
formatStr := call.Target().AsLiteral().Value().(string)
args := call.Args()[0].AsList().Elements()
formatCheck := &stringFormatChecker{
args: args,
ast: a,
}
// use a placeholder locale, since locale doesn't affect syntax
_, err := parseFormatString(formatStr, formatCheck, formatCheck, "en_US")
if err != nil {
iss.ReportErrorAtID(getErrorExprID(e.ID(), err), err.Error())
continue
}
seenArgs := formatCheck.argsRequested
if len(args) > seenArgs {
iss.ReportErrorAtID(e.ID(),
"too many arguments supplied to string.format (expected %d, got %d)", seenArgs, len(args))
}
}
}
// getErrorExprID determines which list literal argument triggered a type-disagreement for the
// purposes of more accurate error message reports.
func getErrorExprID(id int64, err error) int64 {
fmtErr, ok := err.(formatError)
if ok {
return fmtErr.id
}
wrapped := errors.Unwrap(err)
if wrapped != nil {
return getErrorExprID(id, wrapped)
}
return id
}
// matchConstantFormatStringWithListLiteralArgs matches all valid expression nodes for string
// format checking.
func matchConstantFormatStringWithListLiteralArgs(a *ast.AST) ast.ExprMatcher {
return func(e ast.NavigableExpr) bool {
if e.Kind() != ast.CallKind {
return false
}
call := e.AsCall()
if !call.IsMemberFunction() || call.FunctionName() != "format" {
return false
}
overloadIDs := a.GetOverloadIDs(e.ID())
if len(overloadIDs) != 0 {
found := false
for _, overload := range overloadIDs {
if overload == overloads.ExtFormatString {
found = true
break
}
}
if !found {
return false
}
}
formatString := call.Target()
if formatString.Kind() != ast.LiteralKind || formatString.AsLiteral().Type() != cel.StringType {
return false
}
args := call.Args()
if len(args) != 1 {
return false
}
formatArgs := args[0]
return formatArgs.Kind() == ast.ListKind
}
}
// stringFormatChecker implements the formatStringInterpolater interface
type stringFormatChecker struct {
args []ast.Expr
argsRequested int
currArgIndex int64
ast *ast.AST
}
func (c *stringFormatChecker) String(arg ref.Val, locale string) (string, error) {
formatArg := c.args[c.currArgIndex]
valid, badID := c.verifyString(formatArg)
if !valid {
return "", stringFormatError(badID, c.typeOf(badID).TypeName())
}
return "", nil
}
func (c *stringFormatChecker) Decimal(arg ref.Val, locale string) (string, error) {
id := c.args[c.currArgIndex].ID()
valid := c.verifyTypeOneOf(id, types.IntType, types.UintType)
if !valid {
return "", decimalFormatError(id, c.typeOf(id).TypeName())
}
return "", nil
}
func (c *stringFormatChecker) Fixed(precision *int) func(ref.Val, string) (string, error) {
return func(arg ref.Val, locale string) (string, error) {
id := c.args[c.currArgIndex].ID()
// we allow StringType since "NaN", "Infinity", and "-Infinity" are also valid values
valid := c.verifyTypeOneOf(id, types.DoubleType, types.StringType)
if !valid {
return "", fixedPointFormatError(id, c.typeOf(id).TypeName())
}
return "", nil
}
}
func (c *stringFormatChecker) Scientific(precision *int) func(ref.Val, string) (string, error) {
return func(arg ref.Val, locale string) (string, error) {
id := c.args[c.currArgIndex].ID()
valid := c.verifyTypeOneOf(id, types.DoubleType, types.StringType)
if !valid {
return "", scientificFormatError(id, c.typeOf(id).TypeName())
}
return "", nil
}
}
func (c *stringFormatChecker) Binary(arg ref.Val, locale string) (string, error) {
id := c.args[c.currArgIndex].ID()
valid := c.verifyTypeOneOf(id, types.IntType, types.UintType, types.BoolType)
if !valid {
return "", binaryFormatError(id, c.typeOf(id).TypeName())
}
return "", nil
}
func (c *stringFormatChecker) Hex(useUpper bool) func(ref.Val, string) (string, error) {
return func(arg ref.Val, locale string) (string, error) {
id := c.args[c.currArgIndex].ID()
valid := c.verifyTypeOneOf(id, types.IntType, types.UintType, types.StringType, types.BytesType)
if !valid {
return "", hexFormatError(id, c.typeOf(id).TypeName())
}
return "", nil
}
}
func (c *stringFormatChecker) Octal(arg ref.Val, locale string) (string, error) {
id := c.args[c.currArgIndex].ID()
valid := c.verifyTypeOneOf(id, types.IntType, types.UintType)
if !valid {
return "", octalFormatError(id, c.typeOf(id).TypeName())
}
return "", nil
}
func (c *stringFormatChecker) Arg(index int64) (ref.Val, error) {
c.argsRequested++
c.currArgIndex = index
// return a dummy value - this is immediately passed to back to us
// through one of the FormatCallback functions, so anything will do
return types.Int(0), nil
}
func (c *stringFormatChecker) Size() int64 {
return int64(len(c.args))
}
func (c *stringFormatChecker) typeOf(id int64) *cel.Type {
return c.ast.GetType(id)
}
func (c *stringFormatChecker) verifyTypeOneOf(id int64, validTypes ...*cel.Type) bool {
t := c.typeOf(id)
if t == cel.DynType {
return true
}
for _, vt := range validTypes {
// Only check runtime type compatibility without delving deeper into parameterized types
if t.Kind() == vt.Kind() {
return true
}
}
return false
}
func (c *stringFormatChecker) verifyString(sub ast.Expr) (bool, int64) {
paramA := cel.TypeParamType("A")
paramB := cel.TypeParamType("B")
subVerified := c.verifyTypeOneOf(sub.ID(),
cel.ListType(paramA), cel.MapType(paramA, paramB),
cel.IntType, cel.UintType, cel.DoubleType, cel.BoolType, cel.StringType,
cel.TimestampType, cel.BytesType, cel.DurationType, cel.TypeType, cel.NullType)
if !subVerified {
return false, sub.ID()
}
switch sub.Kind() {
case ast.ListKind:
for _, e := range sub.AsList().Elements() {
// recursively verify if we're dealing with a list/map
verified, id := c.verifyString(e)
if !verified {
return false, id
}
}
return true, sub.ID()
case ast.MapKind:
for _, e := range sub.AsMap().Entries() {
// recursively verify if we're dealing with a list/map
entry := e.AsMapEntry()
verified, id := c.verifyString(entry.Key())
if !verified {
return false, id
}
verified, id = c.verifyString(entry.Value())
if !verified {
return false, id
}
}
return true, sub.ID()
default:
return true, sub.ID()
}
}
// helper routines for reporting common errors during string formatting static validation and
// runtime execution.
func binaryFormatError(id int64, badType string) error {
return newFormatError(id, "only integers and bools can be formatted as binary, was given %s", badType)
}
func decimalFormatError(id int64, badType string) error {
return newFormatError(id, "decimal clause can only be used on integers, was given %s", badType)
}
func fixedPointFormatError(id int64, badType string) error {
return newFormatError(id, "fixed-point clause can only be used on doubles, was given %s", badType)
}
func hexFormatError(id int64, badType string) error {
return newFormatError(id, "only integers, byte buffers, and strings can be formatted as hex, was given %s", badType)
}
func octalFormatError(id int64, badType string) error {
return newFormatError(id, "octal clause can only be used on integers, was given %s", badType)
}
func scientificFormatError(id int64, badType string) error {
return newFormatError(id, "scientific clause can only be used on doubles, was given %s", badType)
}
func stringFormatError(id int64, badType string) error {
return newFormatError(id, "string clause can only be used on strings, bools, bytes, ints, doubles, maps, lists, types, durations, and timestamps, was given %s", badType)
}
type formatError struct {
id int64
msg string
}
func newFormatError(id int64, msg string, args ...any) error {
return formatError{
id: id,
msg: fmt.Sprintf(msg, args...),
}
}
func (e formatError) Error() string {
return e.msg
}
func (e formatError) Is(target error) bool {
return e.msg == target.Error()
}
// stringArgList implements the formatListArgs interface.
type stringArgList struct {
args traits.Lister
}
func (c *stringArgList) Arg(index int64) (ref.Val, error) {
if index >= c.args.Size().Value().(int64) {
return nil, fmt.Errorf("index %d out of range", index)
}
return c.args.Get(types.Int(index)), nil
}
func (c *stringArgList) Size() int64 {
return c.args.Size().Value().(int64)
}
// formatStringInterpolator is an interface that allows user-defined behavior
// for formatting clause implementations, as well as argument retrieval.
// Each function is expected to support the appropriate types as laid out in
// the string.format documentation, and to return an error if given an inappropriate type.
type formatStringInterpolator interface {
// String takes a ref.Val and a string representing the current locale identifier
// and returns the Val formatted as a string, or an error if one occurred.
String(ref.Val, string) (string, error)
// Decimal takes a ref.Val and a string representing the current locale identifier
// and returns the Val formatted as a decimal integer, or an error if one occurred.
Decimal(ref.Val, string) (string, error)
// Fixed takes an int pointer representing precision (or nil if none was given) and
// returns a function operating in a similar manner to String and Decimal, taking a
// ref.Val and locale and returning the appropriate string. A closure is returned
// so precision can be set without needing an additional function call/configuration.
Fixed(*int) func(ref.Val, string) (string, error)
// Scientific functions identically to Fixed, except the string returned from the closure
// is expected to be in scientific notation.
Scientific(*int) func(ref.Val, string) (string, error)
// Binary takes a ref.Val and a string representing the current locale identifier
// and returns the Val formatted as a binary integer, or an error if one occurred.
Binary(ref.Val, string) (string, error)
// Hex takes a boolean that, if true, indicates the hex string output by the returned
// closure should use uppercase letters for A-F.
Hex(bool) func(ref.Val, string) (string, error)
// Octal takes a ref.Val and a string representing the current locale identifier and
// returns the Val formatted in octal, or an error if one occurred.
Octal(ref.Val, string) (string, error)
}
// formatListArgs is an interface that allows user-defined list-like datatypes to be used
// for formatting clause implementations.
type formatListArgs interface {
// Arg returns the ref.Val at the given index, or an error if one occurred.
Arg(int64) (ref.Val, error)
// Size returns the length of the argument list.
Size() int64
}
// parseFormatString formats a string according to the string.format syntax, taking the clause implementations
// from the provided FormatCallback and the args from the given FormatList.
func parseFormatString(formatStr string, callback formatStringInterpolator, list formatListArgs, locale string) (string, error) {
i := 0
argIndex := 0
var builtStr strings.Builder
for i < len(formatStr) {
if formatStr[i] == '%' {
if i+1 < len(formatStr) && formatStr[i+1] == '%' {
err := builtStr.WriteByte('%')
if err != nil {
return "", fmt.Errorf("error writing format string: %w", err)
}
i += 2
continue
} else {
argAny, err := list.Arg(int64(argIndex))
if err != nil {
return "", err
}
if i+1 >= len(formatStr) {
return "", errors.New("unexpected end of string")
}
if int64(argIndex) >= list.Size() {
return "", fmt.Errorf("index %d out of range", argIndex)
}
numRead, val, refErr := parseAndFormatClause(formatStr[i:], argAny, callback, list, locale)
if refErr != nil {
return "", refErr
}
_, err = builtStr.WriteString(val)
if err != nil {
return "", fmt.Errorf("error writing format string: %w", err)
}
i += numRead
argIndex++
}
} else {
err := builtStr.WriteByte(formatStr[i])
if err != nil {
return "", fmt.Errorf("error writing format string: %w", err)
}
i++
}
}
return builtStr.String(), nil
}
// parseAndFormatClause parses the format clause at the start of the given string with val, and returns
// how many characters were consumed and the substituted string form of val, or an error if one occurred.
func parseAndFormatClause(formatStr string, val ref.Val, callback formatStringInterpolator, list formatListArgs, locale string) (int, string, error) {
i := 1
read, formatter, err := parseFormattingClause(formatStr[i:], callback)
i += read
if err != nil {
return -1, "", newParseFormatError("could not parse formatting clause", err)
}
valStr, err := formatter(val, locale)
if err != nil {
return -1, "", newParseFormatError("error during formatting", err)
}
return i, valStr, nil
}
func parseFormattingClause(formatStr string, callback formatStringInterpolator) (int, clauseImpl, error) {
i := 0
read, precision, err := parsePrecision(formatStr[i:])
i += read
if err != nil {
return -1, nil, fmt.Errorf("error while parsing precision: %w", err)
}
r := rune(formatStr[i])
i++
switch r {
case 's':
return i, callback.String, nil
case 'd':
return i, callback.Decimal, nil
case 'f':
return i, callback.Fixed(precision), nil
case 'e':
return i, callback.Scientific(precision), nil
case 'b':
return i, callback.Binary, nil
case 'x', 'X':
return i, callback.Hex(unicode.IsUpper(r)), nil
case 'o':
return i, callback.Octal, nil
default:
return -1, nil, fmt.Errorf("unrecognized formatting clause \"%c\"", r)
}
}
func parsePrecision(formatStr string) (int, *int, error) {
i := 0
if formatStr[i] != '.' {
return i, nil, nil
}
i++
var buffer strings.Builder
for {
if i >= len(formatStr) {
return -1, nil, errors.New("could not find end of precision specifier")
}
if !isASCIIDigit(rune(formatStr[i])) {
break
}
buffer.WriteByte(formatStr[i])
i++
}
precision, err := strconv.Atoi(buffer.String())
if err != nil {
return -1, nil, fmt.Errorf("error while converting precision to integer: %w", err)
}
return i, &precision, nil
}
func isASCIIDigit(r rune) bool {
return r <= unicode.MaxASCII && unicode.IsDigit(r)
}
type parseFormatError struct {
msg string
wrapped error
}
func newParseFormatError(msg string, wrapped error) error {
return parseFormatError{msg: msg, wrapped: wrapped}
}
func (e parseFormatError) Error() string {
return fmt.Sprintf("%s: %s", e.msg, e.wrapped.Error())
}
func (e parseFormatError) Is(target error) bool {
return e.Error() == target.Error()
}
func (e parseFormatError) Unwrap() error {
return e.wrapped
}
const (
runtimeID = int64(-1)
)

67
e2e/vendor/github.com/google/cel-go/ext/guards.go generated vendored Normal file
View File

@ -0,0 +1,67 @@
// 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 ext
import (
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// function invocation guards for common call signatures within extension functions.
func intOrError(i int64, err error) ref.Val {
if err != nil {
return types.NewErr(err.Error())
}
return types.Int(i)
}
func bytesOrError(bytes []byte, err error) ref.Val {
if err != nil {
return types.NewErr(err.Error())
}
return types.Bytes(bytes)
}
func stringOrError(str string, err error) ref.Val {
if err != nil {
return types.NewErr(err.Error())
}
return types.String(str)
}
func listStringOrError(strs []string, err error) ref.Val {
if err != nil {
return types.NewErr(err.Error())
}
return types.DefaultTypeAdapter.NativeToValue(strs)
}
func extractIdent(target ast.Expr) (string, bool) {
switch target.Kind() {
case ast.IdentKind:
return target.AsIdent(), true
default:
return "", false
}
}
func macroTargetMatchesNamespace(ns string, target ast.Expr) bool {
if id, found := extractIdent(target); found {
return id == ns
}
return false
}

560
e2e/vendor/github.com/google/cel-go/ext/lists.go generated vendored Normal file
View File

@ -0,0 +1,560 @@
// Copyright 2023 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 ext
import (
"fmt"
"math"
"sort"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/decls"
"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/parser"
)
var comparableTypes = []*cel.Type{
cel.IntType,
cel.UintType,
cel.DoubleType,
cel.BoolType,
cel.DurationType,
cel.TimestampType,
cel.StringType,
cel.BytesType,
}
// Lists returns a cel.EnvOption to configure extended functions for list manipulation.
// As a general note, all indices are zero-based.
//
// # Distinct
//
// Introduced in version: 2
//
// Returns the distinct elements of a list.
//
// <list(T)>.distinct() -> <list(T)>
//
// Examples:
//
// [1, 2, 2, 3, 3, 3].distinct() // return [1, 2, 3]
// ["b", "b", "c", "a", "c"].distinct() // return ["b", "c", "a"]
// [1, "b", 2, "b"].distinct() // return [1, "b", 2]
//
// # Range
//
// Introduced in version: 2
//
// Returns a list of integers from 0 to n-1.
//
// lists.range(<int>) -> <list(int)>
//
// Examples:
//
// lists.range(5) -> [0, 1, 2, 3, 4]
//
// # Reverse
//
// Introduced in version: 2
//
// Returns the elements of a list in reverse order.
//
// <list(T)>.reverse() -> <list(T)>
//
// Examples:
//
// [5, 3, 1, 2].reverse() // return [2, 1, 3, 5]
//
// # Slice
//
// Returns a new sub-list using the indexes provided.
//
// <list>.slice(<int>, <int>) -> <list>
//
// Examples:
//
// [1,2,3,4].slice(1, 3) // return [2, 3]
// [1,2,3,4].slice(2, 4) // return [3 ,4]
//
// # Flatten
//
// Flattens a list recursively.
// If an optional depth is provided, the list is flattened to a the specificied level.
// A negative depth value will result in an error.
//
// <list>.flatten(<list>) -> <list>
// <list>.flatten(<list>, <int>) -> <list>
//
// Examples:
//
// [1,[2,3],[4]].flatten() // return [1, 2, 3, 4]
// [1,[2,[3,4]]].flatten() // return [1, 2, [3, 4]]
// [1,2,[],[],[3,4]].flatten() // return [1, 2, 3, 4]
// [1,[2,[3,[4]]]].flatten(2) // return [1, 2, 3, [4]]
// [1,[2,[3,[4]]]].flatten(-1) // error
//
// # Sort
//
// Introduced in version: 2
//
// Sorts a list with comparable elements. If the element type is not comparable
// or the element types are not the same, the function will produce an error.
//
// <list(T)>.sort() -> <list(T)>
// T in {int, uint, double, bool, duration, timestamp, string, bytes}
//
// Examples:
//
// [3, 2, 1].sort() // return [1, 2, 3]
// ["b", "c", "a"].sort() // return ["a", "b", "c"]
// [1, "b"].sort() // error
// [[1, 2, 3]].sort() // error
//
// # SortBy
//
// Sorts a list by a key value, i.e., the order is determined by the result of
// an expression applied to each element of the list.
// The output of the key expression must be a comparable type, otherwise the
// function will return an error.
//
// <list(T)>.sortBy(<bindingName>, <keyExpr>) -> <list(T)>
// keyExpr returns a value in {int, uint, double, bool, duration, timestamp, string, bytes}
// Examples:
//
// [
// Player { name: "foo", score: 0 },
// Player { name: "bar", score: -10 },
// Player { name: "baz", score: 1000 },
// ].sortBy(e, e.score).map(e, e.name)
// == ["bar", "foo", "baz"]
func Lists(options ...ListsOption) cel.EnvOption {
l := &listsLib{
version: math.MaxUint32,
}
for _, o := range options {
l = o(l)
}
return cel.Lib(l)
}
type listsLib struct {
version uint32
}
// LibraryName implements the SingletonLibrary interface method.
func (listsLib) LibraryName() string {
return "cel.lib.ext.lists"
}
// ListsOption is a functional interface for configuring the strings library.
type ListsOption func(*listsLib) *listsLib
// ListsVersion configures the version of the string 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 ListsVersion(version uint32) ListsOption {
return func(lib *listsLib) *listsLib {
lib.version = version
return lib
}
}
// CompileOptions implements the Library interface method.
func (lib listsLib) CompileOptions() []cel.EnvOption {
listType := cel.ListType(cel.TypeParamType("T"))
listListType := cel.ListType(listType)
listDyn := cel.ListType(cel.DynType)
opts := []cel.EnvOption{
cel.Function("slice",
cel.MemberOverload("list_slice",
[]*cel.Type{listType, cel.IntType, cel.IntType}, listType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
list := args[0].(traits.Lister)
start := args[1].(types.Int)
end := args[2].(types.Int)
result, err := slice(list, start, end)
if err != nil {
return types.WrapErr(err)
}
return result
}),
),
),
}
if lib.version >= 1 {
opts = append(opts,
cel.Function("flatten",
cel.MemberOverload("list_flatten",
[]*cel.Type{listListType}, listType,
cel.UnaryBinding(func(arg ref.Val) ref.Val {
list, ok := arg.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
flatList, err := flatten(list, 1)
if err != nil {
return types.WrapErr(err)
}
return types.DefaultTypeAdapter.NativeToValue(flatList)
}),
),
cel.MemberOverload("list_flatten_int",
[]*cel.Type{listDyn, types.IntType}, listDyn,
cel.BinaryBinding(func(arg1, arg2 ref.Val) ref.Val {
list, ok := arg1.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
depth, ok := arg2.(types.Int)
if !ok {
return types.MaybeNoSuchOverloadErr(arg2)
}
flatList, err := flatten(list, int64(depth))
if err != nil {
return types.WrapErr(err)
}
return types.DefaultTypeAdapter.NativeToValue(flatList)
}),
),
// To handle the case where a variable of just `list(T)` is provided at runtime
// with a graceful failure more, disable the type guards since the implementation
// can handle lists which are already flat.
decls.DisableTypeGuards(true),
),
)
}
if lib.version >= 2 {
sortDecl := cel.Function("sort",
append(
templatedOverloads(comparableTypes, func(t *cel.Type) cel.FunctionOpt {
return cel.MemberOverload(
fmt.Sprintf("list_%s_sort", t.TypeName()),
[]*cel.Type{cel.ListType(t)}, cel.ListType(t),
)
}),
cel.SingletonUnaryBinding(
func(arg ref.Val) ref.Val {
list, ok := arg.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
sorted, err := sortList(list)
if err != nil {
return types.WrapErr(err)
}
return sorted
},
// List traits
traits.ListerType,
),
)...,
)
opts = append(opts, sortDecl)
opts = append(opts, cel.Macros(cel.ReceiverMacro("sortBy", 2, sortByMacro)))
opts = append(opts, cel.Function("@sortByAssociatedKeys",
append(
templatedOverloads(comparableTypes, func(u *cel.Type) cel.FunctionOpt {
return cel.MemberOverload(
fmt.Sprintf("list_%s_sortByAssociatedKeys", u.TypeName()),
[]*cel.Type{listType, cel.ListType(u)}, listType,
)
}),
cel.SingletonBinaryBinding(
func(arg1 ref.Val, arg2 ref.Val) ref.Val {
list, ok := arg1.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
keys, ok := arg2.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(arg2)
}
sorted, err := sortListByAssociatedKeys(list, keys)
if err != nil {
return types.WrapErr(err)
}
return sorted
},
// List traits
traits.ListerType,
),
)...,
))
opts = append(opts, cel.Function("lists.range",
cel.Overload("lists_range",
[]*cel.Type{cel.IntType}, cel.ListType(cel.IntType),
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
n := args[0].(types.Int)
result, err := genRange(n)
if err != nil {
return types.WrapErr(err)
}
return result
}),
),
))
opts = append(opts, cel.Function("reverse",
cel.MemberOverload("list_reverse",
[]*cel.Type{listType}, listType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
list := args[0].(traits.Lister)
result, err := reverseList(list)
if err != nil {
return types.WrapErr(err)
}
return result
}),
),
))
opts = append(opts, cel.Function("distinct",
cel.MemberOverload("list_distinct",
[]*cel.Type{listType}, listType,
cel.UnaryBinding(func(list ref.Val) ref.Val {
result, err := distinctList(list.(traits.Lister))
if err != nil {
return types.WrapErr(err)
}
return result
}),
),
))
}
return opts
}
// ProgramOptions implements the Library interface method.
func (listsLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func genRange(n types.Int) (ref.Val, error) {
var newList []ref.Val
for i := types.Int(0); i < n; i++ {
newList = append(newList, i)
}
return types.DefaultTypeAdapter.NativeToValue(newList), nil
}
func reverseList(list traits.Lister) (ref.Val, error) {
var newList []ref.Val
listLength := list.Size().(types.Int)
for i := types.Int(0); i < listLength; i++ {
val := list.Get(listLength - i - 1)
newList = append(newList, val)
}
return types.DefaultTypeAdapter.NativeToValue(newList), nil
}
func slice(list traits.Lister, start, end types.Int) (ref.Val, error) {
listLength := list.Size().(types.Int)
if start < 0 || end < 0 {
return nil, fmt.Errorf("cannot slice(%d, %d), negative indexes not supported", start, end)
}
if start > end {
return nil, fmt.Errorf("cannot slice(%d, %d), start index must be less than or equal to end index", start, end)
}
if listLength < end {
return nil, fmt.Errorf("cannot slice(%d, %d), list is length %d", start, end, listLength)
}
var newList []ref.Val
for i := types.Int(start); i < end; i++ {
val := list.Get(i)
newList = append(newList, val)
}
return types.DefaultTypeAdapter.NativeToValue(newList), nil
}
func flatten(list traits.Lister, depth int64) ([]ref.Val, error) {
if depth < 0 {
return nil, fmt.Errorf("level must be non-negative")
}
var newList []ref.Val
iter := list.Iterator()
for iter.HasNext() == types.True {
val := iter.Next()
nestedList, isList := val.(traits.Lister)
if !isList || depth == 0 {
newList = append(newList, val)
continue
} else {
flattenedList, err := flatten(nestedList, depth-1)
if err != nil {
return nil, err
}
newList = append(newList, flattenedList...)
}
}
return newList, nil
}
func sortList(list traits.Lister) (ref.Val, error) {
return sortListByAssociatedKeys(list, list)
}
// Internal function used for the implementation of sort() and sortBy().
//
// Sorts a list of arbitrary elements, according to the order produced by sorting
// another list of comparable elements. If the element type of the keys is not
// comparable or the element types are not the same, the function will produce an error.
//
// <list(T)>.@sortByAssociatedKeys(<list(U)>) -> <list(T)>
// U in {int, uint, double, bool, duration, timestamp, string, bytes}
//
// Example:
//
// ["foo", "bar", "baz"].@sortByAssociatedKeys([3, 1, 2]) // return ["bar", "baz", "foo"]
func sortListByAssociatedKeys(list, keys traits.Lister) (ref.Val, error) {
listLength := list.Size().(types.Int)
keysLength := keys.Size().(types.Int)
if listLength != keysLength {
return nil, fmt.Errorf(
"@sortByAssociatedKeys() expected a list of the same size as the associated keys list, but got %d and %d elements respectively",
listLength,
keysLength,
)
}
if listLength == 0 {
return list, nil
}
elem := keys.Get(types.IntZero)
if _, ok := elem.(traits.Comparer); !ok {
return nil, fmt.Errorf("list elements must be comparable")
}
sortedIndices := make([]ref.Val, 0, listLength)
for i := types.IntZero; i < listLength; i++ {
if keys.Get(i).Type() != elem.Type() {
return nil, fmt.Errorf("list elements must have the same type")
}
sortedIndices = append(sortedIndices, i)
}
sort.Slice(sortedIndices, func(i, j int) bool {
iKey := keys.Get(sortedIndices[i])
jKey := keys.Get(sortedIndices[j])
return iKey.(traits.Comparer).Compare(jKey) == types.IntNegOne
})
sorted := make([]ref.Val, 0, listLength)
for _, sortedIdx := range sortedIndices {
sorted = append(sorted, list.Get(sortedIdx))
}
return types.DefaultTypeAdapter.NativeToValue(sorted), nil
}
// sortByMacro transforms an expression like:
//
// mylistExpr.sortBy(e, -math.abs(e))
//
// into something equivalent to:
//
// cel.bind(
// __sortBy_input__,
// myListExpr,
// __sortBy_input__.@sortByAssociatedKeys(__sortBy_input__.map(e, -math.abs(e))
// )
func sortByMacro(meh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
varIdent := meh.NewIdent("@__sortBy_input__")
varName := varIdent.AsIdent()
targetKind := target.Kind()
if targetKind != ast.ListKind &&
targetKind != ast.SelectKind &&
targetKind != ast.IdentKind &&
targetKind != ast.ComprehensionKind && targetKind != ast.CallKind {
return nil, meh.NewError(target.ID(), fmt.Sprintf("sortBy can only be applied to a list, identifier, comprehension, call or select expression"))
}
mapCompr, err := parser.MakeMap(meh, meh.Copy(varIdent), args)
if err != nil {
return nil, err
}
callExpr := meh.NewMemberCall("@sortByAssociatedKeys",
meh.Copy(varIdent),
mapCompr,
)
bindExpr := meh.NewComprehension(
meh.NewList(),
"#unused",
varName,
target,
meh.NewLiteral(types.False),
varIdent,
callExpr,
)
return bindExpr, nil
}
func distinctList(list traits.Lister) (ref.Val, error) {
listLength := list.Size().(types.Int)
if listLength == 0 {
return list, nil
}
uniqueList := make([]ref.Val, 0, listLength)
for i := types.IntZero; i < listLength; i++ {
val := list.Get(i)
seen := false
for j := types.IntZero; j < types.Int(len(uniqueList)); j++ {
if i == j {
continue
}
other := uniqueList[j]
if val.Equal(other) == types.True {
seen = true
break
}
}
if !seen {
uniqueList = append(uniqueList, val)
}
}
return types.DefaultTypeAdapter.NativeToValue(uniqueList), nil
}
func templatedOverloads(types []*cel.Type, template func(t *cel.Type) cel.FunctionOpt) []cel.FunctionOpt {
overloads := make([]cel.FunctionOpt, len(types))
for i, t := range types {
overloads[i] = template(t)
}
return overloads
}

901
e2e/vendor/github.com/google/cel-go/ext/math.go generated vendored Normal file
View File

@ -0,0 +1,901 @@
// Copyright 2022 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 ext
import (
"fmt"
"math"
"strings"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
)
// Math returns a cel.EnvOption to configure namespaced math helper macros and
// functions.
//
// Note, all macros use the 'math' namespace; however, at the time of macro
// expansion the namespace looks just like any other identifier. If you are
// currently using a variable named 'math', the macro will likely work just as
// intended; however, there is some chance for collision.
//
// # Math.Greatest
//
// Returns the greatest valued number present in the arguments to the macro.
//
// Greatest is a variable argument count macro which must take at least one
// argument. Simple numeric and list literals are supported as valid argument
// types; however, other literals will be flagged as errors during macro
// expansion. If the argument expression does not resolve to a numeric or
// list(numeric) type during type-checking, or during runtime then an error
// will be produced. If a list argument is empty, this too will produce an
// error.
//
// math.greatest(<arg>, ...) -> <double|int|uint>
//
// Examples:
//
// math.greatest(1) // 1
// math.greatest(1u, 2u) // 2u
// math.greatest(-42.0, -21.5, -100.0) // -21.5
// math.greatest([-42.0, -21.5, -100.0]) // -21.5
// math.greatest(numbers) // numbers must be list(numeric)
//
// math.greatest() // parse error
// math.greatest('string') // parse error
// math.greatest(a, b) // check-time error if a or b is non-numeric
// math.greatest(dyn('string')) // runtime error
//
// # Math.Least
//
// Returns the least valued number present in the arguments to the macro.
//
// Least is a variable argument count macro which must take at least one
// argument. Simple numeric and list literals are supported as valid argument
// types; however, other literals will be flagged as errors during macro
// expansion. If the argument expression does not resolve to a numeric or
// list(numeric) type during type-checking, or during runtime then an error
// will be produced. If a list argument is empty, this too will produce an
// error.
//
// math.least(<arg>, ...) -> <double|int|uint>
//
// Examples:
//
// math.least(1) // 1
// math.least(1u, 2u) // 1u
// math.least(-42.0, -21.5, -100.0) // -100.0
// math.least([-42.0, -21.5, -100.0]) // -100.0
// math.least(numbers) // numbers must be list(numeric)
//
// math.least() // parse error
// math.least('string') // parse error
// math.least(a, b) // check-time error if a or b is non-numeric
// math.least(dyn('string')) // runtime error
//
// # Math.BitOr
//
// Introduced at version: 1
//
// Performs a bitwise-OR operation over two int or uint values.
//
// math.bitOr(<int>, <int>) -> <int>
// math.bitOr(<uint>, <uint>) -> <uint>
//
// Examples:
//
// math.bitOr(1u, 2u) // returns 3u
// math.bitOr(-2, -4) // returns -2
//
// # Math.BitAnd
//
// Introduced at version: 1
//
// Performs a bitwise-AND operation over two int or uint values.
//
// math.bitAnd(<int>, <int>) -> <int>
// math.bitAnd(<uint>, <uint>) -> <uint>
//
// Examples:
//
// math.bitAnd(3u, 2u) // return 2u
// math.bitAnd(3, 5) // returns 3
// math.bitAnd(-3, -5) // returns -7
//
// # Math.BitXor
//
// Introduced at version: 1
//
// math.bitXor(<int>, <int>) -> <int>
// math.bitXor(<uint>, <uint>) -> <uint>
//
// Performs a bitwise-XOR operation over two int or uint values.
//
// Examples:
//
// math.bitXor(3u, 5u) // returns 6u
// math.bitXor(1, 3) // returns 2
//
// # Math.BitNot
//
// Introduced at version: 1
//
// Function which accepts a single int or uint and performs a bitwise-NOT
// ones-complement of the given binary value.
//
// math.bitNot(<int>) -> <int>
// math.bitNot(<uint>) -> <uint>
//
// Examples
//
// math.bitNot(1) // returns -1
// math.bitNot(-1) // return 0
// math.bitNot(0u) // returns 18446744073709551615u
//
// # Math.BitShiftLeft
//
// Introduced at version: 1
//
// Perform a left shift of bits on the first parameter, by the amount of bits
// specified in the second parameter. The first parameter is either a uint or
// an int. The second parameter must be an int.
//
// When the second parameter is 64 or greater, 0 will be always be returned
// since the number of bits shifted is greater than or equal to the total bit
// length of the number being shifted. Negative valued bit shifts will result
// in a runtime error.
//
// math.bitShiftLeft(<int>, <int>) -> <int>
// math.bitShiftLeft(<uint>, <int>) -> <uint>
//
// Examples
//
// math.bitShiftLeft(1, 2) // returns 4
// math.bitShiftLeft(-1, 2) // returns -4
// math.bitShiftLeft(1u, 2) // return 4u
// math.bitShiftLeft(1u, 200) // returns 0u
//
// # Math.BitShiftRight
//
// Introduced at version: 1
//
// Perform a right shift of bits on the first parameter, by the amount of bits
// specified in the second parameter. The first parameter is either a uint or
// an int. The second parameter must be an int.
//
// When the second parameter is 64 or greater, 0 will always be returned since
// the number of bits shifted is greater than or equal to the total bit length
// of the number being shifted. Negative valued bit shifts will result in a
// runtime error.
//
// The sign bit extension will not be preserved for this operation: vacant bits
// on the left are filled with 0.
//
// math.bitShiftRight(<int>, <int>) -> <int>
// math.bitShiftRight(<uint>, <int>) -> <uint>
//
// Examples
//
// math.bitShiftRight(1024, 2) // returns 256
// math.bitShiftRight(1024u, 2) // returns 256u
// math.bitShiftRight(1024u, 64) // returns 0u
//
// # Math.Ceil
//
// Introduced at version: 1
//
// Compute the ceiling of a double value.
//
// math.ceil(<double>) -> <double>
//
// Examples:
//
// math.ceil(1.2) // returns 2.0
// math.ceil(-1.2) // returns -1.0
//
// # Math.Floor
//
// Introduced at version: 1
//
// Compute the floor of a double value.
//
// math.floor(<double>) -> <double>
//
// Examples:
//
// math.floor(1.2) // returns 1.0
// math.floor(-1.2) // returns -2.0
//
// # Math.Round
//
// Introduced at version: 1
//
// Rounds the double value to the nearest whole number with ties rounding away
// from zero, e.g. 1.5 -> 2.0, -1.5 -> -2.0.
//
// math.round(<double>) -> <double>
//
// Examples:
//
// math.round(1.2) // returns 1.0
// math.round(1.5) // returns 2.0
// math.round(-1.5) // returns -2.0
//
// # Math.Trunc
//
// Introduced at version: 1
//
// Truncates the fractional portion of the double value.
//
// math.trunc(<double>) -> <double>
//
// Examples:
//
// math.trunc(-1.3) // returns -1.0
// math.trunc(1.3) // returns 1.0
//
// # Math.Abs
//
// Introduced at version: 1
//
// Returns the absolute value of the numeric type provided as input. If the
// value is NaN, the output is NaN. If the input is int64 min, the function
// will result in an overflow error.
//
// math.abs(<double>) -> <double>
// math.abs(<int>) -> <int>
// math.abs(<uint>) -> <uint>
//
// Examples:
//
// math.abs(-1) // returns 1
// math.abs(1) // returns 1
// math.abs(-9223372036854775808) // overflow error
//
// # Math.Sign
//
// Introduced at version: 1
//
// Returns the sign of the numeric type, either -1, 0, 1 as an int, double, or
// uint depending on the overload. For floating point values, if NaN is
// provided as input, the output is also NaN. The implementation does not
// differentiate between positive and negative zero.
//
// math.sign(<double>) -> <double>
// math.sign(<int>) -> <int>
// math.sign(<uint>) -> <uint>
//
// Examples:
//
// math.sign(-42) // returns -1
// math.sign(0) // returns 0
// math.sign(42) // returns 1
//
// # Math.IsInf
//
// Introduced at version: 1
//
// Returns true if the input double value is -Inf or +Inf.
//
// math.isInf(<double>) -> <bool>
//
// Examples:
//
// math.isInf(1.0/0.0) // returns true
// math.isInf(1.2) // returns false
//
// # Math.IsNaN
//
// Introduced at version: 1
//
// Returns true if the input double value is NaN, false otherwise.
//
// math.isNaN(<double>) -> <bool>
//
// Examples:
//
// math.isNaN(0.0/0.0) // returns true
// math.isNaN(1.2) // returns false
//
// # Math.IsFinite
//
// Introduced at version: 1
//
// Returns true if the value is a finite number. Equivalent in behavior to:
// !math.isNaN(double) && !math.isInf(double)
//
// math.isFinite(<double>) -> <bool>
//
// Examples:
//
// math.isFinite(0.0/0.0) // returns false
// math.isFinite(1.2) // returns true
func Math(options ...MathOption) cel.EnvOption {
m := &mathLib{version: math.MaxUint32}
for _, o := range options {
m = o(m)
}
return cel.Lib(m)
}
const (
mathNamespace = "math"
leastMacro = "least"
greatestMacro = "greatest"
// Min-max functions
minFunc = "math.@min"
maxFunc = "math.@max"
// Rounding functions
ceilFunc = "math.ceil"
floorFunc = "math.floor"
roundFunc = "math.round"
truncFunc = "math.trunc"
// Floating point helper functions
isInfFunc = "math.isInf"
isNanFunc = "math.isNaN"
isFiniteFunc = "math.isFinite"
// Signedness functions
absFunc = "math.abs"
signFunc = "math.sign"
// Bitwise functions
bitAndFunc = "math.bitAnd"
bitOrFunc = "math.bitOr"
bitXorFunc = "math.bitXor"
bitNotFunc = "math.bitNot"
bitShiftLeftFunc = "math.bitShiftLeft"
bitShiftRightFunc = "math.bitShiftRight"
)
var (
errIntOverflow = types.NewErr("integer overflow")
)
// MathOption declares a functional operator for configuring math extensions.
type MathOption func(*mathLib) *mathLib
// MathVersion sets the library version for math extensions.
func MathVersion(version uint32) MathOption {
return func(lib *mathLib) *mathLib {
lib.version = version
return lib
}
}
type mathLib struct {
version uint32
}
// LibraryName implements the SingletonLibrary interface method.
func (*mathLib) LibraryName() string {
return "cel.lib.ext.math"
}
// CompileOptions implements the Library interface method.
func (lib *mathLib) CompileOptions() []cel.EnvOption {
opts := []cel.EnvOption{
cel.Macros(
// math.least(num, ...)
cel.ReceiverVarArgMacro(leastMacro, mathLeast),
// math.greatest(num, ...)
cel.ReceiverVarArgMacro(greatestMacro, mathGreatest),
),
cel.Function(minFunc,
cel.Overload("math_@min_double", []*cel.Type{cel.DoubleType}, cel.DoubleType,
cel.UnaryBinding(identity)),
cel.Overload("math_@min_int", []*cel.Type{cel.IntType}, cel.IntType,
cel.UnaryBinding(identity)),
cel.Overload("math_@min_uint", []*cel.Type{cel.UintType}, cel.UintType,
cel.UnaryBinding(identity)),
cel.Overload("math_@min_double_double", []*cel.Type{cel.DoubleType, cel.DoubleType}, cel.DoubleType,
cel.BinaryBinding(minPair)),
cel.Overload("math_@min_int_int", []*cel.Type{cel.IntType, cel.IntType}, cel.IntType,
cel.BinaryBinding(minPair)),
cel.Overload("math_@min_uint_uint", []*cel.Type{cel.UintType, cel.UintType}, cel.UintType,
cel.BinaryBinding(minPair)),
cel.Overload("math_@min_int_uint", []*cel.Type{cel.IntType, cel.UintType}, cel.DynType,
cel.BinaryBinding(minPair)),
cel.Overload("math_@min_int_double", []*cel.Type{cel.IntType, cel.DoubleType}, cel.DynType,
cel.BinaryBinding(minPair)),
cel.Overload("math_@min_double_int", []*cel.Type{cel.DoubleType, cel.IntType}, cel.DynType,
cel.BinaryBinding(minPair)),
cel.Overload("math_@min_double_uint", []*cel.Type{cel.DoubleType, cel.UintType}, cel.DynType,
cel.BinaryBinding(minPair)),
cel.Overload("math_@min_uint_int", []*cel.Type{cel.UintType, cel.IntType}, cel.DynType,
cel.BinaryBinding(minPair)),
cel.Overload("math_@min_uint_double", []*cel.Type{cel.UintType, cel.DoubleType}, cel.DynType,
cel.BinaryBinding(minPair)),
cel.Overload("math_@min_list_double", []*cel.Type{cel.ListType(cel.DoubleType)}, cel.DoubleType,
cel.UnaryBinding(minList)),
cel.Overload("math_@min_list_int", []*cel.Type{cel.ListType(cel.IntType)}, cel.IntType,
cel.UnaryBinding(minList)),
cel.Overload("math_@min_list_uint", []*cel.Type{cel.ListType(cel.UintType)}, cel.UintType,
cel.UnaryBinding(minList)),
),
cel.Function(maxFunc,
cel.Overload("math_@max_double", []*cel.Type{cel.DoubleType}, cel.DoubleType,
cel.UnaryBinding(identity)),
cel.Overload("math_@max_int", []*cel.Type{cel.IntType}, cel.IntType,
cel.UnaryBinding(identity)),
cel.Overload("math_@max_uint", []*cel.Type{cel.UintType}, cel.UintType,
cel.UnaryBinding(identity)),
cel.Overload("math_@max_double_double", []*cel.Type{cel.DoubleType, cel.DoubleType}, cel.DoubleType,
cel.BinaryBinding(maxPair)),
cel.Overload("math_@max_int_int", []*cel.Type{cel.IntType, cel.IntType}, cel.IntType,
cel.BinaryBinding(maxPair)),
cel.Overload("math_@max_uint_uint", []*cel.Type{cel.UintType, cel.UintType}, cel.UintType,
cel.BinaryBinding(maxPair)),
cel.Overload("math_@max_int_uint", []*cel.Type{cel.IntType, cel.UintType}, cel.DynType,
cel.BinaryBinding(maxPair)),
cel.Overload("math_@max_int_double", []*cel.Type{cel.IntType, cel.DoubleType}, cel.DynType,
cel.BinaryBinding(maxPair)),
cel.Overload("math_@max_double_int", []*cel.Type{cel.DoubleType, cel.IntType}, cel.DynType,
cel.BinaryBinding(maxPair)),
cel.Overload("math_@max_double_uint", []*cel.Type{cel.DoubleType, cel.UintType}, cel.DynType,
cel.BinaryBinding(maxPair)),
cel.Overload("math_@max_uint_int", []*cel.Type{cel.UintType, cel.IntType}, cel.DynType,
cel.BinaryBinding(maxPair)),
cel.Overload("math_@max_uint_double", []*cel.Type{cel.UintType, cel.DoubleType}, cel.DynType,
cel.BinaryBinding(maxPair)),
cel.Overload("math_@max_list_double", []*cel.Type{cel.ListType(cel.DoubleType)}, cel.DoubleType,
cel.UnaryBinding(maxList)),
cel.Overload("math_@max_list_int", []*cel.Type{cel.ListType(cel.IntType)}, cel.IntType,
cel.UnaryBinding(maxList)),
cel.Overload("math_@max_list_uint", []*cel.Type{cel.ListType(cel.UintType)}, cel.UintType,
cel.UnaryBinding(maxList)),
),
}
if lib.version >= 1 {
opts = append(opts,
// Rounding function declarations
cel.Function(ceilFunc,
cel.Overload("math_ceil_double", []*cel.Type{cel.DoubleType}, cel.DoubleType,
cel.UnaryBinding(ceil))),
cel.Function(floorFunc,
cel.Overload("math_floor_double", []*cel.Type{cel.DoubleType}, cel.DoubleType,
cel.UnaryBinding(floor))),
cel.Function(roundFunc,
cel.Overload("math_round_double", []*cel.Type{cel.DoubleType}, cel.DoubleType,
cel.UnaryBinding(round))),
cel.Function(truncFunc,
cel.Overload("math_trunc_double", []*cel.Type{cel.DoubleType}, cel.DoubleType,
cel.UnaryBinding(trunc))),
// Floating point helpers
cel.Function(isInfFunc,
cel.Overload("math_isInf_double", []*cel.Type{cel.DoubleType}, cel.BoolType,
cel.UnaryBinding(isInf))),
cel.Function(isNanFunc,
cel.Overload("math_isNaN_double", []*cel.Type{cel.DoubleType}, cel.BoolType,
cel.UnaryBinding(isNaN))),
cel.Function(isFiniteFunc,
cel.Overload("math_isFinite_double", []*cel.Type{cel.DoubleType}, cel.BoolType,
cel.UnaryBinding(isFinite))),
// Signedness functions
cel.Function(absFunc,
cel.Overload("math_abs_double", []*cel.Type{cel.DoubleType}, cel.DoubleType,
cel.UnaryBinding(absDouble)),
cel.Overload("math_abs_int", []*cel.Type{cel.IntType}, cel.IntType,
cel.UnaryBinding(absInt)),
cel.Overload("math_abs_uint", []*cel.Type{cel.UintType}, cel.UintType,
cel.UnaryBinding(identity)),
),
cel.Function(signFunc,
cel.Overload("math_sign_double", []*cel.Type{cel.DoubleType}, cel.DoubleType,
cel.UnaryBinding(sign)),
cel.Overload("math_sign_int", []*cel.Type{cel.IntType}, cel.IntType,
cel.UnaryBinding(sign)),
cel.Overload("math_sign_uint", []*cel.Type{cel.UintType}, cel.UintType,
cel.UnaryBinding(sign)),
),
// Bitwise operator declarations
cel.Function(bitAndFunc,
cel.Overload("math_bitAnd_int_int", []*cel.Type{cel.IntType, cel.IntType}, cel.IntType,
cel.BinaryBinding(bitAndPairInt)),
cel.Overload("math_bitAnd_uint_uint", []*cel.Type{cel.UintType, cel.UintType}, cel.UintType,
cel.BinaryBinding(bitAndPairUint)),
),
cel.Function(bitOrFunc,
cel.Overload("math_bitOr_int_int", []*cel.Type{cel.IntType, cel.IntType}, cel.IntType,
cel.BinaryBinding(bitOrPairInt)),
cel.Overload("math_bitOr_uint_uint", []*cel.Type{cel.UintType, cel.UintType}, cel.UintType,
cel.BinaryBinding(bitOrPairUint)),
),
cel.Function(bitXorFunc,
cel.Overload("math_bitXor_int_int", []*cel.Type{cel.IntType, cel.IntType}, cel.IntType,
cel.BinaryBinding(bitXorPairInt)),
cel.Overload("math_bitXor_uint_uint", []*cel.Type{cel.UintType, cel.UintType}, cel.UintType,
cel.BinaryBinding(bitXorPairUint)),
),
cel.Function(bitNotFunc,
cel.Overload("math_bitNot_int_int", []*cel.Type{cel.IntType}, cel.IntType,
cel.UnaryBinding(bitNotInt)),
cel.Overload("math_bitNot_uint_uint", []*cel.Type{cel.UintType}, cel.UintType,
cel.UnaryBinding(bitNotUint)),
),
cel.Function(bitShiftLeftFunc,
cel.Overload("math_bitShiftLeft_int_int", []*cel.Type{cel.IntType, cel.IntType}, cel.IntType,
cel.BinaryBinding(bitShiftLeftIntInt)),
cel.Overload("math_bitShiftLeft_uint_int", []*cel.Type{cel.UintType, cel.IntType}, cel.UintType,
cel.BinaryBinding(bitShiftLeftUintInt)),
),
cel.Function(bitShiftRightFunc,
cel.Overload("math_bitShiftRight_int_int", []*cel.Type{cel.IntType, cel.IntType}, cel.IntType,
cel.BinaryBinding(bitShiftRightIntInt)),
cel.Overload("math_bitShiftRight_uint_int", []*cel.Type{cel.UintType, cel.IntType}, cel.UintType,
cel.BinaryBinding(bitShiftRightUintInt)),
),
)
}
return opts
}
// ProgramOptions implements the Library interface method.
func (*mathLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func mathLeast(meh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
if !macroTargetMatchesNamespace(mathNamespace, target) {
return nil, nil
}
switch len(args) {
case 0:
return nil, meh.NewError(target.ID(), "math.least() requires at least one argument")
case 1:
if isListLiteralWithNumericArgs(args[0]) || isNumericArgType(args[0]) {
return meh.NewCall(minFunc, args[0]), nil
}
return nil, meh.NewError(args[0].ID(), "math.least() invalid single argument value")
case 2:
err := checkInvalidArgs(meh, "math.least()", args)
if err != nil {
return nil, err
}
return meh.NewCall(minFunc, args...), nil
default:
err := checkInvalidArgs(meh, "math.least()", args)
if err != nil {
return nil, err
}
return meh.NewCall(minFunc, meh.NewList(args...)), nil
}
}
func mathGreatest(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
if !macroTargetMatchesNamespace(mathNamespace, target) {
return nil, nil
}
switch len(args) {
case 0:
return nil, mef.NewError(target.ID(), "math.greatest() requires at least one argument")
case 1:
if isListLiteralWithNumericArgs(args[0]) || isNumericArgType(args[0]) {
return mef.NewCall(maxFunc, args[0]), nil
}
return nil, mef.NewError(args[0].ID(), "math.greatest() invalid single argument value")
case 2:
err := checkInvalidArgs(mef, "math.greatest()", args)
if err != nil {
return nil, err
}
return mef.NewCall(maxFunc, args...), nil
default:
err := checkInvalidArgs(mef, "math.greatest()", args)
if err != nil {
return nil, err
}
return mef.NewCall(maxFunc, mef.NewList(args...)), nil
}
}
func identity(val ref.Val) ref.Val {
return val
}
func ceil(val ref.Val) ref.Val {
v := val.(types.Double)
return types.Double(math.Ceil(float64(v)))
}
func floor(val ref.Val) ref.Val {
v := val.(types.Double)
return types.Double(math.Floor(float64(v)))
}
func round(val ref.Val) ref.Val {
v := val.(types.Double)
return types.Double(math.Round(float64(v)))
}
func trunc(val ref.Val) ref.Val {
v := val.(types.Double)
return types.Double(math.Trunc(float64(v)))
}
func isInf(val ref.Val) ref.Val {
v := val.(types.Double)
return types.Bool(math.IsInf(float64(v), 0))
}
func isFinite(val ref.Val) ref.Val {
v := float64(val.(types.Double))
return types.Bool(!math.IsInf(v, 0) && !math.IsNaN(v))
}
func isNaN(val ref.Val) ref.Val {
v := val.(types.Double)
return types.Bool(math.IsNaN(float64(v)))
}
func absDouble(val ref.Val) ref.Val {
v := float64(val.(types.Double))
return types.Double(math.Abs(v))
}
func absInt(val ref.Val) ref.Val {
v := int64(val.(types.Int))
if v == math.MinInt64 {
return errIntOverflow
}
if v >= 0 {
return val
}
return -types.Int(v)
}
func sign(val ref.Val) ref.Val {
switch v := val.(type) {
case types.Double:
if isNaN(v) == types.True {
return v
}
zero := types.Double(0)
if v > zero {
return types.Double(1)
}
if v < zero {
return types.Double(-1)
}
return zero
case types.Int:
return v.Compare(types.IntZero)
case types.Uint:
if v == types.Uint(0) {
return types.Uint(0)
}
return types.Uint(1)
default:
return maybeSuffixError(val, "math.sign")
}
}
func bitAndPairInt(first, second ref.Val) ref.Val {
l := first.(types.Int)
r := second.(types.Int)
return l & r
}
func bitAndPairUint(first, second ref.Val) ref.Val {
l := first.(types.Uint)
r := second.(types.Uint)
return l & r
}
func bitOrPairInt(first, second ref.Val) ref.Val {
l := first.(types.Int)
r := second.(types.Int)
return l | r
}
func bitOrPairUint(first, second ref.Val) ref.Val {
l := first.(types.Uint)
r := second.(types.Uint)
return l | r
}
func bitXorPairInt(first, second ref.Val) ref.Val {
l := first.(types.Int)
r := second.(types.Int)
return l ^ r
}
func bitXorPairUint(first, second ref.Val) ref.Val {
l := first.(types.Uint)
r := second.(types.Uint)
return l ^ r
}
func bitNotInt(value ref.Val) ref.Val {
v := value.(types.Int)
return ^v
}
func bitNotUint(value ref.Val) ref.Val {
v := value.(types.Uint)
return ^v
}
func bitShiftLeftIntInt(value, bits ref.Val) ref.Val {
v := value.(types.Int)
bs := bits.(types.Int)
if bs < types.IntZero {
return types.NewErr("math.bitShiftLeft() negative offset: %d", bs)
}
return v << bs
}
func bitShiftLeftUintInt(value, bits ref.Val) ref.Val {
v := value.(types.Uint)
bs := bits.(types.Int)
if bs < types.IntZero {
return types.NewErr("math.bitShiftLeft() negative offset: %d", bs)
}
return v << bs
}
func bitShiftRightIntInt(value, bits ref.Val) ref.Val {
v := value.(types.Int)
bs := bits.(types.Int)
if bs < types.IntZero {
return types.NewErr("math.bitShiftRight() negative offset: %d", bs)
}
return types.Int(types.Uint(v) >> bs)
}
func bitShiftRightUintInt(value, bits ref.Val) ref.Val {
v := value.(types.Uint)
bs := bits.(types.Int)
if bs < types.IntZero {
return types.NewErr("math.bitShiftRight() negative offset: %d", bs)
}
return v >> bs
}
func minPair(first, second ref.Val) ref.Val {
cmp, ok := first.(traits.Comparer)
if !ok {
return types.MaybeNoSuchOverloadErr(first)
}
out := cmp.Compare(second)
if types.IsUnknownOrError(out) {
return maybeSuffixError(out, "math.@min")
}
if out == types.IntOne {
return second
}
return first
}
func minList(numList ref.Val) ref.Val {
l := numList.(traits.Lister)
size := l.Size().(types.Int)
if size == types.IntZero {
return types.NewErr("math.@min(list) argument must not be empty")
}
min := l.Get(types.IntZero)
for i := types.IntOne; i < size; i++ {
min = minPair(min, l.Get(i))
}
switch min.Type() {
case types.IntType, types.DoubleType, types.UintType, types.UnknownType:
return min
default:
return types.NewErr("no such overload: math.@min")
}
}
func maxPair(first, second ref.Val) ref.Val {
cmp, ok := first.(traits.Comparer)
if !ok {
return types.MaybeNoSuchOverloadErr(first)
}
out := cmp.Compare(second)
if types.IsUnknownOrError(out) {
return maybeSuffixError(out, "math.@max")
}
if out == types.IntNegOne {
return second
}
return first
}
func maxList(numList ref.Val) ref.Val {
l := numList.(traits.Lister)
size := l.Size().(types.Int)
if size == types.IntZero {
return types.NewErr("math.@max(list) argument must not be empty")
}
max := l.Get(types.IntZero)
for i := types.IntOne; i < size; i++ {
max = maxPair(max, l.Get(i))
}
switch max.Type() {
case types.IntType, types.DoubleType, types.UintType, types.UnknownType:
return max
default:
return types.NewErr("no such overload: math.@max")
}
}
func checkInvalidArgs(meh cel.MacroExprFactory, funcName string, args []ast.Expr) *cel.Error {
for _, arg := range args {
err := checkInvalidArgLiteral(funcName, arg)
if err != nil {
return meh.NewError(arg.ID(), err.Error())
}
}
return nil
}
func checkInvalidArgLiteral(funcName string, arg ast.Expr) error {
if !isNumericArgType(arg) {
return fmt.Errorf("%s simple literal arguments must be numeric", funcName)
}
return nil
}
func isNumericArgType(arg ast.Expr) bool {
switch arg.Kind() {
case ast.LiteralKind:
c := ref.Val(arg.AsLiteral())
switch c.(type) {
case types.Double, types.Int, types.Uint:
return true
default:
return false
}
case ast.ListKind, ast.MapKind, ast.StructKind:
return false
default:
return true
}
}
func isListLiteralWithNumericArgs(arg ast.Expr) bool {
switch arg.Kind() {
case ast.ListKind:
list := arg.AsList()
if list.Size() == 0 {
return false
}
for _, e := range list.Elements() {
if !isNumericArgType(e) {
return false
}
}
return true
}
return false
}
func maybeSuffixError(val ref.Val, suffix string) ref.Val {
if types.IsError(val) {
msg := val.(*types.Err).String()
if !strings.Contains(msg, suffix) {
return types.NewErr("%s: %s", msg, suffix)
}
}
return val
}

782
e2e/vendor/github.com/google/cel-go/ext/native.go generated vendored Normal file
View File

@ -0,0 +1,782 @@
// Copyright 2022 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 ext
import (
"errors"
"fmt"
"reflect"
"strings"
"time"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/pb"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
structpb "google.golang.org/protobuf/types/known/structpb"
)
var (
nativeObjTraitMask = traits.FieldTesterType | traits.IndexerType
jsonValueType = reflect.TypeOf(&structpb.Value{})
jsonStructType = reflect.TypeOf(&structpb.Struct{})
)
// NativeTypes creates a type provider which uses reflect.Type and reflect.Value instances
// to produce type definitions that can be used within CEL.
//
// All struct types in Go are exposed to CEL via their simple package name and struct type name:
//
// ```go
// package identity
//
// type Account struct {
// ID int
// }
//
// ```
//
// The type `identity.Account` would be exported to CEL using the same qualified name, e.g.
// `identity.Account{ID: 1234}` would create a new `Account` instance with the `ID` field
// populated.
//
// Only exported fields are exposed via NativeTypes, and the type-mapping between Go and CEL
// is as follows:
//
// | Go type | CEL type |
// |-------------------------------------|-----------|
// | bool | bool |
// | []byte | bytes |
// | float32, float64 | double |
// | int, int8, int16, int32, int64 | int |
// | string | string |
// | uint, uint8, uint16, uint32, uint64 | uint |
// | time.Duration | duration |
// | time.Time | timestamp |
// | array, slice | list |
// | map | map |
//
// Please note, if you intend to configure support for proto messages in addition to native
// types, you will need to provide the protobuf types before the golang native types. The
// same advice holds if you are using custom type adapters and type providers. The native type
// provider composes over whichever type adapter and provider is configured in the cel.Env at
// the time that it is invoked.
//
// There is also the possibility to rename the fields of native structs by setting the `cel` tag
// for fields you want to override. In order to enable this feature, pass in the `EnableStructTag`
// option. Here is an example to see it in action:
//
// ```go
// package identity
//
// type Account struct {
// ID int
// OwnerName string `cel:"owner"`
// }
//
// ```
//
// The `OwnerName` field is now accessible in CEL via `owner`, e.g. `identity.Account{owner: 'bob'}`.
// In case there are duplicated field names in the struct, an error will be returned.
func NativeTypes(args ...any) cel.EnvOption {
return func(env *cel.Env) (*cel.Env, error) {
nativeTypes := make([]any, 0, len(args))
tpOptions := nativeTypeOptions{}
for _, v := range args {
switch v := v.(type) {
case NativeTypesOption:
err := v(&tpOptions)
if err != nil {
return nil, err
}
default:
nativeTypes = append(nativeTypes, v)
}
}
tp, err := newNativeTypeProvider(tpOptions, env.CELTypeAdapter(), env.CELTypeProvider(), nativeTypes...)
if err != nil {
return nil, err
}
env, err = cel.CustomTypeAdapter(tp)(env)
if err != nil {
return nil, err
}
return cel.CustomTypeProvider(tp)(env)
}
}
// NativeTypesOption is a functional interface for configuring handling of native types.
type NativeTypesOption func(*nativeTypeOptions) error
// NativeTypesFieldNameHandler is a handler for mapping a reflect.StructField to a CEL field name.
// This can be used to override the default Go struct field to CEL field name mapping.
type NativeTypesFieldNameHandler = func(field reflect.StructField) string
func fieldNameByTag(structTagToParse string) func(field reflect.StructField) string {
return func(field reflect.StructField) string {
tag, found := field.Tag.Lookup(structTagToParse)
if found {
splits := strings.Split(tag, ",")
if len(splits) > 0 {
// We make the assumption that the leftmost entry in the tag is the name.
// This seems to be true for most tags that have the concept of a name/key, such as:
// https://pkg.go.dev/encoding/xml#Marshal
// https://pkg.go.dev/encoding/json#Marshal
// https://pkg.go.dev/go.mongodb.org/mongo-driver/bson#hdr-Structs
// https://pkg.go.dev/gopkg.in/yaml.v2#Marshal
name := splits[0]
return name
}
}
return field.Name
}
}
type nativeTypeOptions struct {
// fieldNameHandler controls how CEL should perform struct field renames.
// This is most commonly used for switching to parsing based off the struct field tag,
// such as "cel" or "json".
fieldNameHandler NativeTypesFieldNameHandler
}
// ParseStructTags configures if native types field names should be overridable by CEL struct tags.
// This is equivalent to ParseStructTag("cel")
func ParseStructTags(enabled bool) NativeTypesOption {
return func(ntp *nativeTypeOptions) error {
if enabled {
ntp.fieldNameHandler = fieldNameByTag("cel")
} else {
ntp.fieldNameHandler = nil
}
return nil
}
}
// ParseStructTag configures the struct tag to parse. The 0th item in the tag is used as the name of the CEL field.
// For example:
// If the tag to parse is "cel" and the struct field has tag cel:"foo", the CEL struct field will be "foo".
// If the tag to parse is "json" and the struct field has tag json:"foo,omitempty", the CEL struct field will be "foo".
func ParseStructTag(tag string) NativeTypesOption {
return func(ntp *nativeTypeOptions) error {
ntp.fieldNameHandler = fieldNameByTag(tag)
return nil
}
}
// ParseStructField configures how to parse Go struct fields. It can be used to customize struct field parsing.
func ParseStructField(handler NativeTypesFieldNameHandler) NativeTypesOption {
return func(ntp *nativeTypeOptions) error {
ntp.fieldNameHandler = handler
return nil
}
}
func newNativeTypeProvider(tpOptions nativeTypeOptions, adapter types.Adapter, provider types.Provider, refTypes ...any) (*nativeTypeProvider, error) {
nativeTypes := make(map[string]*nativeType, len(refTypes))
for _, refType := range refTypes {
switch rt := refType.(type) {
case reflect.Type:
result, err := newNativeTypes(tpOptions.fieldNameHandler, rt)
if err != nil {
return nil, err
}
for idx := range result {
nativeTypes[result[idx].TypeName()] = result[idx]
}
case reflect.Value:
result, err := newNativeTypes(tpOptions.fieldNameHandler, rt.Type())
if err != nil {
return nil, err
}
for idx := range result {
nativeTypes[result[idx].TypeName()] = result[idx]
}
default:
return nil, fmt.Errorf("unsupported native type: %v (%T) must be reflect.Type or reflect.Value", rt, rt)
}
}
return &nativeTypeProvider{
nativeTypes: nativeTypes,
baseAdapter: adapter,
baseProvider: provider,
options: tpOptions,
}, nil
}
type nativeTypeProvider struct {
nativeTypes map[string]*nativeType
baseAdapter types.Adapter
baseProvider types.Provider
options nativeTypeOptions
}
// EnumValue proxies to the types.Provider configured at the times the NativeTypes
// option was configured.
func (tp *nativeTypeProvider) EnumValue(enumName string) ref.Val {
return tp.baseProvider.EnumValue(enumName)
}
// FindIdent looks up natives type instances by qualified identifier, and if not found
// proxies to the composed types.Provider.
func (tp *nativeTypeProvider) FindIdent(typeName string) (ref.Val, bool) {
if t, found := tp.nativeTypes[typeName]; found {
return t, true
}
return tp.baseProvider.FindIdent(typeName)
}
// FindStructType looks up the CEL type definition by qualified identifier, and if not found
// proxies to the composed types.Provider.
func (tp *nativeTypeProvider) FindStructType(typeName string) (*types.Type, bool) {
if _, found := tp.nativeTypes[typeName]; found {
return types.NewTypeTypeWithParam(types.NewObjectType(typeName)), true
}
if celType, found := tp.baseProvider.FindStructType(typeName); found {
return celType, true
}
return tp.baseProvider.FindStructType(typeName)
}
func toFieldName(fieldNameHandler NativeTypesFieldNameHandler, f reflect.StructField) string {
if fieldNameHandler == nil {
return f.Name
}
return fieldNameHandler(f)
}
// FindStructFieldNames looks up the type definition first from the native types, then from
// the backing provider type set. If found, a set of field names corresponding to the type
// will be returned.
func (tp *nativeTypeProvider) FindStructFieldNames(typeName string) ([]string, bool) {
if t, found := tp.nativeTypes[typeName]; found {
fieldCount := t.refType.NumField()
fields := make([]string, fieldCount)
for i := 0; i < fieldCount; i++ {
fields[i] = toFieldName(tp.options.fieldNameHandler, t.refType.Field(i))
}
return fields, true
}
if celTypeFields, found := tp.baseProvider.FindStructFieldNames(typeName); found {
return celTypeFields, true
}
return tp.baseProvider.FindStructFieldNames(typeName)
}
// FindStructFieldType looks up a native type's field definition, and if the type name is not a native
// type then proxies to the composed types.Provider
func (tp *nativeTypeProvider) FindStructFieldType(typeName, fieldName string) (*types.FieldType, bool) {
t, found := tp.nativeTypes[typeName]
if !found {
return tp.baseProvider.FindStructFieldType(typeName, fieldName)
}
refField, isDefined := t.hasField(fieldName)
if !found || !isDefined {
return nil, false
}
celType, ok := convertToCelType(refField.Type)
if !ok {
return nil, false
}
return &types.FieldType{
Type: celType,
IsSet: func(obj any) bool {
refVal := reflect.Indirect(reflect.ValueOf(obj))
refField := refVal.FieldByName(refField.Name)
return !refField.IsZero()
},
GetFrom: func(obj any) (any, error) {
refVal := reflect.Indirect(reflect.ValueOf(obj))
refField := refVal.FieldByName(refField.Name)
return getFieldValue(refField), nil
},
}, true
}
// NewValue implements the ref.TypeProvider interface method.
func (tp *nativeTypeProvider) NewValue(typeName string, fields map[string]ref.Val) ref.Val {
t, found := tp.nativeTypes[typeName]
if !found {
return tp.baseProvider.NewValue(typeName, fields)
}
refPtr := reflect.New(t.refType)
refVal := refPtr.Elem()
for fieldName, val := range fields {
refFieldDef, isDefined := t.hasField(fieldName)
if !isDefined {
return types.NewErr("no such field: %s", fieldName)
}
fieldVal, err := val.ConvertToNative(refFieldDef.Type)
if err != nil {
return types.NewErr(err.Error())
}
refField := refVal.FieldByIndex(refFieldDef.Index)
refFieldVal := reflect.ValueOf(fieldVal)
refField.Set(refFieldVal)
}
return tp.NativeToValue(refPtr.Interface())
}
// NewValue adapts native values to CEL values and will proxy to the composed type adapter
// for non-native types.
func (tp *nativeTypeProvider) NativeToValue(val any) ref.Val {
if val == nil {
return types.NullValue
}
if v, ok := val.(ref.Val); ok {
return v
}
rawVal := reflect.ValueOf(val)
refVal := rawVal
if refVal.Kind() == reflect.Ptr {
refVal = reflect.Indirect(refVal)
}
// This isn't quite right if you're also supporting proto,
// but maybe an acceptable limitation.
switch refVal.Kind() {
case reflect.Array, reflect.Slice:
switch val := val.(type) {
case []byte:
return tp.baseAdapter.NativeToValue(val)
default:
if refVal.Type().Elem() == reflect.TypeOf(byte(0)) {
return tp.baseAdapter.NativeToValue(val)
}
return types.NewDynamicList(tp, val)
}
case reflect.Map:
return types.NewDynamicMap(tp, val)
case reflect.Struct:
switch val := val.(type) {
case proto.Message, *pb.Map, protoreflect.List, protoreflect.Message, protoreflect.Value,
time.Time:
return tp.baseAdapter.NativeToValue(val)
default:
return tp.newNativeObject(val, rawVal)
}
default:
return tp.baseAdapter.NativeToValue(val)
}
}
func convertToCelType(refType reflect.Type) (*cel.Type, bool) {
switch refType.Kind() {
case reflect.Bool:
return cel.BoolType, true
case reflect.Float32, reflect.Float64:
return cel.DoubleType, true
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if refType == durationType {
return cel.DurationType, true
}
return cel.IntType, true
case reflect.String:
return cel.StringType, true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return cel.UintType, true
case reflect.Array, reflect.Slice:
refElem := refType.Elem()
if refElem == reflect.TypeOf(byte(0)) {
return cel.BytesType, true
}
elemType, ok := convertToCelType(refElem)
if !ok {
return nil, false
}
return cel.ListType(elemType), true
case reflect.Map:
keyType, ok := convertToCelType(refType.Key())
if !ok {
return nil, false
}
// Ensure the key type is a int, bool, uint, string
elemType, ok := convertToCelType(refType.Elem())
if !ok {
return nil, false
}
return cel.MapType(keyType, elemType), true
case reflect.Struct:
if refType == timestampType {
return cel.TimestampType, true
}
return cel.ObjectType(
fmt.Sprintf("%s.%s", simplePkgAlias(refType.PkgPath()), refType.Name()),
), true
case reflect.Pointer:
if refType.Implements(pbMsgInterfaceType) {
pbMsg := reflect.New(refType.Elem()).Interface().(protoreflect.ProtoMessage)
return cel.ObjectType(string(pbMsg.ProtoReflect().Descriptor().FullName())), true
}
return convertToCelType(refType.Elem())
}
return nil, false
}
func (tp *nativeTypeProvider) newNativeObject(val any, refValue reflect.Value) ref.Val {
valType, err := newNativeType(tp.options.fieldNameHandler, refValue.Type())
if err != nil {
return types.NewErr(err.Error())
}
return &nativeObj{
Adapter: tp,
val: val,
valType: valType,
refValue: refValue,
}
}
type nativeObj struct {
types.Adapter
val any
valType *nativeType
refValue reflect.Value
}
// ConvertToNative implements the ref.Val interface method.
//
// CEL does not have a notion of pointers, so whether a field is a pointer or value
// is handled as part of this conversion step.
func (o *nativeObj) ConvertToNative(typeDesc reflect.Type) (any, error) {
if o.refValue.Type() == typeDesc {
return o.val, nil
}
if o.refValue.Kind() == reflect.Pointer && o.refValue.Type().Elem() == typeDesc {
return o.refValue.Elem().Interface(), nil
}
if typeDesc.Kind() == reflect.Pointer && o.refValue.Type() == typeDesc.Elem() {
ptr := reflect.New(typeDesc.Elem())
ptr.Elem().Set(o.refValue)
return ptr.Interface(), nil
}
switch typeDesc {
case jsonValueType:
jsonStruct, err := o.ConvertToNative(jsonStructType)
if err != nil {
return nil, err
}
return structpb.NewStructValue(jsonStruct.(*structpb.Struct)), nil
case jsonStructType:
refVal := reflect.Indirect(o.refValue)
refType := refVal.Type()
fields := make(map[string]*structpb.Value, refVal.NumField())
for i := 0; i < refVal.NumField(); i++ {
fieldType := refType.Field(i)
fieldValue := refVal.Field(i)
if !fieldValue.IsValid() || fieldValue.IsZero() {
continue
}
fieldName := toFieldName(o.valType.fieldNameHandler, fieldType)
fieldCELVal := o.NativeToValue(fieldValue.Interface())
fieldJSONVal, err := fieldCELVal.ConvertToNative(jsonValueType)
if err != nil {
return nil, err
}
fields[fieldName] = fieldJSONVal.(*structpb.Value)
}
return &structpb.Struct{Fields: fields}, nil
}
return nil, fmt.Errorf("type conversion error from '%v' to '%v'", o.Type(), typeDesc)
}
// ConvertToType implements the ref.Val interface method.
func (o *nativeObj) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case types.TypeType:
return o.valType
default:
if typeVal.TypeName() == o.valType.typeName {
return o
}
}
return types.NewErr("type conversion error from '%s' to '%s'", o.Type(), typeVal)
}
// Equal implements the ref.Val interface method.
//
// Note, that in Golang a pointer to a value is not equal to the value it contains.
// In CEL pointers and values to which they point are equal.
func (o *nativeObj) Equal(other ref.Val) ref.Val {
otherNtv, ok := other.(*nativeObj)
if !ok {
return types.False
}
val := o.val
otherVal := otherNtv.val
refVal := o.refValue
otherRefVal := otherNtv.refValue
if refVal.Kind() != otherRefVal.Kind() {
if refVal.Kind() == reflect.Pointer {
val = refVal.Elem().Interface()
} else if otherRefVal.Kind() == reflect.Pointer {
otherVal = otherRefVal.Elem().Interface()
}
}
return types.Bool(reflect.DeepEqual(val, otherVal))
}
// IsZeroValue indicates whether the contained Golang value is a zero value.
//
// Golang largely follows proto3 semantics for zero values.
func (o *nativeObj) IsZeroValue() bool {
return reflect.Indirect(o.refValue).IsZero()
}
// IsSet tests whether a field which is defined is set to a non-default value.
func (o *nativeObj) IsSet(field ref.Val) ref.Val {
refField, refErr := o.getReflectedField(field)
if refErr != nil {
return refErr
}
return types.Bool(!refField.IsZero())
}
// Get returns the value fo a field name.
func (o *nativeObj) Get(field ref.Val) ref.Val {
refField, refErr := o.getReflectedField(field)
if refErr != nil {
return refErr
}
return adaptFieldValue(o, refField)
}
func (o *nativeObj) getReflectedField(field ref.Val) (reflect.Value, ref.Val) {
fieldName, ok := field.(types.String)
if !ok {
return reflect.Value{}, types.MaybeNoSuchOverloadErr(field)
}
fieldNameStr := string(fieldName)
refField, isDefined := o.valType.hasField(fieldNameStr)
if !isDefined {
return reflect.Value{}, types.NewErr("no such field: %s", fieldName)
}
refVal := reflect.Indirect(o.refValue)
return refVal.FieldByIndex(refField.Index), nil
}
// Type implements the ref.Val interface method.
func (o *nativeObj) Type() ref.Type {
return o.valType
}
// Value implements the ref.Val interface method.
func (o *nativeObj) Value() any {
return o.val
}
func newNativeTypes(fieldNameHandler NativeTypesFieldNameHandler, rawType reflect.Type) ([]*nativeType, error) {
nt, err := newNativeType(fieldNameHandler, rawType)
if err != nil {
return nil, err
}
result := []*nativeType{nt}
alreadySeen := make(map[string]struct{})
var iterateStructMembers func(reflect.Type)
iterateStructMembers = func(t reflect.Type) {
if k := t.Kind(); k == reflect.Pointer || k == reflect.Slice || k == reflect.Array || k == reflect.Map {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return
}
if _, seen := alreadySeen[t.String()]; seen {
return
}
alreadySeen[t.String()] = struct{}{}
nt, ntErr := newNativeType(fieldNameHandler, t)
if ntErr != nil {
err = ntErr
return
}
result = append(result, nt)
for idx := 0; idx < t.NumField(); idx++ {
iterateStructMembers(t.Field(idx).Type)
}
}
iterateStructMembers(rawType)
return result, err
}
var (
errDuplicatedFieldName = errors.New("field name already exists in struct")
)
func newNativeType(fieldNameHandler NativeTypesFieldNameHandler, rawType reflect.Type) (*nativeType, error) {
refType := rawType
if refType.Kind() == reflect.Pointer {
refType = refType.Elem()
}
if !isValidObjectType(refType) {
return nil, fmt.Errorf("unsupported reflect.Type %v, must be reflect.Struct", rawType)
}
// Since naming collisions can only happen with struct tag parsing, we only check for them if it is enabled.
if fieldNameHandler != nil {
fieldNames := make(map[string]struct{})
for idx := 0; idx < refType.NumField(); idx++ {
field := refType.Field(idx)
fieldName := toFieldName(fieldNameHandler, field)
if _, found := fieldNames[fieldName]; found {
return nil, fmt.Errorf("invalid field name `%s` in struct `%s`: %w", fieldName, refType.Name(), errDuplicatedFieldName)
} else {
fieldNames[fieldName] = struct{}{}
}
}
}
return &nativeType{
typeName: fmt.Sprintf("%s.%s", simplePkgAlias(refType.PkgPath()), refType.Name()),
refType: refType,
fieldNameHandler: fieldNameHandler,
}, nil
}
type nativeType struct {
typeName string
refType reflect.Type
fieldNameHandler NativeTypesFieldNameHandler
}
// ConvertToNative implements ref.Val.ConvertToNative.
func (t *nativeType) ConvertToNative(typeDesc reflect.Type) (any, error) {
return nil, fmt.Errorf("type conversion error for type to '%v'", typeDesc)
}
// ConvertToType implements ref.Val.ConvertToType.
func (t *nativeType) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case types.TypeType:
return types.TypeType
}
return types.NewErr("type conversion error from '%s' to '%s'", types.TypeType, typeVal)
}
// Equal returns true of both type names are equal to each other.
func (t *nativeType) Equal(other ref.Val) ref.Val {
otherType, ok := other.(ref.Type)
return types.Bool(ok && t.TypeName() == otherType.TypeName())
}
// HasTrait implements the ref.Type interface method.
func (t *nativeType) HasTrait(trait int) bool {
return nativeObjTraitMask&trait == trait
}
// String implements the strings.Stringer interface method.
func (t *nativeType) String() string {
return t.typeName
}
// Type implements the ref.Val interface method.
func (t *nativeType) Type() ref.Type {
return types.TypeType
}
// TypeName implements the ref.Type interface method.
func (t *nativeType) TypeName() string {
return t.typeName
}
// Value implements the ref.Val interface method.
func (t *nativeType) Value() any {
return t.typeName
}
// fieldByName returns the corresponding reflect.StructField for the give name either by matching
// field tag or field name.
func (t *nativeType) fieldByName(fieldName string) (reflect.StructField, bool) {
if t.fieldNameHandler == nil {
return t.refType.FieldByName(fieldName)
}
for i := 0; i < t.refType.NumField(); i++ {
f := t.refType.Field(i)
if toFieldName(t.fieldNameHandler, f) == fieldName {
return f, true
}
}
return reflect.StructField{}, false
}
// hasField returns whether a field name has a corresponding Golang reflect.StructField
func (t *nativeType) hasField(fieldName string) (reflect.StructField, bool) {
f, found := t.fieldByName(fieldName)
if !found || !f.IsExported() || !isSupportedType(f.Type) {
return reflect.StructField{}, false
}
return f, true
}
func adaptFieldValue(adapter types.Adapter, refField reflect.Value) ref.Val {
return adapter.NativeToValue(getFieldValue(refField))
}
func getFieldValue(refField reflect.Value) any {
if refField.IsZero() {
switch refField.Kind() {
case reflect.Struct:
if refField.Type() == timestampType {
return time.Unix(0, 0)
}
case reflect.Pointer:
return reflect.New(refField.Type().Elem()).Interface()
}
}
return refField.Interface()
}
func simplePkgAlias(pkgPath string) string {
paths := strings.Split(pkgPath, "/")
if len(paths) == 0 {
return ""
}
return paths[len(paths)-1]
}
func isValidObjectType(refType reflect.Type) bool {
return refType.Kind() == reflect.Struct
}
func isSupportedType(refType reflect.Type) bool {
switch refType.Kind() {
case reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func, reflect.UnsafePointer, reflect.Uintptr:
return false
case reflect.Array, reflect.Slice:
return isSupportedType(refType.Elem())
case reflect.Map:
return isSupportedType(refType.Key()) && isSupportedType(refType.Elem())
}
return true
}
var (
pbMsgInterfaceType = reflect.TypeOf((*protoreflect.ProtoMessage)(nil)).Elem()
timestampType = reflect.TypeOf(time.Now())
durationType = reflect.TypeOf(time.Nanosecond)
)

140
e2e/vendor/github.com/google/cel-go/ext/protos.go generated vendored Normal file
View File

@ -0,0 +1,140 @@
// Copyright 2022 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 ext
import (
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/ast"
)
// Protos returns a cel.EnvOption to configure extended macros and functions for
// proto manipulation.
//
// Note, all macros use the 'proto' namespace; however, at the time of macro
// expansion the namespace looks just like any other identifier. If you are
// currently using a variable named 'proto', the macro will likely work just as
// intended; however, there is some chance for collision.
//
// # Protos.GetExt
//
// Macro which generates a select expression that retrieves an extension field
// from the input proto2 syntax message. If the field is not set, the default
// value forthe extension field is returned according to safe-traversal semantics.
//
// proto.getExt(<msg>, <fully.qualified.extension.name>) -> <field-type>
//
// Examples:
//
// proto.getExt(msg, google.expr.proto2.test.int32_ext) // returns int value
//
// # Protos.HasExt
//
// Macro which generates a test-only select expression that determines whether
// an extension field is set on a proto2 syntax message.
//
// proto.hasExt(<msg>, <fully.qualified.extension.name>) -> <bool>
//
// Examples:
//
// proto.hasExt(msg, google.expr.proto2.test.int32_ext) // returns true || false
func Protos() cel.EnvOption {
return cel.Lib(protoLib{})
}
var (
protoNamespace = "proto"
hasExtension = "hasExt"
getExtension = "getExt"
)
type protoLib struct{}
// LibraryName implements the SingletonLibrary interface method.
func (protoLib) LibraryName() string {
return "cel.lib.ext.protos"
}
// CompileOptions implements the Library interface method.
func (protoLib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
cel.Macros(
// proto.getExt(msg, select_expression)
cel.ReceiverMacro(getExtension, 2, getProtoExt),
// proto.hasExt(msg, select_expression)
cel.ReceiverMacro(hasExtension, 2, hasProtoExt),
),
}
}
// ProgramOptions implements the Library interface method.
func (protoLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
// hasProtoExt generates a test-only select expression for a fully-qualified extension name on a protobuf message.
func hasProtoExt(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
if !macroTargetMatchesNamespace(protoNamespace, target) {
return nil, nil
}
extensionField, err := getExtFieldName(mef, args[1])
if err != nil {
return nil, err
}
return mef.NewPresenceTest(args[0], extensionField), nil
}
// getProtoExt generates a select expression for a fully-qualified extension name on a protobuf message.
func getProtoExt(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
if !macroTargetMatchesNamespace(protoNamespace, target) {
return nil, nil
}
extFieldName, err := getExtFieldName(mef, args[1])
if err != nil {
return nil, err
}
return mef.NewSelect(args[0], extFieldName), nil
}
func getExtFieldName(mef cel.MacroExprFactory, expr ast.Expr) (string, *cel.Error) {
isValid := false
extensionField := ""
switch expr.Kind() {
case ast.SelectKind:
extensionField, isValid = validateIdentifier(expr)
}
if !isValid {
return "", mef.NewError(expr.ID(), "invalid extension field")
}
return extensionField, nil
}
func validateIdentifier(expr ast.Expr) (string, bool) {
switch expr.Kind() {
case ast.IdentKind:
return expr.AsIdent(), true
case ast.SelectKind:
sel := expr.AsSelect()
if sel.IsTestOnly() {
return "", false
}
opStr, isIdent := validateIdentifier(sel.Operand())
if !isIdent {
return "", false
}
return opStr + "." + sel.FieldName(), true
default:
return "", false
}
}

261
e2e/vendor/github.com/google/cel-go/ext/sets.go generated vendored Normal file
View File

@ -0,0 +1,261 @@
// Copyright 2023 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 ext
import (
"math"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/operators"
"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"
)
// Sets returns a cel.EnvOption to configure namespaced set relationship
// functions.
//
// There is no set type within CEL, and while one may be introduced in the
// future, there are cases where a `list` type is known to behave like a set.
// For such cases, this library provides some basic functionality for
// determining set containment, equivalence, and intersection.
//
// # Sets.Contains
//
// Returns whether the first list argument contains all elements in the second
// list argument. The list may contain elements of any type and standard CEL
// equality is used to determine whether a value exists in both lists. If the
// second list is empty, the result will always return true.
//
// sets.contains(list(T), list(T)) -> bool
//
// Examples:
//
// sets.contains([], []) // true
// sets.contains([], [1]) // false
// sets.contains([1, 2, 3, 4], [2, 3]) // true
// sets.contains([1, 2.0, 3u], [1.0, 2u, 3]) // true
//
// # Sets.Equivalent
//
// Returns whether the first and second list are set equivalent. Lists are set
// equivalent if for every item in the first list, there is an element in the
// second which is equal. The lists may not be of the same size as they do not
// guarantee the elements within them are unique, so size does not factor into
// the computation.
//
// Examples:
//
// sets.equivalent([], []) // true
// sets.equivalent([1], [1, 1]) // true
// sets.equivalent([1], [1u, 1.0]) // true
// sets.equivalent([1, 2, 3], [3u, 2.0, 1]) // true
//
// # Sets.Intersects
//
// Returns whether the first list has at least one element whose value is equal
// to an element in the second list. If either list is empty, the result will
// be false.
//
// Examples:
//
// sets.intersects([1], []) // false
// sets.intersects([1], [1, 2]) // true
// sets.intersects([[1], [2, 3]], [[1, 2], [2, 3.0]]) // true
func Sets() cel.EnvOption {
return cel.Lib(setsLib{})
}
type setsLib struct{}
// LibraryName implements the SingletonLibrary interface method.
func (setsLib) LibraryName() string {
return "cel.lib.ext.sets"
}
// CompileOptions implements the Library interface method.
func (setsLib) CompileOptions() []cel.EnvOption {
listType := cel.ListType(cel.TypeParamType("T"))
return []cel.EnvOption{
cel.Function("sets.contains",
cel.Overload("list_sets_contains_list", []*cel.Type{listType, listType}, cel.BoolType,
cel.BinaryBinding(setsContains))),
cel.Function("sets.equivalent",
cel.Overload("list_sets_equivalent_list", []*cel.Type{listType, listType}, cel.BoolType,
cel.BinaryBinding(setsEquivalent))),
cel.Function("sets.intersects",
cel.Overload("list_sets_intersects_list", []*cel.Type{listType, listType}, cel.BoolType,
cel.BinaryBinding(setsIntersects))),
cel.CostEstimatorOptions(
checker.OverloadCostEstimate("list_sets_contains_list", estimateSetsCost(1)),
checker.OverloadCostEstimate("list_sets_intersects_list", estimateSetsCost(1)),
// equivalence requires potentially two m*n comparisons to ensure each list is contained by the other
checker.OverloadCostEstimate("list_sets_equivalent_list", estimateSetsCost(2)),
),
}
}
// ProgramOptions implements the Library interface method.
func (setsLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{
cel.CostTrackerOptions(
interpreter.OverloadCostTracker("list_sets_contains_list", trackSetsCost(1)),
interpreter.OverloadCostTracker("list_sets_intersects_list", trackSetsCost(1)),
interpreter.OverloadCostTracker("list_sets_equivalent_list", trackSetsCost(2)),
),
}
}
// NewSetMembershipOptimizer rewrites set membership tests using the `in` operator against a list
// of constant values of enum, int, uint, string, or boolean type into a set membership test against
// a map where the map keys are the elements of the list.
func NewSetMembershipOptimizer() (cel.ASTOptimizer, error) {
return setsLib{}, nil
}
func (setsLib) Optimize(ctx *cel.OptimizerContext, a *ast.AST) *ast.AST {
root := ast.NavigateAST(a)
matches := ast.MatchDescendants(root, matchInConstantList(a))
for _, match := range matches {
call := match.AsCall()
listArg := call.Args()[1]
entries := make([]ast.EntryExpr, len(listArg.AsList().Elements()))
for i, elem := range listArg.AsList().Elements() {
var entry ast.EntryExpr
if r, found := a.ReferenceMap()[elem.ID()]; found && r.Value != nil {
entry = ctx.NewMapEntry(ctx.NewLiteral(r.Value), ctx.NewLiteral(types.True), false)
} else {
entry = ctx.NewMapEntry(elem, ctx.NewLiteral(types.True), false)
}
entries[i] = entry
}
mapArg := ctx.NewMap(entries)
ctx.UpdateExpr(listArg, mapArg)
}
return a
}
func matchInConstantList(a *ast.AST) ast.ExprMatcher {
return func(e ast.NavigableExpr) bool {
if e.Kind() != ast.CallKind {
return false
}
call := e.AsCall()
if call.FunctionName() != operators.In {
return false
}
aggregateVal := call.Args()[1]
if aggregateVal.Kind() != ast.ListKind {
return false
}
listVal := aggregateVal.AsList()
for _, elem := range listVal.Elements() {
if r, found := a.ReferenceMap()[elem.ID()]; found {
if r.Value != nil {
continue
}
}
if elem.Kind() != ast.LiteralKind {
return false
}
lit := elem.AsLiteral()
if !(lit.Type() == cel.StringType || lit.Type() == cel.IntType ||
lit.Type() == cel.UintType || lit.Type() == cel.BoolType) {
return false
}
}
return true
}
}
func setsIntersects(listA, listB ref.Val) ref.Val {
lA := listA.(traits.Lister)
lB := listB.(traits.Lister)
it := lA.Iterator()
for it.HasNext() == types.True {
exists := lB.Contains(it.Next())
if exists == types.True {
return types.True
}
}
return types.False
}
func setsContains(list, sublist ref.Val) ref.Val {
l := list.(traits.Lister)
sub := sublist.(traits.Lister)
it := sub.Iterator()
for it.HasNext() == types.True {
exists := l.Contains(it.Next())
if exists != types.True {
return exists
}
}
return types.True
}
func setsEquivalent(listA, listB ref.Val) ref.Val {
aContainsB := setsContains(listA, listB)
if aContainsB != types.True {
return aContainsB
}
return setsContains(listB, listA)
}
func estimateSetsCost(costFactor float64) checker.FunctionEstimator {
return func(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
if len(args) == 2 {
arg0Size := estimateSize(estimator, args[0])
arg1Size := estimateSize(estimator, args[1])
costEstimate := arg0Size.Multiply(arg1Size).MultiplyByCostFactor(costFactor).Add(callCostEstimate)
return &checker.CallEstimate{CostEstimate: costEstimate}
}
return nil
}
}
func estimateSize(estimator checker.CostEstimator, node checker.AstNode) checker.SizeEstimate {
if l := node.ComputedSize(); l != nil {
return *l
}
if l := estimator.EstimateSize(node); l != nil {
return *l
}
return checker.SizeEstimate{Min: 0, Max: math.MaxUint64}
}
func trackSetsCost(costFactor float64) interpreter.FunctionTracker {
return func(args []ref.Val, _ ref.Val) *uint64 {
lhsSize := actualSize(args[0])
rhsSize := actualSize(args[1])
cost := callCost + uint64(float64(lhsSize*rhsSize)*costFactor)
return &cost
}
}
func actualSize(value ref.Val) uint64 {
if sz, ok := value.(traits.Sizer); ok {
return uint64(sz.Size().(types.Int))
}
return 1
}
var (
callCostEstimate = checker.CostEstimate{Min: 1, Max: 1}
callCost = uint64(1)
)

775
e2e/vendor/github.com/google/cel-go/ext/strings.go generated vendored Normal file
View File

@ -0,0 +1,775 @@
// 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 ext contains CEL extension libraries where each library defines a related set of
// constants, functions, macros, or other configuration settings which may not be covered by
// the core CEL spec.
package ext
import (
"fmt"
"math"
"reflect"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/text/language"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
)
const (
defaultLocale = "en-US"
defaultPrecision = 6
)
// Strings returns a cel.EnvOption to configure extended functions for string manipulation.
// As a general note, all indices are zero-based.
//
// # CharAt
//
// Returns the character at the given position. If the position is negative, or greater than
// the length of the string, the function will produce an error:
//
// <string>.charAt(<int>) -> <string>
//
// Examples:
//
// 'hello'.charAt(4) // return 'o'
// 'hello'.charAt(5) // return ''
// 'hello'.charAt(-1) // error
//
// # Format
//
// Introduced at version: 1
//
// Returns a new string with substitutions being performed, printf-style.
// The valid formatting clauses are:
//
// `%s` - substitutes a string. This can also be used on bools, lists, maps, bytes,
// Duration and Timestamp, in addition to all numerical types (int, uint, and double).
// Note that the dot/period decimal separator will always be used when printing a list
// or map that contains a double, and that null can be passed (which results in the
// string "null") in addition to types.
// `%d` - substitutes an integer.
// `%f` - substitutes a double with fixed-point precision. The default precision is 6, but
// this can be adjusted. The strings `Infinity`, `-Infinity`, and `NaN` are also valid input
// for this clause.
// `%e` - substitutes a double in scientific notation. The default precision is 6, but this
// can be adjusted.
// `%b` - substitutes an integer with its equivalent binary string. Can also be used on bools.
// `%x` - substitutes an integer with its equivalent in hexadecimal, or if given a string or
// bytes, will output each character's equivalent in hexadecimal.
// `%X` - same as above, but with A-F capitalized.
// `%o` - substitutes an integer with its equivalent in octal.
//
// <string>.format(<list>) -> <string>
//
// Examples:
//
// "this is a string: %s\nand an integer: %d".format(["str", 42]) // returns "this is a string: str\nand an integer: 42"
// "a double substituted with %%s: %s".format([64.2]) // returns "a double substituted with %s: 64.2"
// "string type: %s".format([type(string)]) // returns "string type: string"
// "timestamp: %s".format([timestamp("2023-02-03T23:31:20+00:00")]) // returns "timestamp: 2023-02-03T23:31:20Z"
// "duration: %s".format([duration("1h45m47s")]) // returns "duration: 6347s"
// "%f".format([3.14]) // returns "3.140000"
// "scientific notation: %e".format([2.71828]) // returns "scientific notation: 2.718280\u202f\u00d7\u202f10\u2070\u2070"
// "5 in binary: %b".format([5]), // returns "5 in binary; 101"
// "26 in hex: %x".format([26]), // returns "26 in hex: 1a"
// "26 in hex (uppercase): %X".format([26]) // returns "26 in hex (uppercase): 1A"
// "30 in octal: %o".format([30]) // returns "30 in octal: 36"
// "a map inside a list: %s".format([[1, 2, 3, {"a": "x", "b": "y", "c": "z"}]]) // returns "a map inside a list: [1, 2, 3, {"a":"x", "b":"y", "c":"d"}]"
// "true bool: %s - false bool: %s\nbinary bool: %b".format([true, false, true]) // returns "true bool: true - false bool: false\nbinary bool: 1"
//
// Passing an incorrect type (a string to `%b`) is considered an error, as well as attempting
// to use more formatting clauses than there are arguments (`%d %d %d` while passing two ints, for instance).
// If compile-time checking is enabled, and the formatting string is a constant, and the argument list is a literal,
// then letting any arguments go unused/unformatted is also considered an error.
//
// # IndexOf
//
// Returns the integer index of the first occurrence of the search string. If the search string is
// not found the function returns -1.
//
// The function also accepts an optional position from which to begin the substring search. If the
// substring is the empty string, the index where the search starts is returned (zero or custom).
//
// <string>.indexOf(<string>) -> <int>
// <string>.indexOf(<string>, <int>) -> <int>
//
// Examples:
//
// 'hello mellow'.indexOf('') // returns 0
// 'hello mellow'.indexOf('ello') // returns 1
// 'hello mellow'.indexOf('jello') // returns -1
// 'hello mellow'.indexOf('', 2) // returns 2
// 'hello mellow'.indexOf('ello', 2) // returns 7
// 'hello mellow'.indexOf('ello', 20) // returns -1
// 'hello mellow'.indexOf('ello', -1) // error
//
// # Join
//
// Returns a new string where the elements of string list are concatenated.
//
// The function also accepts an optional separator which is placed between elements in the resulting string.
//
// <list<string>>.join() -> <string>
// <list<string>>.join(<string>) -> <string>
//
// Examples:
//
// ['hello', 'mellow'].join() // returns 'hellomellow'
// ['hello', 'mellow'].join(' ') // returns 'hello mellow'
// [].join() // returns ''
// [].join('/') // returns ''
//
// # LastIndexOf
//
// Returns the integer index at the start of the last occurrence of the search string. If the
// search string is not found the function returns -1.
//
// The function also accepts an optional position which represents the last index to be
// considered as the beginning of the substring match. If the substring is the empty string,
// the index where the search starts is returned (string length or custom).
//
// <string>.lastIndexOf(<string>) -> <int>
// <string>.lastIndexOf(<string>, <int>) -> <int>
//
// Examples:
//
// 'hello mellow'.lastIndexOf('') // returns 12
// 'hello mellow'.lastIndexOf('ello') // returns 7
// 'hello mellow'.lastIndexOf('jello') // returns -1
// 'hello mellow'.lastIndexOf('ello', 6) // returns 1
// 'hello mellow'.lastIndexOf('ello', 20) // returns -1
// 'hello mellow'.lastIndexOf('ello', -1) // error
//
// # LowerAscii
//
// Returns a new string where all ASCII characters are lower-cased.
//
// This function does not perform Unicode case-mapping for characters outside the ASCII range.
//
// <string>.lowerAscii() -> <string>
//
// Examples:
//
// 'TacoCat'.lowerAscii() // returns 'tacocat'
// 'TacoCÆt Xii'.lowerAscii() // returns 'tacocÆt xii'
//
// # Strings.Quote
//
// Introduced in version: 1
//
// Takes the given string and makes it safe to print (without any formatting due to escape sequences).
// If any invalid UTF-8 characters are encountered, they are replaced with \uFFFD.
//
// strings.quote(<string>)
//
// Examples:
//
// strings.quote('single-quote with "double quote"') // returns '"single-quote with \"double quote\""'
// strings.quote("two escape sequences \a\n") // returns '"two escape sequences \\a\\n"'
//
// # Replace
//
// Returns a new string based on the target, which replaces the occurrences of a search string
// with a replacement string if present. The function accepts an optional limit on the number of
// substring replacements to be made.
//
// When the replacement limit is 0, the result is the original string. When the limit is a negative
// number, the function behaves the same as replace all.
//
// <string>.replace(<string>, <string>) -> <string>
// <string>.replace(<string>, <string>, <int>) -> <string>
//
// Examples:
//
// 'hello hello'.replace('he', 'we') // returns 'wello wello'
// 'hello hello'.replace('he', 'we', -1) // returns 'wello wello'
// 'hello hello'.replace('he', 'we', 1) // returns 'wello hello'
// 'hello hello'.replace('he', 'we', 0) // returns 'hello hello'
// 'hello hello'.replace('', '_') // returns '_h_e_l_l_o_ _h_e_l_l_o_'
// 'hello hello'.replace('h', '') // returns 'ello ello'
//
// # Split
//
// Returns a list of strings split from the input by the given separator. The function accepts
// an optional argument specifying a limit on the number of substrings produced by the split.
//
// When the split limit is 0, the result is an empty list. When the limit is 1, the result is the
// target string to split. When the limit is a negative number, the function behaves the same as
// split all.
//
// <string>.split(<string>) -> <list<string>>
// <string>.split(<string>, <int>) -> <list<string>>
//
// Examples:
//
// 'hello hello hello'.split(' ') // returns ['hello', 'hello', 'hello']
// 'hello hello hello'.split(' ', 0) // returns []
// 'hello hello hello'.split(' ', 1) // returns ['hello hello hello']
// 'hello hello hello'.split(' ', 2) // returns ['hello', 'hello hello']
// 'hello hello hello'.split(' ', -1) // returns ['hello', 'hello', 'hello']
//
// # Substring
//
// Returns the substring given a numeric range corresponding to character positions. Optionally
// may omit the trailing range for a substring from a given character position until the end of
// a string.
//
// Character offsets are 0-based with an inclusive start range and exclusive end range. It is an
// error to specify an end range that is lower than the start range, or for either the start or end
// index to be negative or exceed the string length.
//
// <string>.substring(<int>) -> <string>
// <string>.substring(<int>, <int>) -> <string>
//
// Examples:
//
// 'tacocat'.substring(4) // returns 'cat'
// 'tacocat'.substring(0, 4) // returns 'taco'
// 'tacocat'.substring(-1) // error
// 'tacocat'.substring(2, 1) // error
//
// # Trim
//
// Returns a new string which removes the leading and trailing whitespace in the target string.
// The trim function uses the Unicode definition of whitespace which does not include the
// zero-width spaces. See: https://en.wikipedia.org/wiki/Whitespace_character#Unicode
//
// <string>.trim() -> <string>
//
// Examples:
//
// ' \ttrim\n '.trim() // returns 'trim'
//
// # UpperAscii
//
// Returns a new string where all ASCII characters are upper-cased.
//
// This function does not perform Unicode case-mapping for characters outside the ASCII range.
//
// <string>.upperAscii() -> <string>
//
// Examples:
//
// 'TacoCat'.upperAscii() // returns 'TACOCAT'
// 'TacoCÆt Xii'.upperAscii() // returns 'TACOCÆT XII'
//
// # Reverse
//
// Introduced at version: 3
//
// Returns a new string whose characters are the same as the target string, only formatted in
// reverse order.
// This function relies on converting strings to rune arrays in order to reverse
//
// <string>.reverse() -> <string>
//
// Examples:
//
// 'gums'.reverse() // returns 'smug'
// 'John Smith'.reverse() // returns 'htimS nhoJ'
func Strings(options ...StringsOption) cel.EnvOption {
s := &stringLib{
version: math.MaxUint32,
validateFormat: true,
}
for _, o := range options {
s = o(s)
}
return cel.Lib(s)
}
type stringLib struct {
locale string
version uint32
validateFormat bool
}
// LibraryName implements the SingletonLibrary interface method.
func (*stringLib) LibraryName() string {
return "cel.lib.ext.strings"
}
// StringsOption is a functional interface for configuring the strings library.
type StringsOption func(*stringLib) *stringLib
// StringsLocale configures the library with the given locale. The locale tag will
// be checked for validity at the time that EnvOptions are configured. If this option
// is not passed, string.format will behave as if en_US was passed as the locale.
func StringsLocale(locale string) StringsOption {
return func(sl *stringLib) *stringLib {
sl.locale = locale
return sl
}
}
// StringsVersion configures the version of the string 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 StringsVersion(version uint32) StringsOption {
return func(lib *stringLib) *stringLib {
lib.version = version
return lib
}
}
// StringsValidateFormatCalls validates type-checked ASTs to ensure that string.format() calls have
// valid formatting clauses and valid argument types for each clause.
//
// Enabled by default.
func StringsValidateFormatCalls(value bool) StringsOption {
return func(s *stringLib) *stringLib {
s.validateFormat = value
return s
}
}
// CompileOptions implements the Library interface method.
func (lib *stringLib) CompileOptions() []cel.EnvOption {
formatLocale := "en_US"
if lib.locale != "" {
// ensure locale is properly-formed if set
_, err := language.Parse(lib.locale)
if err != nil {
return []cel.EnvOption{
func(e *cel.Env) (*cel.Env, error) {
return nil, fmt.Errorf("failed to parse locale: %w", err)
},
}
}
formatLocale = lib.locale
}
opts := []cel.EnvOption{
cel.Function("charAt",
cel.MemberOverload("string_char_at_int", []*cel.Type{cel.StringType, cel.IntType}, cel.StringType,
cel.BinaryBinding(func(str, ind ref.Val) ref.Val {
s := str.(types.String)
i := ind.(types.Int)
return stringOrError(charAt(string(s), int64(i)))
}))),
cel.Function("indexOf",
cel.MemberOverload("string_index_of_string", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType,
cel.BinaryBinding(func(str, substr ref.Val) ref.Val {
s := str.(types.String)
sub := substr.(types.String)
return intOrError(indexOf(string(s), string(sub)))
})),
cel.MemberOverload("string_index_of_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.IntType}, cel.IntType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := args[0].(types.String)
sub := args[1].(types.String)
offset := args[2].(types.Int)
return intOrError(indexOfOffset(string(s), string(sub), int64(offset)))
}))),
cel.Function("lastIndexOf",
cel.MemberOverload("string_last_index_of_string", []*cel.Type{cel.StringType, cel.StringType}, cel.IntType,
cel.BinaryBinding(func(str, substr ref.Val) ref.Val {
s := str.(types.String)
sub := substr.(types.String)
return intOrError(lastIndexOf(string(s), string(sub)))
})),
cel.MemberOverload("string_last_index_of_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.IntType}, cel.IntType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := args[0].(types.String)
sub := args[1].(types.String)
offset := args[2].(types.Int)
return intOrError(lastIndexOfOffset(string(s), string(sub), int64(offset)))
}))),
cel.Function("lowerAscii",
cel.MemberOverload("string_lower_ascii", []*cel.Type{cel.StringType}, cel.StringType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return stringOrError(lowerASCII(string(s)))
}))),
cel.Function("replace",
cel.MemberOverload(
"string_replace_string_string", []*cel.Type{cel.StringType, cel.StringType, cel.StringType}, cel.StringType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
str := args[0].(types.String)
old := args[1].(types.String)
new := args[2].(types.String)
return stringOrError(replace(string(str), string(old), string(new)))
})),
cel.MemberOverload(
"string_replace_string_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.StringType, cel.IntType}, cel.StringType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
str := args[0].(types.String)
old := args[1].(types.String)
new := args[2].(types.String)
n := args[3].(types.Int)
return stringOrError(replaceN(string(str), string(old), string(new), int64(n)))
}))),
cel.Function("split",
cel.MemberOverload("string_split_string", []*cel.Type{cel.StringType, cel.StringType}, cel.ListType(cel.StringType),
cel.BinaryBinding(func(str, separator ref.Val) ref.Val {
s := str.(types.String)
sep := separator.(types.String)
return listStringOrError(split(string(s), string(sep)))
})),
cel.MemberOverload("string_split_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.IntType}, cel.ListType(cel.StringType),
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := args[0].(types.String)
sep := args[1].(types.String)
n := args[2].(types.Int)
return listStringOrError(splitN(string(s), string(sep), int64(n)))
}))),
cel.Function("substring",
cel.MemberOverload("string_substring_int", []*cel.Type{cel.StringType, cel.IntType}, cel.StringType,
cel.BinaryBinding(func(str, offset ref.Val) ref.Val {
s := str.(types.String)
off := offset.(types.Int)
return stringOrError(substr(string(s), int64(off)))
})),
cel.MemberOverload("string_substring_int_int", []*cel.Type{cel.StringType, cel.IntType, cel.IntType}, cel.StringType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := args[0].(types.String)
start := args[1].(types.Int)
end := args[2].(types.Int)
return stringOrError(substrRange(string(s), int64(start), int64(end)))
}))),
cel.Function("trim",
cel.MemberOverload("string_trim", []*cel.Type{cel.StringType}, cel.StringType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return stringOrError(trimSpace(string(s)))
}))),
cel.Function("upperAscii",
cel.MemberOverload("string_upper_ascii", []*cel.Type{cel.StringType}, cel.StringType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return stringOrError(upperASCII(string(s)))
}))),
}
if lib.version >= 1 {
opts = append(opts, cel.Function("format",
cel.MemberOverload("string_format", []*cel.Type{cel.StringType, cel.ListType(cel.DynType)}, cel.StringType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := string(args[0].(types.String))
formatArgs := args[1].(traits.Lister)
return stringOrError(parseFormatString(s, &stringFormatter{}, &stringArgList{formatArgs}, formatLocale))
}))),
cel.Function("strings.quote", cel.Overload("strings_quote", []*cel.Type{cel.StringType}, cel.StringType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return stringOrError(quote(string(s)))
}))),
cel.ASTValidators(stringFormatValidator{}))
}
if lib.version >= 2 {
opts = append(opts,
cel.Function("join",
cel.MemberOverload("list_join", []*cel.Type{cel.ListType(cel.StringType)}, cel.StringType,
cel.UnaryBinding(func(list ref.Val) ref.Val {
l := list.(traits.Lister)
return stringOrError(joinValSeparator(l, ""))
})),
cel.MemberOverload("list_join_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.StringType,
cel.BinaryBinding(func(list, delim ref.Val) ref.Val {
l := list.(traits.Lister)
d := delim.(types.String)
return stringOrError(joinValSeparator(l, string(d)))
}))),
)
} else {
opts = append(opts,
cel.Function("join",
cel.MemberOverload("list_join", []*cel.Type{cel.ListType(cel.StringType)}, cel.StringType,
cel.UnaryBinding(func(list ref.Val) ref.Val {
l, err := list.ConvertToNative(stringListType)
if err != nil {
return types.WrapErr(err)
}
return stringOrError(join(l.([]string)))
})),
cel.MemberOverload("list_join_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.StringType,
cel.BinaryBinding(func(list, delim ref.Val) ref.Val {
l, err := list.ConvertToNative(stringListType)
if err != nil {
return types.WrapErr(err)
}
d := delim.(types.String)
return stringOrError(joinSeparator(l.([]string), string(d)))
}))),
)
}
if lib.version >= 3 {
opts = append(opts,
cel.Function("reverse",
cel.MemberOverload("string_reverse", []*cel.Type{cel.StringType}, cel.StringType,
cel.UnaryBinding(func(str ref.Val) ref.Val {
s := str.(types.String)
return stringOrError(reverse(string(s)))
}))),
)
}
if lib.validateFormat {
opts = append(opts, cel.ASTValidators(stringFormatValidator{}))
}
return opts
}
// ProgramOptions implements the Library interface method.
func (*stringLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func charAt(str string, ind int64) (string, error) {
i := int(ind)
runes := []rune(str)
if i < 0 || i > len(runes) {
return "", fmt.Errorf("index out of range: %d", ind)
}
if i == len(runes) {
return "", nil
}
return string(runes[i]), nil
}
func indexOf(str, substr string) (int64, error) {
return indexOfOffset(str, substr, int64(0))
}
func indexOfOffset(str, substr string, offset int64) (int64, error) {
if substr == "" {
return offset, nil
}
off := int(offset)
runes := []rune(str)
subrunes := []rune(substr)
if off < 0 {
return -1, fmt.Errorf("index out of range: %d", off)
}
// If the offset exceeds the length, return -1 rather than error.
if off >= len(runes) {
return -1, nil
}
for i := off; i < len(runes)-(len(subrunes)-1); i++ {
found := true
for j := 0; j < len(subrunes); j++ {
if runes[i+j] != subrunes[j] {
found = false
break
}
}
if found {
return int64(i), nil
}
}
return -1, nil
}
func lastIndexOf(str, substr string) (int64, error) {
runes := []rune(str)
if substr == "" {
return int64(len(runes)), nil
}
return lastIndexOfOffset(str, substr, int64(len(runes)-1))
}
func lastIndexOfOffset(str, substr string, offset int64) (int64, error) {
if substr == "" {
return offset, nil
}
off := int(offset)
runes := []rune(str)
subrunes := []rune(substr)
if off < 0 {
return -1, fmt.Errorf("index out of range: %d", off)
}
// If the offset is far greater than the length return -1
if off >= len(runes) {
return -1, nil
}
if off > len(runes)-len(subrunes) {
off = len(runes) - len(subrunes)
}
for i := off; i >= 0; i-- {
found := true
for j := 0; j < len(subrunes); j++ {
if runes[i+j] != subrunes[j] {
found = false
break
}
}
if found {
return int64(i), nil
}
}
return -1, nil
}
func lowerASCII(str string) (string, error) {
runes := []rune(str)
for i, r := range runes {
if r <= unicode.MaxASCII {
r = unicode.ToLower(r)
runes[i] = r
}
}
return string(runes), nil
}
func replace(str, old, new string) (string, error) {
return strings.ReplaceAll(str, old, new), nil
}
func replaceN(str, old, new string, n int64) (string, error) {
return strings.Replace(str, old, new, int(n)), nil
}
func split(str, sep string) ([]string, error) {
return strings.Split(str, sep), nil
}
func splitN(str, sep string, n int64) ([]string, error) {
return strings.SplitN(str, sep, int(n)), nil
}
func substr(str string, start int64) (string, error) {
runes := []rune(str)
if int(start) < 0 || int(start) > len(runes) {
return "", fmt.Errorf("index out of range: %d", start)
}
return string(runes[start:]), nil
}
func substrRange(str string, start, end int64) (string, error) {
runes := []rune(str)
l := len(runes)
if start > end {
return "", fmt.Errorf("invalid substring range. start: %d, end: %d", start, end)
}
if int(start) < 0 || int(start) > l {
return "", fmt.Errorf("index out of range: %d", start)
}
if int(end) < 0 || int(end) > l {
return "", fmt.Errorf("index out of range: %d", end)
}
return string(runes[int(start):int(end)]), nil
}
func trimSpace(str string) (string, error) {
return strings.TrimSpace(str), nil
}
func upperASCII(str string) (string, error) {
runes := []rune(str)
for i, r := range runes {
if r <= unicode.MaxASCII {
r = unicode.ToUpper(r)
runes[i] = r
}
}
return string(runes), nil
}
func reverse(str string) (string, error) {
chars := []rune(str)
for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 {
chars[i], chars[j] = chars[j], chars[i]
}
return string(chars), nil
}
func joinSeparator(strs []string, separator string) (string, error) {
return strings.Join(strs, separator), nil
}
func join(strs []string) (string, error) {
return strings.Join(strs, ""), nil
}
func joinValSeparator(strs traits.Lister, separator string) (string, error) {
sz := strs.Size().(types.Int)
var sb strings.Builder
for i := types.Int(0); i < sz; i++ {
if i != 0 {
sb.WriteString(separator)
}
elem := strs.Get(i)
str, ok := elem.(types.String)
if !ok {
return "", fmt.Errorf("join: invalid input: %v", elem)
}
sb.WriteString(string(str))
}
return sb.String(), nil
}
// quote implements a string quoting function. The string will be wrapped in
// double quotes, and all valid CEL escape sequences will be escaped to show up
// literally if printed. If the input contains any invalid UTF-8, the invalid runes
// will be replaced with utf8.RuneError.
func quote(s string) (string, error) {
var quotedStrBuilder strings.Builder
for _, c := range sanitize(s) {
switch c {
case '\a':
quotedStrBuilder.WriteString("\\a")
case '\b':
quotedStrBuilder.WriteString("\\b")
case '\f':
quotedStrBuilder.WriteString("\\f")
case '\n':
quotedStrBuilder.WriteString("\\n")
case '\r':
quotedStrBuilder.WriteString("\\r")
case '\t':
quotedStrBuilder.WriteString("\\t")
case '\v':
quotedStrBuilder.WriteString("\\v")
case '\\':
quotedStrBuilder.WriteString("\\\\")
case '"':
quotedStrBuilder.WriteString("\\\"")
default:
quotedStrBuilder.WriteRune(c)
}
}
escapedStr := quotedStrBuilder.String()
return "\"" + escapedStr + "\"", nil
}
// sanitize replaces all invalid runes in the given string with utf8.RuneError.
func sanitize(s string) string {
var sanitizedStringBuilder strings.Builder
for _, r := range s {
if !utf8.ValidRune(r) {
sanitizedStringBuilder.WriteRune(utf8.RuneError)
} else {
sanitizedStringBuilder.WriteRune(r)
}
}
return sanitizedStringBuilder.String()
}
var (
stringListType = reflect.TypeOf([]string{})
)