mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
rebase: update kubernetes to 1.28.0 in main
updating kubernetes to 1.28.0 in the main repo. Signed-off-by: Madhu Rajanna <madhupr007@gmail.com>
This commit is contained in:
committed by
mergify[bot]
parent
b2fdc269c3
commit
ff3e84ad67
32
vendor/github.com/google/cel-go/ext/BUILD.bazel
generated
vendored
32
vendor/github.com/google/cel-go/ext/BUILD.bazel
generated
vendored
@ -9,14 +9,30 @@ go_library(
|
||||
srcs = [
|
||||
"encoders.go",
|
||||
"guards.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/decls:go_default_library",
|
||||
"//common:go_default_library",
|
||||
"//common/overloads: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",
|
||||
"@org_golang_google_genproto_googleapis_api//expr/v1alpha1: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",
|
||||
],
|
||||
)
|
||||
|
||||
@ -25,6 +41,10 @@ go_test(
|
||||
size = "small",
|
||||
srcs = [
|
||||
"encoders_test.go",
|
||||
"math_test.go",
|
||||
"native_test.go",
|
||||
"protos_test.go",
|
||||
"sets_test.go",
|
||||
"strings_test.go",
|
||||
],
|
||||
embed = [
|
||||
@ -32,5 +52,17 @@ go_test(
|
||||
],
|
||||
deps = [
|
||||
"//cel:go_default_library",
|
||||
"//checker:go_default_library",
|
||||
"//common: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_genproto_googleapis_api//expr/v1alpha1: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",
|
||||
],
|
||||
)
|
||||
|
205
vendor/github.com/google/cel-go/ext/README.md
generated
vendored
205
vendor/github.com/google/cel-go/ext/README.md
generated
vendored
@ -3,6 +3,30 @@
|
||||
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.
|
||||
@ -31,6 +55,156 @@ 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
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
@ -70,6 +244,23 @@ Examples:
|
||||
'hello mellow'.indexOf('ello', 2) // returns 7
|
||||
'hello mellow'.indexOf('ello', 20) // 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
|
||||
@ -105,6 +296,20 @@ 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
|
||||
|
100
vendor/github.com/google/cel-go/ext/bindings.go
generated
vendored
Normal file
100
vendor/github.com/google/cel-go/ext/bindings.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
// 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 (
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// 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() cel.EnvOption {
|
||||
return cel.Lib(celBindings{})
|
||||
}
|
||||
|
||||
const (
|
||||
celNamespace = "cel"
|
||||
bindMacro = "bind"
|
||||
unusedIterVar = "#unused"
|
||||
)
|
||||
|
||||
type celBindings struct{}
|
||||
|
||||
func (celBindings) LibraryName() string {
|
||||
return "cel.lib.ext.cel.bindings"
|
||||
}
|
||||
|
||||
func (celBindings) CompileOptions() []cel.EnvOption {
|
||||
return []cel.EnvOption{
|
||||
cel.Macros(
|
||||
// cel.bind(var, <init>, <expr>)
|
||||
cel.NewReceiverMacro(bindMacro, 3, celBind),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (celBindings) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func celBind(meh cel.MacroExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
|
||||
if !macroTargetMatchesNamespace(celNamespace, target) {
|
||||
return nil, nil
|
||||
}
|
||||
varIdent := args[0]
|
||||
varName := ""
|
||||
switch varIdent.GetExprKind().(type) {
|
||||
case *exprpb.Expr_IdentExpr:
|
||||
varName = varIdent.GetIdentExpr().GetName()
|
||||
default:
|
||||
return nil, &common.Error{
|
||||
Message: "cel.bind() variable names must be simple identifers",
|
||||
Location: meh.OffsetLocation(varIdent.GetId()),
|
||||
}
|
||||
}
|
||||
varInit := args[1]
|
||||
resultExpr := args[2]
|
||||
return meh.Fold(
|
||||
unusedIterVar,
|
||||
meh.NewList(),
|
||||
varName,
|
||||
varInit,
|
||||
meh.LiteralBool(false),
|
||||
meh.Ident(varName),
|
||||
resultExpr,
|
||||
), nil
|
||||
}
|
18
vendor/github.com/google/cel-go/ext/encoders.go
generated
vendored
18
vendor/github.com/google/cel-go/ext/encoders.go
generated
vendored
@ -26,34 +26,38 @@ import (
|
||||
// Encoders returns a cel.EnvOption to configure extended functions for string, byte, and object
|
||||
// encodings.
|
||||
//
|
||||
// Base64.Decode
|
||||
// # 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>
|
||||
// base64.decode(<string>) -> <bytes>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// base64.decode('aGVsbG8=') // return b'hello'
|
||||
// base64.decode('aGVsbG8') // error
|
||||
// base64.decode('aGVsbG8=') // return b'hello'
|
||||
// base64.decode('aGVsbG8') // error
|
||||
//
|
||||
// Base64.Encode
|
||||
// # Base64.Encode
|
||||
//
|
||||
// Encodes bytes to a base64-encoded string.
|
||||
//
|
||||
// base64.encode(<bytes>) -> <string>
|
||||
// base64.encode(<bytes>) -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// base64.encode(b'hello') // return b'aGVsbG8='
|
||||
// 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",
|
||||
|
13
vendor/github.com/google/cel-go/ext/guards.go
generated
vendored
13
vendor/github.com/google/cel-go/ext/guards.go
generated
vendored
@ -17,6 +17,7 @@ package ext
|
||||
import (
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// function invocation guards for common call signatures within extension functions.
|
||||
@ -48,3 +49,15 @@ func listStringOrError(strs []string, err error) ref.Val {
|
||||
}
|
||||
return types.DefaultTypeAdapter.NativeToValue(strs)
|
||||
}
|
||||
|
||||
func macroTargetMatchesNamespace(ns string, target *exprpb.Expr) bool {
|
||||
switch target.GetExprKind().(type) {
|
||||
case *exprpb.Expr_IdentExpr:
|
||||
if target.GetIdentExpr().GetName() != ns {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
388
vendor/github.com/google/cel-go/ext/math.go
generated
vendored
Normal file
388
vendor/github.com/google/cel-go/ext/math.go
generated
vendored
Normal file
@ -0,0 +1,388 @@
|
||||
// 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"
|
||||
"strings"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// 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
|
||||
func Math() cel.EnvOption {
|
||||
return cel.Lib(mathLib{})
|
||||
}
|
||||
|
||||
const (
|
||||
mathNamespace = "math"
|
||||
leastMacro = "least"
|
||||
greatestMacro = "greatest"
|
||||
minFunc = "math.@min"
|
||||
maxFunc = "math.@max"
|
||||
)
|
||||
|
||||
type mathLib struct{}
|
||||
|
||||
// LibraryName implements the SingletonLibrary interface method.
|
||||
func (mathLib) LibraryName() string {
|
||||
return "cel.lib.ext.math"
|
||||
}
|
||||
|
||||
// CompileOptions implements the Library interface method.
|
||||
func (mathLib) CompileOptions() []cel.EnvOption {
|
||||
return []cel.EnvOption{
|
||||
cel.Macros(
|
||||
// math.least(num, ...)
|
||||
cel.NewReceiverVarArgMacro(leastMacro, mathLeast),
|
||||
// math.greatest(num, ...)
|
||||
cel.NewReceiverVarArgMacro(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)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// ProgramOptions implements the Library interface method.
|
||||
func (mathLib) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func mathLeast(meh cel.MacroExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
|
||||
if !macroTargetMatchesNamespace(mathNamespace, target) {
|
||||
return nil, nil
|
||||
}
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return nil, &common.Error{
|
||||
Message: "math.least() requires at least one argument",
|
||||
Location: meh.OffsetLocation(target.GetId()),
|
||||
}
|
||||
case 1:
|
||||
if isListLiteralWithValidArgs(args[0]) || isValidArgType(args[0]) {
|
||||
return meh.GlobalCall(minFunc, args[0]), nil
|
||||
}
|
||||
return nil, &common.Error{
|
||||
Message: "math.least() invalid single argument value",
|
||||
Location: meh.OffsetLocation(args[0].GetId()),
|
||||
}
|
||||
case 2:
|
||||
err := checkInvalidArgs(meh, "math.least()", args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return meh.GlobalCall(minFunc, args...), nil
|
||||
default:
|
||||
err := checkInvalidArgs(meh, "math.least()", args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return meh.GlobalCall(minFunc, meh.NewList(args...)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func mathGreatest(meh cel.MacroExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
|
||||
if !macroTargetMatchesNamespace(mathNamespace, target) {
|
||||
return nil, nil
|
||||
}
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return nil, &common.Error{
|
||||
Message: "math.greatest() requires at least one argument",
|
||||
Location: meh.OffsetLocation(target.GetId()),
|
||||
}
|
||||
case 1:
|
||||
if isListLiteralWithValidArgs(args[0]) || isValidArgType(args[0]) {
|
||||
return meh.GlobalCall(maxFunc, args[0]), nil
|
||||
}
|
||||
return nil, &common.Error{
|
||||
Message: "math.greatest() invalid single argument value",
|
||||
Location: meh.OffsetLocation(args[0].GetId()),
|
||||
}
|
||||
case 2:
|
||||
err := checkInvalidArgs(meh, "math.greatest()", args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return meh.GlobalCall(maxFunc, args...), nil
|
||||
default:
|
||||
err := checkInvalidArgs(meh, "math.greatest()", args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return meh.GlobalCall(maxFunc, meh.NewList(args...)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func identity(val ref.Val) ref.Val {
|
||||
return val
|
||||
}
|
||||
|
||||
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.MacroExprHelper, funcName string, args []*exprpb.Expr) *common.Error {
|
||||
for _, arg := range args {
|
||||
err := checkInvalidArgLiteral(funcName, arg)
|
||||
if err != nil {
|
||||
return &common.Error{
|
||||
Message: err.Error(),
|
||||
Location: meh.OffsetLocation(arg.GetId()),
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkInvalidArgLiteral(funcName string, arg *exprpb.Expr) error {
|
||||
if !isValidArgType(arg) {
|
||||
return fmt.Errorf("%s simple literal arguments must be numeric", funcName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isValidArgType(arg *exprpb.Expr) bool {
|
||||
switch arg.GetExprKind().(type) {
|
||||
case *exprpb.Expr_ConstExpr:
|
||||
c := arg.GetConstExpr()
|
||||
switch c.GetConstantKind().(type) {
|
||||
case *exprpb.Constant_DoubleValue, *exprpb.Constant_Int64Value, *exprpb.Constant_Uint64Value:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case *exprpb.Expr_ListExpr, *exprpb.Expr_StructExpr:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func isListLiteralWithValidArgs(arg *exprpb.Expr) bool {
|
||||
switch arg.GetExprKind().(type) {
|
||||
case *exprpb.Expr_ListExpr:
|
||||
list := arg.GetListExpr()
|
||||
if len(list.GetElements()) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, e := range list.GetElements() {
|
||||
if !isValidArgType(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
|
||||
}
|
574
vendor/github.com/google/cel-go/ext/native.go
generated
vendored
Normal file
574
vendor/github.com/google/cel-go/ext/native.go
generated
vendored
Normal file
@ -0,0 +1,574 @@
|
||||
// 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"
|
||||
"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/checker/decls"
|
||||
"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"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
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.
|
||||
func NativeTypes(refTypes ...any) cel.EnvOption {
|
||||
return func(env *cel.Env) (*cel.Env, error) {
|
||||
tp, err := newNativeTypeProvider(env.TypeAdapter(), env.TypeProvider(), refTypes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env, err = cel.CustomTypeAdapter(tp)(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cel.CustomTypeProvider(tp)(env)
|
||||
}
|
||||
}
|
||||
|
||||
func newNativeTypeProvider(adapter ref.TypeAdapter, provider ref.TypeProvider, refTypes ...any) (*nativeTypeProvider, error) {
|
||||
nativeTypes := make(map[string]*nativeType, len(refTypes))
|
||||
for _, refType := range refTypes {
|
||||
switch rt := refType.(type) {
|
||||
case reflect.Type:
|
||||
t, err := newNativeType(rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nativeTypes[t.TypeName()] = t
|
||||
case reflect.Value:
|
||||
t, err := newNativeType(rt.Type())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nativeTypes[t.TypeName()] = t
|
||||
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,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type nativeTypeProvider struct {
|
||||
nativeTypes map[string]*nativeType
|
||||
baseAdapter ref.TypeAdapter
|
||||
baseProvider ref.TypeProvider
|
||||
}
|
||||
|
||||
// EnumValue proxies to the ref.TypeProvider 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 ref.TypeProvider.
|
||||
func (tp *nativeTypeProvider) FindIdent(typeName string) (ref.Val, bool) {
|
||||
if t, found := tp.nativeTypes[typeName]; found {
|
||||
return t, true
|
||||
}
|
||||
return tp.baseProvider.FindIdent(typeName)
|
||||
}
|
||||
|
||||
// FindType looks up CEL type-checker type definition by qualified identifier, and if not found
|
||||
// proxies to the composed ref.TypeProvider.
|
||||
func (tp *nativeTypeProvider) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
if _, found := tp.nativeTypes[typeName]; found {
|
||||
return decls.NewTypeType(decls.NewObjectType(typeName)), true
|
||||
}
|
||||
return tp.baseProvider.FindType(typeName)
|
||||
}
|
||||
|
||||
// FindFieldType looks up a native type's field definition, and if the type name is not a native
|
||||
// type then proxies to the composed ref.TypeProvider
|
||||
func (tp *nativeTypeProvider) FindFieldType(typeName, fieldName string) (*ref.FieldType, bool) {
|
||||
t, found := tp.nativeTypes[typeName]
|
||||
if !found {
|
||||
return tp.baseProvider.FindFieldType(typeName, fieldName)
|
||||
}
|
||||
refField, isDefined := t.hasField(fieldName)
|
||||
if !found || !isDefined {
|
||||
return nil, false
|
||||
}
|
||||
exprType, ok := convertToExprType(refField.Type)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return &ref.FieldType{
|
||||
Type: exprType,
|
||||
IsSet: func(obj any) bool {
|
||||
refVal := reflect.Indirect(reflect.ValueOf(obj))
|
||||
refField := refVal.FieldByName(fieldName)
|
||||
return !refField.IsZero()
|
||||
},
|
||||
GetFrom: func(obj any) (any, error) {
|
||||
refVal := reflect.Indirect(reflect.ValueOf(obj))
|
||||
refField := refVal.FieldByName(fieldName)
|
||||
return getFieldValue(tp, 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:
|
||||
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 newNativeObject(tp, val, rawVal)
|
||||
}
|
||||
default:
|
||||
return tp.baseAdapter.NativeToValue(val)
|
||||
}
|
||||
}
|
||||
|
||||
// convertToExprType converts the Golang reflect.Type to a protobuf exprpb.Type.
|
||||
func convertToExprType(refType reflect.Type) (*exprpb.Type, bool) {
|
||||
switch refType.Kind() {
|
||||
case reflect.Bool:
|
||||
return decls.Bool, true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return decls.Double, true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if refType == durationType {
|
||||
return decls.Duration, true
|
||||
}
|
||||
return decls.Int, true
|
||||
case reflect.String:
|
||||
return decls.String, true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return decls.Uint, true
|
||||
case reflect.Array, reflect.Slice:
|
||||
refElem := refType.Elem()
|
||||
if refElem == reflect.TypeOf(byte(0)) {
|
||||
return decls.Bytes, true
|
||||
}
|
||||
elemType, ok := convertToExprType(refElem)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return decls.NewListType(elemType), true
|
||||
case reflect.Map:
|
||||
keyType, ok := convertToExprType(refType.Key())
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
// Ensure the key type is a int, bool, uint, string
|
||||
elemType, ok := convertToExprType(refType.Elem())
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return decls.NewMapType(keyType, elemType), true
|
||||
case reflect.Struct:
|
||||
if refType == timestampType {
|
||||
return decls.Timestamp, true
|
||||
}
|
||||
return decls.NewObjectType(
|
||||
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 decls.NewObjectType(string(pbMsg.ProtoReflect().Descriptor().FullName())), true
|
||||
}
|
||||
return convertToExprType(refType.Elem())
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func newNativeObject(adapter ref.TypeAdapter, val any, refValue reflect.Value) ref.Val {
|
||||
valType, err := newNativeType(refValue.Type())
|
||||
if err != nil {
|
||||
return types.NewErr(err.Error())
|
||||
}
|
||||
return &nativeObj{
|
||||
TypeAdapter: adapter,
|
||||
val: val,
|
||||
valType: valType,
|
||||
refValue: refValue,
|
||||
}
|
||||
}
|
||||
|
||||
type nativeObj struct {
|
||||
ref.TypeAdapter
|
||||
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
|
||||
}
|
||||
fieldCELVal := o.NativeToValue(fieldValue.Interface())
|
||||
fieldJSONVal, err := fieldCELVal.ConvertToNative(jsonValueType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields[fieldType.Name] = 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 newNativeType(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)
|
||||
}
|
||||
return &nativeType{
|
||||
typeName: fmt.Sprintf("%s.%s", simplePkgAlias(refType.PkgPath()), refType.Name()),
|
||||
refType: refType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type nativeType struct {
|
||||
typeName string
|
||||
refType reflect.Type
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// hasField returns whether a field name has a corresponding Golang reflect.StructField
|
||||
func (t *nativeType) hasField(fieldName string) (reflect.StructField, bool) {
|
||||
f, found := t.refType.FieldByName(fieldName)
|
||||
if !found || !f.IsExported() || !isSupportedType(f.Type) {
|
||||
return reflect.StructField{}, false
|
||||
}
|
||||
return f, true
|
||||
}
|
||||
|
||||
func adaptFieldValue(adapter ref.TypeAdapter, refField reflect.Value) ref.Val {
|
||||
return adapter.NativeToValue(getFieldValue(adapter, refField))
|
||||
}
|
||||
|
||||
func getFieldValue(adapter ref.TypeAdapter, refField reflect.Value) any {
|
||||
if refField.IsZero() {
|
||||
switch refField.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return types.NewDynamicList(adapter, []ref.Val{})
|
||||
case reflect.Map:
|
||||
return types.NewDynamicMap(adapter, map[ref.Val]ref.Val{})
|
||||
case reflect.Struct:
|
||||
if refField.Type() == timestampType {
|
||||
return types.Timestamp{Time: time.Unix(0, 0)}
|
||||
}
|
||||
return reflect.New(refField.Type()).Elem().Interface()
|
||||
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)
|
||||
)
|
145
vendor/github.com/google/cel-go/ext/protos.go
generated
vendored
Normal file
145
vendor/github.com/google/cel-go/ext/protos.go
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
// 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"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// 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.NewReceiverMacro(getExtension, 2, getProtoExt),
|
||||
// proto.hasExt(msg, select_expression)
|
||||
cel.NewReceiverMacro(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(meh cel.MacroExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
|
||||
if !macroTargetMatchesNamespace(protoNamespace, target) {
|
||||
return nil, nil
|
||||
}
|
||||
extensionField, err := getExtFieldName(meh, args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return meh.PresenceTest(args[0], extensionField), nil
|
||||
}
|
||||
|
||||
// getProtoExt generates a select expression for a fully-qualified extension name on a protobuf message.
|
||||
func getProtoExt(meh cel.MacroExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
|
||||
if !macroTargetMatchesNamespace(protoNamespace, target) {
|
||||
return nil, nil
|
||||
}
|
||||
extFieldName, err := getExtFieldName(meh, args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return meh.Select(args[0], extFieldName), nil
|
||||
}
|
||||
|
||||
func getExtFieldName(meh cel.MacroExprHelper, expr *exprpb.Expr) (string, *common.Error) {
|
||||
isValid := false
|
||||
extensionField := ""
|
||||
switch expr.GetExprKind().(type) {
|
||||
case *exprpb.Expr_SelectExpr:
|
||||
extensionField, isValid = validateIdentifier(expr)
|
||||
}
|
||||
if !isValid {
|
||||
return "", &common.Error{
|
||||
Message: "invalid extension field",
|
||||
Location: meh.OffsetLocation(expr.GetId()),
|
||||
}
|
||||
}
|
||||
return extensionField, nil
|
||||
}
|
||||
|
||||
func validateIdentifier(expr *exprpb.Expr) (string, bool) {
|
||||
switch expr.GetExprKind().(type) {
|
||||
case *exprpb.Expr_IdentExpr:
|
||||
return expr.GetIdentExpr().GetName(), true
|
||||
case *exprpb.Expr_SelectExpr:
|
||||
sel := expr.GetSelectExpr()
|
||||
if sel.GetTestOnly() {
|
||||
return "", false
|
||||
}
|
||||
opStr, isIdent := validateIdentifier(sel.GetOperand())
|
||||
if !isIdent {
|
||||
return "", false
|
||||
}
|
||||
return opStr + "." + sel.GetField(), true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
138
vendor/github.com/google/cel-go/ext/sets.go
generated
vendored
Normal file
138
vendor/github.com/google/cel-go/ext/sets.go
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
// 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 (
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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))),
|
||||
}
|
||||
}
|
||||
|
||||
// ProgramOptions implements the Library interface method.
|
||||
func (setsLib) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
778
vendor/github.com/google/cel-go/ext/strings.go
generated
vendored
778
vendor/github.com/google/cel-go/ext/strings.go
generated
vendored
@ -19,32 +19,92 @@ package ext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
|
||||
"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"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
)
|
||||
|
||||
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
|
||||
// # 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>
|
||||
// <string>.charAt(<int>) -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'hello'.charAt(4) // return 'o'
|
||||
// 'hello'.charAt(5) // return ''
|
||||
// 'hello'.charAt(-1) // error
|
||||
// 'hello'.charAt(4) // return 'o'
|
||||
// 'hello'.charAt(5) // return ''
|
||||
// 'hello'.charAt(-1) // error
|
||||
//
|
||||
// IndexOf
|
||||
// # 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 (an integer to `%s`) 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.
|
||||
@ -52,19 +112,19 @@ import (
|
||||
// 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>
|
||||
// <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) // error
|
||||
// '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) // error
|
||||
//
|
||||
// Join
|
||||
// # Join
|
||||
//
|
||||
// Returns a new string where the elements of string list are concatenated.
|
||||
//
|
||||
@ -75,12 +135,12 @@ import (
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ['hello', 'mellow'].join() // returns 'hellomellow'
|
||||
// ['hello', 'mellow'].join(' ') // returns 'hello mellow'
|
||||
// [].join() // returns ''
|
||||
// [].join('/') // returns ''
|
||||
// ['hello', 'mellow'].join() // returns 'hellomellow'
|
||||
// ['hello', 'mellow'].join(' ') // returns 'hello mellow'
|
||||
// [].join() // returns ''
|
||||
// [].join('/') // returns ''
|
||||
//
|
||||
// LastIndexOf
|
||||
// # 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.
|
||||
@ -89,31 +149,45 @@ import (
|
||||
// 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>
|
||||
// <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', -1) // error
|
||||
// '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', -1) // error
|
||||
//
|
||||
// LowerAscii
|
||||
// # 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>
|
||||
// <string>.lowerAscii() -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'TacoCat'.lowerAscii() // returns 'tacocat'
|
||||
// 'TacoCÆt Xii'.lowerAscii() // returns 'tacocÆt xii'
|
||||
// 'TacoCat'.lowerAscii() // returns 'tacocat'
|
||||
// 'TacoCÆt Xii'.lowerAscii() // returns 'tacocÆt xii'
|
||||
//
|
||||
// Replace
|
||||
// # 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
|
||||
@ -122,17 +196,17 @@ import (
|
||||
// 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>
|
||||
// <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('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
|
||||
// # 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.
|
||||
@ -141,18 +215,18 @@ import (
|
||||
// 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>>
|
||||
// <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']
|
||||
// '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
|
||||
// # 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
|
||||
@ -162,48 +236,102 @@ import (
|
||||
// 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>
|
||||
// <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
|
||||
// 'tacocat'.substring(4) // returns 'cat'
|
||||
// 'tacocat'.substring(0, 4) // returns 'taco'
|
||||
// 'tacocat'.substring(-1) // error
|
||||
// 'tacocat'.substring(2, 1) // error
|
||||
//
|
||||
// Trim
|
||||
// # 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>
|
||||
// <string>.trim() -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ' \ttrim\n '.trim() // returns 'trim'
|
||||
// ' \ttrim\n '.trim() // returns 'trim'
|
||||
//
|
||||
// UpperAscii
|
||||
// # 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>
|
||||
// <string>.upperAscii() -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'TacoCat'.upperAscii() // returns 'TACOCAT'
|
||||
// 'TacoCÆt Xii'.upperAscii() // returns 'TACOCÆT XII'
|
||||
func Strings() cel.EnvOption {
|
||||
return cel.Lib(stringLib{})
|
||||
// 'TacoCat'.upperAscii() // returns 'TACOCAT'
|
||||
// 'TacoCÆt Xii'.upperAscii() // returns 'TACOCÆT XII'
|
||||
func Strings(options ...StringsOption) cel.EnvOption {
|
||||
s := &stringLib{version: math.MaxUint32}
|
||||
for _, o := range options {
|
||||
s = o(s)
|
||||
}
|
||||
return cel.Lib(s)
|
||||
}
|
||||
|
||||
type stringLib struct{}
|
||||
type stringLib struct {
|
||||
locale string
|
||||
version uint32
|
||||
}
|
||||
|
||||
func (stringLib) CompileOptions() []cel.EnvOption {
|
||||
return []cel.EnvOption{
|
||||
// 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. See the library documentation to determine
|
||||
// which version a function was introduced at. If the documentation does not
|
||||
// state which version a function was introduced at, it can be assumed to be
|
||||
// introduced at version 0, when the library was first created.
|
||||
// If this option is not set, all functions are available.
|
||||
func StringsVersion(version uint32) func(lib *stringLib) *stringLib {
|
||||
return func(sl *stringLib) *stringLib {
|
||||
sl.version = version
|
||||
return sl
|
||||
}
|
||||
}
|
||||
|
||||
// CompileOptions implements the Library interface method.
|
||||
func (sl *stringLib) CompileOptions() []cel.EnvOption {
|
||||
formatLocale := "en_US"
|
||||
if sl.locale != "" {
|
||||
// ensure locale is properly-formed if set
|
||||
_, err := language.Parse(sl.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 = sl.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 {
|
||||
@ -303,28 +431,64 @@ func (stringLib) CompileOptions() []cel.EnvOption {
|
||||
s := str.(types.String)
|
||||
return stringOrError(upperASCII(string(s)))
|
||||
}))),
|
||||
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.NewErr(err.Error())
|
||||
}
|
||||
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.NewErr(err.Error())
|
||||
}
|
||||
d := delim.(types.String)
|
||||
return stringOrError(joinSeparator(l.([]string), string(d)))
|
||||
}))),
|
||||
}
|
||||
if sl.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(interpreter.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)))
|
||||
}))))
|
||||
|
||||
}
|
||||
if sl.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.NewErr(err.Error())
|
||||
}
|
||||
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.NewErr(err.Error())
|
||||
}
|
||||
d := delim.(types.String)
|
||||
return stringOrError(joinSeparator(l.([]string), string(d)))
|
||||
}))),
|
||||
)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (stringLib) ProgramOptions() []cel.ProgramOption {
|
||||
// ProgramOptions implements the Library interface method.
|
||||
func (*stringLib) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
@ -478,6 +642,452 @@ 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
|
||||
}
|
||||
|
||||
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 "", fmt.Errorf("string clause can only be used on strings, bools, bytes, ints, doubles, maps, lists, types, durations, and timestamps, was given %s", 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 "", fmt.Errorf("decimal clause can only be used on integers, was given %s", 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
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
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 "", fmt.Errorf("fixed-point clause can only be used on doubles, was given %s", 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 "", fmt.Errorf("scientific clause can only be used on doubles, was given %s", 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())
|
||||
}
|
||||
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 "", fmt.Errorf("only integers and bools can be formatted as binary, was given %s", 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 "", fmt.Errorf("only integers, byte buffers, and strings can be formatted as hex, was given %s", 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 "", fmt.Errorf("octal clause can only be used on integers, was given %s", arg.Type().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
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) ArgSize() int64 {
|
||||
return c.args.Size().Value().(int64)
|
||||
}
|
||||
|
||||
var (
|
||||
stringListType = reflect.TypeOf([]string{})
|
||||
)
|
||||
|
Reference in New Issue
Block a user