mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
rebase: bump k8s.io/kubernetes from 1.26.2 to 1.27.2
Bumps [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) from 1.26.2 to 1.27.2. - [Release notes](https://github.com/kubernetes/kubernetes/releases) - [Commits](https://github.com/kubernetes/kubernetes/compare/v1.26.2...v1.27.2) --- updated-dependencies: - dependency-name: k8s.io/kubernetes dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
mergify[bot]
parent
0e79135419
commit
07b05616a0
47
vendor/k8s.io/apiserver/pkg/cel/errors.go
generated
vendored
Normal file
47
vendor/k8s.io/apiserver/pkg/cel/errors.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
|
||||
// Error is an implementation of the 'error' interface, which represents a
|
||||
// XValidation error.
|
||||
type Error struct {
|
||||
Type ErrorType
|
||||
Detail string
|
||||
}
|
||||
|
||||
var _ error = &Error{}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (v *Error) Error() string {
|
||||
return v.Detail
|
||||
}
|
||||
|
||||
// ErrorType is a machine readable value providing more detail about why
|
||||
// a XValidation is invalid.
|
||||
type ErrorType string
|
||||
|
||||
const (
|
||||
// ErrorTypeRequired is used to report withNullable values that are not
|
||||
// provided (e.g. empty strings, null values, or empty arrays). See
|
||||
// Required().
|
||||
ErrorTypeRequired ErrorType = "RuleRequired"
|
||||
// ErrorTypeInvalid is used to report malformed values
|
||||
ErrorTypeInvalid ErrorType = "RuleInvalid"
|
||||
// ErrorTypeInternal is used to report other errors that are not related
|
||||
// to user input. See InternalError().
|
||||
ErrorTypeInternal ErrorType = "InternalError"
|
||||
)
|
170
vendor/k8s.io/apiserver/pkg/cel/escaping.go
generated
vendored
Normal file
170
vendor/k8s.io/apiserver/pkg/cel/escaping.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// celReservedSymbols is a list of RESERVED symbols defined in the CEL lexer.
|
||||
// No identifiers are allowed to collide with these symbols.
|
||||
// https://github.com/google/cel-spec/blob/master/doc/langdef.md#syntax
|
||||
var celReservedSymbols = sets.NewString(
|
||||
"true", "false", "null", "in",
|
||||
"as", "break", "const", "continue", "else",
|
||||
"for", "function", "if", "import", "let",
|
||||
"loop", "package", "namespace", "return", // !! 'namespace' is used heavily in Kubernetes
|
||||
"var", "void", "while",
|
||||
)
|
||||
|
||||
// expandMatcher matches the escape sequence, characters that are escaped, and characters that are unsupported
|
||||
var expandMatcher = regexp.MustCompile(`(__|[-./]|[^a-zA-Z0-9-./_])`)
|
||||
|
||||
// newCharacterFilter returns a boolean array to indicate the allowed characters
|
||||
func newCharacterFilter(characters string) []bool {
|
||||
maxChar := 0
|
||||
for _, c := range characters {
|
||||
if maxChar < int(c) {
|
||||
maxChar = int(c)
|
||||
}
|
||||
}
|
||||
filter := make([]bool, maxChar+1)
|
||||
|
||||
for _, c := range characters {
|
||||
filter[int(c)] = true
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
type escapeCheck struct {
|
||||
canSkipRegex bool
|
||||
invalidCharFound bool
|
||||
}
|
||||
|
||||
// skipRegexCheck checks if escape would be skipped.
|
||||
// if invalidCharFound is true, it must have invalid character; if invalidCharFound is false, not sure if it has invalid character or not
|
||||
func skipRegexCheck(ident string) escapeCheck {
|
||||
escapeCheck := escapeCheck{canSkipRegex: true, invalidCharFound: false}
|
||||
// skip escape if possible
|
||||
previous_underscore := false
|
||||
for _, c := range ident {
|
||||
if c == '/' || c == '-' || c == '.' {
|
||||
escapeCheck.canSkipRegex = false
|
||||
return escapeCheck
|
||||
}
|
||||
intc := int(c)
|
||||
if intc < 0 || intc >= len(validCharacterFilter) || !validCharacterFilter[intc] {
|
||||
escapeCheck.invalidCharFound = true
|
||||
return escapeCheck
|
||||
}
|
||||
if c == '_' && previous_underscore {
|
||||
escapeCheck.canSkipRegex = false
|
||||
return escapeCheck
|
||||
}
|
||||
|
||||
previous_underscore = c == '_'
|
||||
}
|
||||
return escapeCheck
|
||||
}
|
||||
|
||||
// validCharacterFilter indicates the allowed characters.
|
||||
var validCharacterFilter = newCharacterFilter("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")
|
||||
|
||||
// Escape escapes ident and returns a CEL identifier (of the form '[a-zA-Z_][a-zA-Z0-9_]*'), or returns
|
||||
// false if the ident does not match the supported input format of `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*`.
|
||||
// Escaping Rules:
|
||||
// - '__' escapes to '__underscores__'
|
||||
// - '.' escapes to '__dot__'
|
||||
// - '-' escapes to '__dash__'
|
||||
// - '/' escapes to '__slash__'
|
||||
// - Identifiers that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are: "true", "false",
|
||||
// "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if", "import", "let", loop", "package",
|
||||
// "namespace", "return".
|
||||
func Escape(ident string) (string, bool) {
|
||||
if len(ident) == 0 || ('0' <= ident[0] && ident[0] <= '9') {
|
||||
return "", false
|
||||
}
|
||||
if celReservedSymbols.Has(ident) {
|
||||
return "__" + ident + "__", true
|
||||
}
|
||||
|
||||
escapeCheck := skipRegexCheck(ident)
|
||||
if escapeCheck.invalidCharFound {
|
||||
return "", false
|
||||
}
|
||||
if escapeCheck.canSkipRegex {
|
||||
return ident, true
|
||||
}
|
||||
|
||||
ok := true
|
||||
ident = expandMatcher.ReplaceAllStringFunc(ident, func(s string) string {
|
||||
switch s {
|
||||
case "__":
|
||||
return "__underscores__"
|
||||
case ".":
|
||||
return "__dot__"
|
||||
case "-":
|
||||
return "__dash__"
|
||||
case "/":
|
||||
return "__slash__"
|
||||
default: // matched a unsupported supported
|
||||
ok = false
|
||||
return ""
|
||||
}
|
||||
})
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return ident, true
|
||||
}
|
||||
|
||||
var unexpandMatcher = regexp.MustCompile(`(_{2}[^_]+_{2})`)
|
||||
|
||||
// Unescape unescapes an CEL identifier containing the escape sequences described in Escape, or return false if the
|
||||
// string contains invalid escape sequences. The escaped input is expected to be a valid CEL identifier, but is
|
||||
// not checked.
|
||||
func Unescape(escaped string) (string, bool) {
|
||||
ok := true
|
||||
escaped = unexpandMatcher.ReplaceAllStringFunc(escaped, func(s string) string {
|
||||
contents := s[2 : len(s)-2]
|
||||
switch contents {
|
||||
case "underscores":
|
||||
return "__"
|
||||
case "dot":
|
||||
return "."
|
||||
case "dash":
|
||||
return "-"
|
||||
case "slash":
|
||||
return "/"
|
||||
}
|
||||
if celReservedSymbols.Has(contents) {
|
||||
if len(s) != len(escaped) {
|
||||
ok = false
|
||||
}
|
||||
return contents
|
||||
}
|
||||
ok = false
|
||||
return ""
|
||||
})
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return escaped, true
|
||||
}
|
268
vendor/k8s.io/apiserver/pkg/cel/library/cost.go
generated
vendored
Normal file
268
vendor/k8s.io/apiserver/pkg/cel/library/cost.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
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 library
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/google/cel-go/checker"
|
||||
"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"
|
||||
)
|
||||
|
||||
// CostEstimator implements CEL's interpretable.ActualCostEstimator and checker.CostEstimator.
|
||||
type CostEstimator struct {
|
||||
// SizeEstimator provides a CostEstimator.EstimateSize that this CostEstimator will delegate size estimation
|
||||
// calculations to if the size is not well known (i.e. a constant).
|
||||
SizeEstimator checker.CostEstimator
|
||||
}
|
||||
|
||||
func (l *CostEstimator) CallCost(function, overloadId string, args []ref.Val, result ref.Val) *uint64 {
|
||||
switch function {
|
||||
case "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
|
||||
var cost uint64
|
||||
if len(args) > 0 {
|
||||
cost += traversalCost(args[0]) // these O(n) operations all cost roughly the cost of a single traversal
|
||||
}
|
||||
return &cost
|
||||
case "url", "lowerAscii", "upperAscii", "substring", "trim":
|
||||
if len(args) >= 1 {
|
||||
cost := uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor))
|
||||
return &cost
|
||||
}
|
||||
case "replace", "split":
|
||||
if len(args) >= 1 {
|
||||
// cost is the traversal plus the construction of the result
|
||||
cost := uint64(math.Ceil(float64(actualSize(args[0])) * 2 * common.StringTraversalCostFactor))
|
||||
return &cost
|
||||
}
|
||||
case "join":
|
||||
if len(args) >= 1 {
|
||||
cost := uint64(math.Ceil(float64(actualSize(result)) * 2 * common.StringTraversalCostFactor))
|
||||
return &cost
|
||||
}
|
||||
case "find", "findAll":
|
||||
if len(args) >= 2 {
|
||||
strCost := uint64(math.Ceil((1.0 + float64(actualSize(args[0]))) * common.StringTraversalCostFactor))
|
||||
// We don't know how many expressions are in the regex, just the string length (a huge
|
||||
// improvement here would be to somehow get a count the number of expressions in the regex or
|
||||
// how many states are in the regex state machine and use that to measure regex cost).
|
||||
// For now, we're making a guess that each expression in a regex is typically at least 4 chars
|
||||
// in length.
|
||||
regexCost := uint64(math.Ceil(float64(actualSize(args[1])) * common.RegexStringLengthCostFactor))
|
||||
cost := strCost * regexCost
|
||||
return &cost
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
|
||||
// WARNING: Any changes to this code impact API compatibility! The estimated cost is used to determine which CEL rules may be written to a
|
||||
// CRD and any change (cost increases and cost decreases) are breaking.
|
||||
switch function {
|
||||
case "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
|
||||
if target != nil {
|
||||
// Charge 1 cost for comparing each element in the list
|
||||
elCost := checker.CostEstimate{Min: 1, Max: 1}
|
||||
// If the list contains strings or bytes, add the cost of traversing all the strings/bytes as a way
|
||||
// of estimating the additional comparison cost.
|
||||
if elNode := l.listElementNode(*target); elNode != nil {
|
||||
t := elNode.Type().GetPrimitive()
|
||||
if t == exprpb.Type_STRING || t == exprpb.Type_BYTES {
|
||||
sz := l.sizeEstimate(elNode)
|
||||
elCost = elCost.Add(sz.MultiplyByCostFactor(common.StringTraversalCostFactor))
|
||||
}
|
||||
return &checker.CallEstimate{CostEstimate: l.sizeEstimate(*target).MultiplyByCost(elCost)}
|
||||
} else { // the target is a string, which is supported by indexOf and lastIndexOf
|
||||
return &checker.CallEstimate{CostEstimate: l.sizeEstimate(*target).MultiplyByCostFactor(common.StringTraversalCostFactor)}
|
||||
}
|
||||
|
||||
}
|
||||
case "url":
|
||||
if len(args) == 1 {
|
||||
sz := l.sizeEstimate(args[0])
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor)}
|
||||
}
|
||||
case "lowerAscii", "upperAscii", "substring", "trim":
|
||||
if target != nil {
|
||||
sz := l.sizeEstimate(*target)
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor), ResultSize: &sz}
|
||||
}
|
||||
case "replace":
|
||||
if target != nil && len(args) >= 2 {
|
||||
sz := l.sizeEstimate(*target)
|
||||
toReplaceSz := l.sizeEstimate(args[0])
|
||||
replaceWithSz := l.sizeEstimate(args[1])
|
||||
// smallest possible result: smallest input size composed of the largest possible substrings being replaced by smallest possible replacement
|
||||
minSz := uint64(math.Ceil(float64(sz.Min)/float64(toReplaceSz.Max))) * replaceWithSz.Min
|
||||
// largest possible result: largest input size composed of the smallest possible substrings being replaced by largest possible replacement
|
||||
maxSz := uint64(math.Ceil(float64(sz.Max)/float64(toReplaceSz.Min))) * replaceWithSz.Max
|
||||
|
||||
// cost is the traversal plus the construction of the result
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &checker.SizeEstimate{Min: minSz, Max: maxSz}}
|
||||
}
|
||||
case "split":
|
||||
if target != nil {
|
||||
sz := l.sizeEstimate(*target)
|
||||
|
||||
// Worst case size is where is that a separator of "" is used, and each char is returned as a list element.
|
||||
max := sz.Max
|
||||
if len(args) > 1 {
|
||||
if c := args[1].Expr().GetConstExpr(); c != nil {
|
||||
max = uint64(c.GetInt64Value())
|
||||
}
|
||||
}
|
||||
// Cost is the traversal plus the construction of the result.
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &checker.SizeEstimate{Min: 0, Max: max}}
|
||||
}
|
||||
case "join":
|
||||
if target != nil {
|
||||
var sz checker.SizeEstimate
|
||||
listSize := l.sizeEstimate(*target)
|
||||
if elNode := l.listElementNode(*target); elNode != nil {
|
||||
elemSize := l.sizeEstimate(elNode)
|
||||
sz = listSize.Multiply(elemSize)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
sepSize := l.sizeEstimate(args[0])
|
||||
minSeparators := uint64(0)
|
||||
maxSeparators := uint64(0)
|
||||
if listSize.Min > 0 {
|
||||
minSeparators = listSize.Min - 1
|
||||
}
|
||||
if listSize.Max > 0 {
|
||||
maxSeparators = listSize.Max - 1
|
||||
}
|
||||
sz = sz.Add(sepSize.Multiply(checker.SizeEstimate{Min: minSeparators, Max: maxSeparators}))
|
||||
}
|
||||
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor), ResultSize: &sz}
|
||||
}
|
||||
case "find", "findAll":
|
||||
if target != nil && len(args) >= 1 {
|
||||
sz := l.sizeEstimate(*target)
|
||||
// Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0
|
||||
// in case where string is empty but regex is still expensive.
|
||||
strCost := sz.Add(checker.SizeEstimate{Min: 1, Max: 1}).MultiplyByCostFactor(common.StringTraversalCostFactor)
|
||||
// We don't know how many expressions are in the regex, just the string length (a huge
|
||||
// improvement here would be to somehow get a count the number of expressions in the regex or
|
||||
// how many states are in the regex state machine and use that to measure regex cost).
|
||||
// For now, we're making a guess that each expression in a regex is typically at least 4 chars
|
||||
// in length.
|
||||
regexCost := l.sizeEstimate(args[0]).MultiplyByCostFactor(common.RegexStringLengthCostFactor)
|
||||
// worst case size of result is that every char is returned as separate find result.
|
||||
return &checker.CallEstimate{CostEstimate: strCost.Multiply(regexCost), ResultSize: &checker.SizeEstimate{Min: 0, Max: sz.Max}}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func actualSize(value ref.Val) uint64 {
|
||||
if sz, ok := value.(traits.Sizer); ok {
|
||||
return uint64(sz.Size().(types.Int))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (l *CostEstimator) sizeEstimate(t checker.AstNode) checker.SizeEstimate {
|
||||
if sz := t.ComputedSize(); sz != nil {
|
||||
return *sz
|
||||
}
|
||||
if sz := l.EstimateSize(t); sz != nil {
|
||||
return *sz
|
||||
}
|
||||
return checker.SizeEstimate{Min: 0, Max: math.MaxUint64}
|
||||
}
|
||||
|
||||
func (l *CostEstimator) listElementNode(list checker.AstNode) checker.AstNode {
|
||||
if lt := list.Type().GetListType(); lt != nil {
|
||||
nodePath := list.Path()
|
||||
if nodePath != nil {
|
||||
// Provide path if we have it so that a OpenAPIv3 maxLength validation can be looked up, if it exists
|
||||
// for this node.
|
||||
path := make([]string, len(nodePath)+1)
|
||||
copy(path, nodePath)
|
||||
path[len(nodePath)] = "@items"
|
||||
return &itemsNode{path: path, t: lt.GetElemType(), expr: nil}
|
||||
} else {
|
||||
// Provide just the type if no path is available so that worst case size can be looked up based on type.
|
||||
return &itemsNode{t: lt.GetElemType(), expr: nil}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *CostEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate {
|
||||
if l.SizeEstimator != nil {
|
||||
return l.SizeEstimator.EstimateSize(element)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type itemsNode struct {
|
||||
path []string
|
||||
t *exprpb.Type
|
||||
expr *exprpb.Expr
|
||||
}
|
||||
|
||||
func (i *itemsNode) Path() []string {
|
||||
return i.path
|
||||
}
|
||||
|
||||
func (i *itemsNode) Type() *exprpb.Type {
|
||||
return i.t
|
||||
}
|
||||
|
||||
func (i *itemsNode) Expr() *exprpb.Expr {
|
||||
return i.expr
|
||||
}
|
||||
|
||||
func (i *itemsNode) ComputedSize() *checker.SizeEstimate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// traversalCost computes the cost of traversing a ref.Val as a data tree.
|
||||
func traversalCost(v ref.Val) uint64 {
|
||||
// TODO: This could potentially be optimized by sampling maps and lists instead of traversing.
|
||||
switch vt := v.(type) {
|
||||
case types.String:
|
||||
return uint64(float64(len(string(vt))) * common.StringTraversalCostFactor)
|
||||
case types.Bytes:
|
||||
return uint64(float64(len([]byte(vt))) * common.StringTraversalCostFactor)
|
||||
case traits.Lister:
|
||||
cost := uint64(0)
|
||||
for it := vt.Iterator(); it.HasNext() == types.True; {
|
||||
i := it.Next()
|
||||
cost += traversalCost(i)
|
||||
}
|
||||
return cost
|
||||
case traits.Mapper: // maps and objects
|
||||
cost := uint64(0)
|
||||
for it := vt.Iterator(); it.HasNext() == types.True; {
|
||||
k := it.Next()
|
||||
cost += traversalCost(k) + traversalCost(vt.Get(k))
|
||||
}
|
||||
return cost
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
34
vendor/k8s.io/apiserver/pkg/cel/library/libraries.go
generated
vendored
Normal file
34
vendor/k8s.io/apiserver/pkg/cel/library/libraries.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
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 library
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/ext"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
)
|
||||
|
||||
// ExtensionLibs declares the set of CEL extension libraries available everywhere CEL is used in Kubernetes.
|
||||
var ExtensionLibs = append(k8sExtensionLibs, ext.Strings())
|
||||
|
||||
var k8sExtensionLibs = []cel.EnvOption{
|
||||
URLs(),
|
||||
Regex(),
|
||||
Lists(),
|
||||
}
|
||||
|
||||
var ExtensionLibRegexOptimizations = []*interpreter.RegexOptimization{FindRegexOptimization, FindAllRegexOptimization}
|
312
vendor/k8s.io/apiserver/pkg/cel/library/lists.go
generated
vendored
Normal file
312
vendor/k8s.io/apiserver/pkg/cel/library/lists.go
generated
vendored
Normal file
@ -0,0 +1,312 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
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 library
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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/functions"
|
||||
)
|
||||
|
||||
// Lists provides a CEL function library extension of list utility functions.
|
||||
//
|
||||
// isSorted
|
||||
//
|
||||
// Returns true if the provided list of comparable elements is sorted, else returns false.
|
||||
//
|
||||
// <list<T>>.isSorted() <bool>, T must be a comparable type
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// [1, 2, 3].isSorted() // return true
|
||||
// ['a', 'b', 'b', 'c'].isSorted() // return true
|
||||
// [2.0, 1.0].isSorted() // return false
|
||||
// [1].isSorted() // return true
|
||||
// [].isSorted() // return true
|
||||
//
|
||||
// sum
|
||||
//
|
||||
// Returns the sum of the elements of the provided list. Supports CEL number (int, uint, double) and duration types.
|
||||
//
|
||||
// <list<T>>.sum() <T>, T must be a numeric type or a duration
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// [1, 3].sum() // returns 4
|
||||
// [1.0, 3.0].sum() // returns 4.0
|
||||
// ['1m', '1s'].sum() // returns '1m1s'
|
||||
// emptyIntList.sum() // returns 0
|
||||
// emptyDoubleList.sum() // returns 0.0
|
||||
// [].sum() // returns 0
|
||||
//
|
||||
// min / max
|
||||
//
|
||||
// Returns the minimum/maximum valued element of the provided list. Supports all comparable types.
|
||||
// If the list is empty, an error is returned.
|
||||
//
|
||||
// <list<T>>.min() <T>, T must be a comparable type
|
||||
// <list<T>>.max() <T>, T must be a comparable type
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// [1, 3].min() // returns 1
|
||||
// [1, 3].max() // returns 3
|
||||
// [].min() // error
|
||||
// [1].min() // returns 1
|
||||
// ([0] + emptyList).min() // returns 0
|
||||
//
|
||||
// indexOf / lastIndexOf
|
||||
//
|
||||
// Returns either the first or last positional index of the provided element in the list.
|
||||
// If the element is not found, -1 is returned. Supports all equatable types.
|
||||
//
|
||||
// <list<T>>.indexOf(<T>) <int>, T must be an equatable type
|
||||
// <list<T>>.lastIndexOf(<T>) <int>, T must be an equatable type
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// [1, 2, 2, 3].indexOf(2) // returns 1
|
||||
// ['a', 'b', 'b', 'c'].lastIndexOf('b') // returns 2
|
||||
// [1.0].indexOf(1.1) // returns -1
|
||||
// [].indexOf('string') // returns -1
|
||||
func Lists() cel.EnvOption {
|
||||
return cel.Lib(listsLib)
|
||||
}
|
||||
|
||||
var listsLib = &lists{}
|
||||
|
||||
type lists struct{}
|
||||
|
||||
var paramA = cel.TypeParamType("A")
|
||||
|
||||
// CEL typeParams can be used to constraint to a specific trait (e.g. traits.ComparableType) if the 1st operand is the type to constrain.
|
||||
// But the functions we need to constrain are <list<paramType>>, not just <paramType>.
|
||||
// Make sure the order of overload set is deterministic
|
||||
type namedCELType struct {
|
||||
typeName string
|
||||
celType *cel.Type
|
||||
}
|
||||
|
||||
var summableTypes = []namedCELType{
|
||||
{typeName: "int", celType: cel.IntType},
|
||||
{typeName: "uint", celType: cel.UintType},
|
||||
{typeName: "double", celType: cel.DoubleType},
|
||||
{typeName: "duration", celType: cel.DurationType},
|
||||
}
|
||||
|
||||
var zeroValuesOfSummableTypes = map[string]ref.Val{
|
||||
"int": types.Int(0),
|
||||
"uint": types.Uint(0),
|
||||
"double": types.Double(0.0),
|
||||
"duration": types.Duration{Duration: 0},
|
||||
}
|
||||
var comparableTypes = []namedCELType{
|
||||
{typeName: "int", celType: cel.IntType},
|
||||
{typeName: "uint", celType: cel.UintType},
|
||||
{typeName: "double", celType: cel.DoubleType},
|
||||
{typeName: "bool", celType: cel.BoolType},
|
||||
{typeName: "duration", celType: cel.DurationType},
|
||||
{typeName: "timestamp", celType: cel.TimestampType},
|
||||
{typeName: "string", celType: cel.StringType},
|
||||
{typeName: "bytes", celType: cel.BytesType},
|
||||
}
|
||||
|
||||
// WARNING: All library additions or modifications must follow
|
||||
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2876-crd-validation-expression-language#function-library-updates
|
||||
var listsLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"isSorted": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_is_sorted_bool", name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, cel.BoolType, cel.UnaryBinding(isSorted))
|
||||
}),
|
||||
"sum": templatedOverloads(summableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_sum_%s", name, name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(func(list ref.Val) ref.Val {
|
||||
return sum(
|
||||
func() ref.Val {
|
||||
return zeroValuesOfSummableTypes[name]
|
||||
})(list)
|
||||
}))
|
||||
}),
|
||||
"max": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_max_%s", name, name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(max()))
|
||||
}),
|
||||
"min": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
|
||||
return cel.MemberOverload(fmt.Sprintf("list_%s_min_%s", name, name),
|
||||
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(min()))
|
||||
}),
|
||||
"indexOf": {
|
||||
cel.MemberOverload("list_a_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
|
||||
cel.BinaryBinding(indexOf)),
|
||||
},
|
||||
"lastIndexOf": {
|
||||
cel.MemberOverload("list_a_last_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
|
||||
cel.BinaryBinding(lastIndexOf)),
|
||||
},
|
||||
}
|
||||
|
||||
func (*lists) CompileOptions() []cel.EnvOption {
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range listsLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*lists) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func isSorted(val ref.Val) ref.Val {
|
||||
var prev traits.Comparer
|
||||
iterable, ok := val.(traits.Iterable)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(val)
|
||||
}
|
||||
for it := iterable.Iterator(); it.HasNext() == types.True; {
|
||||
next := it.Next()
|
||||
nextCmp, ok := next.(traits.Comparer)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(next)
|
||||
}
|
||||
if prev != nil {
|
||||
cmp := prev.Compare(next)
|
||||
if cmp == types.IntOne {
|
||||
return types.False
|
||||
}
|
||||
}
|
||||
prev = nextCmp
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
func sum(init func() ref.Val) functions.UnaryOp {
|
||||
return func(val ref.Val) ref.Val {
|
||||
i := init()
|
||||
acc, ok := i.(traits.Adder)
|
||||
if !ok {
|
||||
// Should never happen since all passed in init values are valid
|
||||
return types.MaybeNoSuchOverloadErr(i)
|
||||
}
|
||||
iterable, ok := val.(traits.Iterable)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(val)
|
||||
}
|
||||
for it := iterable.Iterator(); it.HasNext() == types.True; {
|
||||
next := it.Next()
|
||||
nextAdder, ok := next.(traits.Adder)
|
||||
if !ok {
|
||||
// Should never happen for type checked CEL programs
|
||||
return types.MaybeNoSuchOverloadErr(next)
|
||||
}
|
||||
if acc != nil {
|
||||
s := acc.Add(next)
|
||||
sum, ok := s.(traits.Adder)
|
||||
if !ok {
|
||||
// Should never happen for type checked CEL programs
|
||||
return types.MaybeNoSuchOverloadErr(s)
|
||||
}
|
||||
acc = sum
|
||||
} else {
|
||||
acc = nextAdder
|
||||
}
|
||||
}
|
||||
return acc.(ref.Val)
|
||||
}
|
||||
}
|
||||
|
||||
func min() functions.UnaryOp {
|
||||
return cmp("min", types.IntOne)
|
||||
}
|
||||
|
||||
func max() functions.UnaryOp {
|
||||
return cmp("max", types.IntNegOne)
|
||||
}
|
||||
|
||||
func cmp(opName string, opPreferCmpResult ref.Val) functions.UnaryOp {
|
||||
return func(val ref.Val) ref.Val {
|
||||
var result traits.Comparer
|
||||
iterable, ok := val.(traits.Iterable)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(val)
|
||||
}
|
||||
for it := iterable.Iterator(); it.HasNext() == types.True; {
|
||||
next := it.Next()
|
||||
nextCmp, ok := next.(traits.Comparer)
|
||||
if !ok {
|
||||
// Should never happen for type checked CEL programs
|
||||
return types.MaybeNoSuchOverloadErr(next)
|
||||
}
|
||||
if result == nil {
|
||||
result = nextCmp
|
||||
} else {
|
||||
cmp := result.Compare(next)
|
||||
if cmp == opPreferCmpResult {
|
||||
result = nextCmp
|
||||
}
|
||||
}
|
||||
}
|
||||
if result == nil {
|
||||
return types.NewErr("%s called on empty list", opName)
|
||||
}
|
||||
return result.(ref.Val)
|
||||
}
|
||||
}
|
||||
|
||||
func indexOf(list ref.Val, item ref.Val) ref.Val {
|
||||
lister, ok := list.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(list)
|
||||
}
|
||||
sz := lister.Size().(types.Int)
|
||||
for i := types.Int(0); i < sz; i++ {
|
||||
if lister.Get(types.Int(i)).Equal(item) == types.True {
|
||||
return types.Int(i)
|
||||
}
|
||||
}
|
||||
return types.Int(-1)
|
||||
}
|
||||
|
||||
func lastIndexOf(list ref.Val, item ref.Val) ref.Val {
|
||||
lister, ok := list.(traits.Lister)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(list)
|
||||
}
|
||||
sz := lister.Size().(types.Int)
|
||||
for i := sz - 1; i >= 0; i-- {
|
||||
if lister.Get(types.Int(i)).Equal(item) == types.True {
|
||||
return types.Int(i)
|
||||
}
|
||||
}
|
||||
return types.Int(-1)
|
||||
}
|
||||
|
||||
// templatedOverloads returns overloads for each of the provided types. The template function is called with each type
|
||||
// name (map key) and type to construct the overloads.
|
||||
func templatedOverloads(types []namedCELType, template func(name string, t *cel.Type) cel.FunctionOpt) []cel.FunctionOpt {
|
||||
overloads := make([]cel.FunctionOpt, len(types))
|
||||
i := 0
|
||||
for _, t := range types {
|
||||
overloads[i] = template(t.typeName, t.celType)
|
||||
i++
|
||||
}
|
||||
return overloads
|
||||
}
|
187
vendor/k8s.io/apiserver/pkg/cel/library/regex.go
generated
vendored
Normal file
187
vendor/k8s.io/apiserver/pkg/cel/library/regex.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
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 library
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"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/interpreter"
|
||||
)
|
||||
|
||||
// Regex provides a CEL function library extension of regex utility functions.
|
||||
//
|
||||
// find / findAll
|
||||
//
|
||||
// Returns substrings that match the provided regular expression. find returns the first match. findAll may optionally
|
||||
// be provided a limit. If the limit is set and >= 0, no more than the limit number of matches are returned.
|
||||
//
|
||||
// <string>.find(<string>) <string>
|
||||
// <string>.findAll(<string>) <list <string>>
|
||||
// <string>.findAll(<string>, <int>) <list <string>>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// "abc 123".find('[0-9]*') // returns '123'
|
||||
// "abc 123".find('xyz') // returns ''
|
||||
// "123 abc 456".findAll('[0-9]*') // returns ['123', '456']
|
||||
// "123 abc 456".findAll('[0-9]*', 1) // returns ['123']
|
||||
// "123 abc 456".findAll('xyz') // returns []
|
||||
func Regex() cel.EnvOption {
|
||||
return cel.Lib(regexLib)
|
||||
}
|
||||
|
||||
var regexLib = ®ex{}
|
||||
|
||||
type regex struct{}
|
||||
|
||||
var regexLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"find": {
|
||||
cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
|
||||
cel.BinaryBinding(find))},
|
||||
"findAll": {
|
||||
cel.MemberOverload("string_find_all_string", []*cel.Type{cel.StringType, cel.StringType},
|
||||
cel.ListType(cel.StringType),
|
||||
cel.BinaryBinding(func(str, regex ref.Val) ref.Val {
|
||||
return findAll(str, regex, types.Int(-1))
|
||||
})),
|
||||
cel.MemberOverload("string_find_all_string_int",
|
||||
[]*cel.Type{cel.StringType, cel.StringType, cel.IntType},
|
||||
cel.ListType(cel.StringType),
|
||||
cel.FunctionBinding(findAll)),
|
||||
},
|
||||
}
|
||||
|
||||
func (*regex) CompileOptions() []cel.EnvOption {
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range regexLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*regex) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func find(strVal ref.Val, regexVal ref.Val) ref.Val {
|
||||
str, ok := strVal.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(strVal)
|
||||
}
|
||||
regex, ok := regexVal.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(regexVal)
|
||||
}
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return types.NewErr("Illegal regex: %v", err.Error())
|
||||
}
|
||||
result := re.FindString(str)
|
||||
return types.String(result)
|
||||
}
|
||||
|
||||
func findAll(args ...ref.Val) ref.Val {
|
||||
argn := len(args)
|
||||
if argn < 2 || argn > 3 {
|
||||
return types.NoSuchOverloadErr()
|
||||
}
|
||||
str, ok := args[0].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[0])
|
||||
}
|
||||
regex, ok := args[1].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[1])
|
||||
}
|
||||
n := int64(-1)
|
||||
if argn == 3 {
|
||||
n, ok = args[2].Value().(int64)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[2])
|
||||
}
|
||||
}
|
||||
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return types.NewErr("Illegal regex: %v", err.Error())
|
||||
}
|
||||
|
||||
result := re.FindAllString(str, int(n))
|
||||
|
||||
return types.NewStringList(types.DefaultTypeAdapter, result)
|
||||
}
|
||||
|
||||
// FindRegexOptimization optimizes the 'find' function by compiling the regex pattern and
|
||||
// reporting any compilation errors at program creation time, and using the compiled regex pattern for all function
|
||||
// call invocations.
|
||||
var FindRegexOptimization = &interpreter.RegexOptimization{
|
||||
Function: "find",
|
||||
RegexIndex: 1,
|
||||
Factory: func(call interpreter.InterpretableCall, regexPattern string) (interpreter.InterpretableCall, error) {
|
||||
compiledRegex, err := regexp.Compile(regexPattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return interpreter.NewCall(call.ID(), call.Function(), call.OverloadID(), call.Args(), func(args ...ref.Val) ref.Val {
|
||||
if len(args) != 2 {
|
||||
return types.NoSuchOverloadErr()
|
||||
}
|
||||
in, ok := args[0].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[0])
|
||||
}
|
||||
return types.String(compiledRegex.FindString(in))
|
||||
}), nil
|
||||
},
|
||||
}
|
||||
|
||||
// FindAllRegexOptimization optimizes the 'findAll' function by compiling the regex pattern and
|
||||
// reporting any compilation errors at program creation time, and using the compiled regex pattern for all function
|
||||
// call invocations.
|
||||
var FindAllRegexOptimization = &interpreter.RegexOptimization{
|
||||
Function: "findAll",
|
||||
RegexIndex: 1,
|
||||
Factory: func(call interpreter.InterpretableCall, regexPattern string) (interpreter.InterpretableCall, error) {
|
||||
compiledRegex, err := regexp.Compile(regexPattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return interpreter.NewCall(call.ID(), call.Function(), call.OverloadID(), call.Args(), func(args ...ref.Val) ref.Val {
|
||||
argn := len(args)
|
||||
if argn < 2 || argn > 3 {
|
||||
return types.NoSuchOverloadErr()
|
||||
}
|
||||
str, ok := args[0].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[0])
|
||||
}
|
||||
n := int64(-1)
|
||||
if argn == 3 {
|
||||
n, ok = args[2].Value().(int64)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[2])
|
||||
}
|
||||
}
|
||||
|
||||
result := compiledRegex.FindAllString(str, int(n))
|
||||
return types.NewStringList(types.DefaultTypeAdapter, result)
|
||||
}), nil
|
||||
},
|
||||
}
|
236
vendor/k8s.io/apiserver/pkg/cel/library/urls.go
generated
vendored
Normal file
236
vendor/k8s.io/apiserver/pkg/cel/library/urls.go
generated
vendored
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
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 library
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
)
|
||||
|
||||
// URLs provides a CEL function library extension of URL parsing functions.
|
||||
//
|
||||
// url
|
||||
//
|
||||
// Converts a string to a URL or results in an error if the string is not a valid URL. The URL must be an absolute URI
|
||||
// or an absolute path.
|
||||
//
|
||||
// url(<string>) <URL>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// url('https://user:pass@example.com:80/path?query=val#fragment') // returns a URL
|
||||
// url('/absolute-path') // returns a URL
|
||||
// url('https://a:b:c/') // error
|
||||
// url('../relative-path') // error
|
||||
//
|
||||
// isURL
|
||||
//
|
||||
// Returns true if a string is a valid URL. The URL must be an absolute URI or an absolute path.
|
||||
//
|
||||
// isURL( <string>) <bool>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// isURL('https://user:pass@example.com:80/path?query=val#fragment') // returns true
|
||||
// isURL('/absolute-path') // returns true
|
||||
// isURL('https://a:b:c/') // returns false
|
||||
// isURL('../relative-path') // returns false
|
||||
//
|
||||
// getScheme / getHost / getHostname / getPort / getEscapedPath / getQuery
|
||||
//
|
||||
// Return the parsed components of a URL.
|
||||
//
|
||||
// - getScheme: If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getHostname: IPv6 addresses are returned with braces, e.g. "[::1]". If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getHost: IPv6 addresses are returned without braces, e.g. "::1". If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getEscapedPath: The string returned by getEscapedPath is URL escaped, e.g. "with space" becomes "with%20space".
|
||||
// If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getPort: If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getQuery: Returns the query parameters in "matrix" form where a repeated query key is interpreted to
|
||||
// mean that there are multiple values for that key. The keys and values are returned unescaped.
|
||||
// If absent in the URL, returns an empty map.
|
||||
//
|
||||
// <URL>.getScheme() <string>
|
||||
// <URL>.getHost() <string>
|
||||
// <URL>.getHostname() <string>
|
||||
// <URL>.getPort() <string>
|
||||
// <URL>.getEscapedPath() <string>
|
||||
// <URL>.getQuery() <map <string>, <list <string>>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// url('/path').getScheme() // returns ''
|
||||
// url('https://example.com/').getScheme() // returns 'https'
|
||||
// url('https://example.com:80/').getHost() // returns 'example.com:80'
|
||||
// url('https://example.com/').getHost() // returns 'example.com'
|
||||
// url('https://[::1]:80/').getHost() // returns '[::1]:80'
|
||||
// url('https://[::1]/').getHost() // returns '[::1]'
|
||||
// url('/path').getHost() // returns ''
|
||||
// url('https://example.com:80/').getHostname() // returns 'example.com'
|
||||
// url('https://127.0.0.1:80/').getHostname() // returns '127.0.0.1'
|
||||
// url('https://[::1]:80/').getHostname() // returns '::1'
|
||||
// url('/path').getHostname() // returns ''
|
||||
// url('https://example.com:80/').getPort() // returns '80'
|
||||
// url('https://example.com/').getPort() // returns ''
|
||||
// url('/path').getPort() // returns ''
|
||||
// url('https://example.com/path').getEscapedPath() // returns '/path'
|
||||
// url('https://example.com/path with spaces/').getEscapedPath() // returns '/path%20with%20spaces/'
|
||||
// url('https://example.com').getEscapedPath() // returns ''
|
||||
// url('https://example.com/path?k1=a&k2=b&k2=c').getQuery() // returns { 'k1': ['a'], 'k2': ['b', 'c']}
|
||||
// url('https://example.com/path?key with spaces=value with spaces').getQuery() // returns { 'key with spaces': ['value with spaces']}
|
||||
// url('https://example.com/path?').getQuery() // returns {}
|
||||
// url('https://example.com/path').getQuery() // returns {}
|
||||
func URLs() cel.EnvOption {
|
||||
return cel.Lib(urlsLib)
|
||||
}
|
||||
|
||||
var urlsLib = &urls{}
|
||||
|
||||
type urls struct{}
|
||||
|
||||
var urlLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"url": {
|
||||
cel.Overload("string_to_url", []*cel.Type{cel.StringType}, apiservercel.URLType,
|
||||
cel.UnaryBinding(stringToUrl))},
|
||||
"getScheme": {
|
||||
cel.MemberOverload("url_get_scheme", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getScheme))},
|
||||
"getHost": {
|
||||
cel.MemberOverload("url_get_host", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getHost))},
|
||||
"getHostname": {
|
||||
cel.MemberOverload("url_get_hostname", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getHostname))},
|
||||
"getPort": {
|
||||
cel.MemberOverload("url_get_port", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getPort))},
|
||||
"getEscapedPath": {
|
||||
cel.MemberOverload("url_get_escaped_path", []*cel.Type{apiservercel.URLType}, cel.StringType,
|
||||
cel.UnaryBinding(getEscapedPath))},
|
||||
"getQuery": {
|
||||
cel.MemberOverload("url_get_query", []*cel.Type{apiservercel.URLType},
|
||||
cel.MapType(cel.StringType, cel.ListType(cel.StringType)),
|
||||
cel.UnaryBinding(getQuery))},
|
||||
"isURL": {
|
||||
cel.Overload("is_url_string", []*cel.Type{cel.StringType}, cel.BoolType,
|
||||
cel.UnaryBinding(isURL))},
|
||||
}
|
||||
|
||||
func (*urls) CompileOptions() []cel.EnvOption {
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range urlLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*urls) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func stringToUrl(arg ref.Val) ref.Val {
|
||||
s, ok := arg.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
// Use ParseRequestURI to check the URL before conversion.
|
||||
// ParseRequestURI requires absolute URLs and is used by the OpenAPIv3 'uri' format.
|
||||
_, err := url.ParseRequestURI(s)
|
||||
if err != nil {
|
||||
return types.NewErr("URL parse error during conversion from string: %v", err)
|
||||
}
|
||||
// We must parse again with Parse since ParseRequestURI incorrectly parses URLs that contain a fragment
|
||||
// part and will incorrectly append the fragment to either the path or the query, depending on which it was adjacent to.
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
// Errors are not expected here since Parse is a more lenient parser than ParseRequestURI.
|
||||
return types.NewErr("URL parse error during conversion from string: %v", err)
|
||||
}
|
||||
return apiservercel.URL{URL: u}
|
||||
}
|
||||
|
||||
func getScheme(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.Scheme)
|
||||
}
|
||||
|
||||
func getHost(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.Host)
|
||||
}
|
||||
|
||||
func getHostname(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.Hostname())
|
||||
}
|
||||
|
||||
func getPort(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.Port())
|
||||
}
|
||||
|
||||
func getEscapedPath(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.String(u.EscapedPath())
|
||||
}
|
||||
|
||||
func getQuery(arg ref.Val) ref.Val {
|
||||
u, ok := arg.Value().(*url.URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
result := map[ref.Val]ref.Val{}
|
||||
for k, v := range u.Query() {
|
||||
result[types.String(k)] = types.NewStringList(types.DefaultTypeAdapter, v)
|
||||
}
|
||||
return types.NewRefValMap(types.DefaultTypeAdapter, result)
|
||||
}
|
||||
|
||||
func isURL(arg ref.Val) ref.Val {
|
||||
s, ok := arg.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
_, err := url.ParseRequestURI(s)
|
||||
return types.Bool(err == nil)
|
||||
}
|
48
vendor/k8s.io/apiserver/pkg/cel/limits.go
generated
vendored
Normal file
48
vendor/k8s.io/apiserver/pkg/cel/limits.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
|
||||
const (
|
||||
// DefaultMaxRequestSizeBytes is the size of the largest request that will be accepted
|
||||
DefaultMaxRequestSizeBytes = int64(3 * 1024 * 1024)
|
||||
|
||||
// MaxDurationSizeJSON
|
||||
// OpenAPI duration strings follow RFC 3339, section 5.6 - see the comment on maxDatetimeSizeJSON
|
||||
MaxDurationSizeJSON = 32
|
||||
// MaxDatetimeSizeJSON
|
||||
// OpenAPI datetime strings follow RFC 3339, section 5.6, and the longest possible
|
||||
// such string is 9999-12-31T23:59:59.999999999Z, which has length 30 - we add 2
|
||||
// to allow for quotation marks
|
||||
MaxDatetimeSizeJSON = 32
|
||||
// MinDurationSizeJSON
|
||||
// Golang allows a string of 0 to be parsed as a duration, so that plus 2 to account for
|
||||
// quotation marks makes 3
|
||||
MinDurationSizeJSON = 3
|
||||
// JSONDateSize is the size of a date serialized as part of a JSON object
|
||||
// RFC 3339 dates require YYYY-MM-DD, and then we add 2 to allow for quotation marks
|
||||
JSONDateSize = 12
|
||||
// MinDatetimeSizeJSON is the minimal length of a datetime formatted as RFC 3339
|
||||
// RFC 3339 datetimes require a full date (YYYY-MM-DD) and full time (HH:MM:SS), and we add 3 for
|
||||
// quotation marks like always in addition to the capital T that separates the date and time
|
||||
MinDatetimeSizeJSON = 21
|
||||
// MinStringSize is the size of literal ""
|
||||
MinStringSize = 2
|
||||
// MinBoolSize is the length of literal true
|
||||
MinBoolSize = 4
|
||||
// MinNumberSize is the length of literal 0
|
||||
MinNumberSize = 1
|
||||
)
|
79
vendor/k8s.io/apiserver/pkg/cel/registry.go
generated
vendored
Normal file
79
vendor/k8s.io/apiserver/pkg/cel/registry.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
)
|
||||
|
||||
// Resolver declares methods to find policy templates and related configuration objects.
|
||||
type Resolver interface {
|
||||
// FindType returns a DeclType instance corresponding to the given fully-qualified name, if
|
||||
// present.
|
||||
FindType(name string) (*DeclType, bool)
|
||||
}
|
||||
|
||||
// NewRegistry create a registry for keeping track of environments and types
|
||||
// from a base cel.Env expression environment.
|
||||
func NewRegistry(stdExprEnv *cel.Env) *Registry {
|
||||
return &Registry{
|
||||
exprEnvs: map[string]*cel.Env{"": stdExprEnv},
|
||||
types: map[string]*DeclType{
|
||||
BoolType.TypeName(): BoolType,
|
||||
BytesType.TypeName(): BytesType,
|
||||
DoubleType.TypeName(): DoubleType,
|
||||
DurationType.TypeName(): DurationType,
|
||||
IntType.TypeName(): IntType,
|
||||
NullType.TypeName(): NullType,
|
||||
StringType.TypeName(): StringType,
|
||||
TimestampType.TypeName(): TimestampType,
|
||||
UintType.TypeName(): UintType,
|
||||
ListType.TypeName(): ListType,
|
||||
MapType.TypeName(): MapType,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Registry defines a repository of environment, schema, template, and type definitions.
|
||||
//
|
||||
// Registry instances are concurrency-safe.
|
||||
type Registry struct {
|
||||
rwMux sync.RWMutex
|
||||
exprEnvs map[string]*cel.Env
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
// FindType implements the Resolver interface method.
|
||||
func (r *Registry) FindType(name string) (*DeclType, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
typ, found := r.types[name]
|
||||
if found {
|
||||
return typ, true
|
||||
}
|
||||
return typ, found
|
||||
}
|
||||
|
||||
// SetType registers a DeclType descriptor by its fully qualified name.
|
||||
func (r *Registry) SetType(name string, declType *DeclType) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
r.types[name] = declType
|
||||
return nil
|
||||
}
|
552
vendor/k8s.io/apiserver/pkg/cel/types.go
generated
vendored
Normal file
552
vendor/k8s.io/apiserver/pkg/cel/types.go
generated
vendored
Normal file
@ -0,0 +1,552 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
noMaxLength = math.MaxInt
|
||||
)
|
||||
|
||||
// NewListType returns a parameterized list type with a specified element type.
|
||||
func NewListType(elem *DeclType, maxItems int64) *DeclType {
|
||||
return &DeclType{
|
||||
name: "list",
|
||||
ElemType: elem,
|
||||
MaxElements: maxItems,
|
||||
celType: cel.ListType(elem.CelType()),
|
||||
defaultValue: NewListValue(),
|
||||
// a list can always be represented as [] in JSON, so hardcode the min size
|
||||
// to 2
|
||||
MinSerializedSize: 2,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMapType returns a parameterized map type with the given key and element types.
|
||||
func NewMapType(key, elem *DeclType, maxProperties int64) *DeclType {
|
||||
return &DeclType{
|
||||
name: "map",
|
||||
KeyType: key,
|
||||
ElemType: elem,
|
||||
MaxElements: maxProperties,
|
||||
celType: cel.MapType(key.CelType(), elem.CelType()),
|
||||
defaultValue: NewMapValue(),
|
||||
// a map can always be represented as {} in JSON, so hardcode the min size
|
||||
// to 2
|
||||
MinSerializedSize: 2,
|
||||
}
|
||||
}
|
||||
|
||||
// NewObjectType creates an object type with a qualified name and a set of field declarations.
|
||||
func NewObjectType(name string, fields map[string]*DeclField) *DeclType {
|
||||
t := &DeclType{
|
||||
name: name,
|
||||
Fields: fields,
|
||||
celType: cel.ObjectType(name),
|
||||
traitMask: traits.FieldTesterType | traits.IndexerType,
|
||||
// an object could potentially be larger than the min size we default to here ({}),
|
||||
// but we rely upon the caller to change MinSerializedSize accordingly if they add
|
||||
// properties to the object
|
||||
MinSerializedSize: 2,
|
||||
}
|
||||
t.defaultValue = NewObjectValue(t)
|
||||
return t
|
||||
}
|
||||
|
||||
func NewSimpleTypeWithMinSize(name string, celType *cel.Type, zeroVal ref.Val, minSize int64) *DeclType {
|
||||
return &DeclType{
|
||||
name: name,
|
||||
celType: celType,
|
||||
defaultValue: zeroVal,
|
||||
MinSerializedSize: minSize,
|
||||
}
|
||||
}
|
||||
|
||||
// DeclType represents the universal type descriptor for OpenAPIv3 types.
|
||||
type DeclType struct {
|
||||
fmt.Stringer
|
||||
|
||||
name string
|
||||
// Fields contains a map of escaped CEL identifier field names to field declarations.
|
||||
Fields map[string]*DeclField
|
||||
KeyType *DeclType
|
||||
ElemType *DeclType
|
||||
TypeParam bool
|
||||
Metadata map[string]string
|
||||
MaxElements int64
|
||||
// MinSerializedSize represents the smallest possible size in bytes that
|
||||
// the DeclType could be serialized to in JSON.
|
||||
MinSerializedSize int64
|
||||
|
||||
celType *cel.Type
|
||||
traitMask int
|
||||
defaultValue ref.Val
|
||||
}
|
||||
|
||||
// MaybeAssignTypeName attempts to set the DeclType name to a fully qualified name, if the type
|
||||
// is of `object` type.
|
||||
//
|
||||
// The DeclType must return true for `IsObject` or this assignment will error.
|
||||
func (t *DeclType) MaybeAssignTypeName(name string) *DeclType {
|
||||
if t.IsObject() {
|
||||
objUpdated := false
|
||||
if t.name != "object" {
|
||||
name = t.name
|
||||
} else {
|
||||
objUpdated = true
|
||||
}
|
||||
fieldMap := make(map[string]*DeclField, len(t.Fields))
|
||||
for fieldName, field := range t.Fields {
|
||||
fieldType := field.Type
|
||||
fieldTypeName := fmt.Sprintf("%s.%s", name, fieldName)
|
||||
updated := fieldType.MaybeAssignTypeName(fieldTypeName)
|
||||
if updated == fieldType {
|
||||
fieldMap[fieldName] = field
|
||||
continue
|
||||
}
|
||||
objUpdated = true
|
||||
fieldMap[fieldName] = &DeclField{
|
||||
Name: fieldName,
|
||||
Type: updated,
|
||||
Required: field.Required,
|
||||
enumValues: field.enumValues,
|
||||
defaultValue: field.defaultValue,
|
||||
}
|
||||
}
|
||||
if !objUpdated {
|
||||
return t
|
||||
}
|
||||
return &DeclType{
|
||||
name: name,
|
||||
Fields: fieldMap,
|
||||
KeyType: t.KeyType,
|
||||
ElemType: t.ElemType,
|
||||
TypeParam: t.TypeParam,
|
||||
Metadata: t.Metadata,
|
||||
celType: cel.ObjectType(name),
|
||||
traitMask: t.traitMask,
|
||||
defaultValue: t.defaultValue,
|
||||
MinSerializedSize: t.MinSerializedSize,
|
||||
}
|
||||
}
|
||||
if t.IsMap() {
|
||||
elemTypeName := fmt.Sprintf("%s.@elem", name)
|
||||
updated := t.ElemType.MaybeAssignTypeName(elemTypeName)
|
||||
if updated == t.ElemType {
|
||||
return t
|
||||
}
|
||||
return NewMapType(t.KeyType, updated, t.MaxElements)
|
||||
}
|
||||
if t.IsList() {
|
||||
elemTypeName := fmt.Sprintf("%s.@idx", name)
|
||||
updated := t.ElemType.MaybeAssignTypeName(elemTypeName)
|
||||
if updated == t.ElemType {
|
||||
return t
|
||||
}
|
||||
return NewListType(updated, t.MaxElements)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ExprType returns the CEL expression type of this declaration.
|
||||
func (t *DeclType) ExprType() (*exprpb.Type, error) {
|
||||
return cel.TypeToExprType(t.celType)
|
||||
}
|
||||
|
||||
// CelType returns the CEL type of this declaration.
|
||||
func (t *DeclType) CelType() *cel.Type {
|
||||
return t.celType
|
||||
}
|
||||
|
||||
// FindField returns the DeclField with the given name if present.
|
||||
func (t *DeclType) FindField(name string) (*DeclField, bool) {
|
||||
f, found := t.Fields[name]
|
||||
return f, found
|
||||
}
|
||||
|
||||
// HasTrait implements the CEL ref.Type interface making this type declaration suitable for use
|
||||
// within the CEL evaluator.
|
||||
func (t *DeclType) HasTrait(trait int) bool {
|
||||
if t.traitMask&trait == trait {
|
||||
return true
|
||||
}
|
||||
if t.defaultValue == nil {
|
||||
return false
|
||||
}
|
||||
_, isDecl := t.defaultValue.Type().(*DeclType)
|
||||
if isDecl {
|
||||
return false
|
||||
}
|
||||
return t.defaultValue.Type().HasTrait(trait)
|
||||
}
|
||||
|
||||
// IsList returns whether the declaration is a `list` type which defines a parameterized element
|
||||
// type, but not a parameterized key type or fields.
|
||||
func (t *DeclType) IsList() bool {
|
||||
return t.KeyType == nil && t.ElemType != nil && t.Fields == nil
|
||||
}
|
||||
|
||||
// IsMap returns whether the declaration is a 'map' type which defines parameterized key and
|
||||
// element types, but not fields.
|
||||
func (t *DeclType) IsMap() bool {
|
||||
return t.KeyType != nil && t.ElemType != nil && t.Fields == nil
|
||||
}
|
||||
|
||||
// IsObject returns whether the declartion is an 'object' type which defined a set of typed fields.
|
||||
func (t *DeclType) IsObject() bool {
|
||||
return t.KeyType == nil && t.ElemType == nil && t.Fields != nil
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface method.
|
||||
func (t *DeclType) String() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// TypeName returns the fully qualified type name for the DeclType.
|
||||
func (t *DeclType) TypeName() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// DefaultValue returns the CEL ref.Val representing the default value for this object type,
|
||||
// if one exists.
|
||||
func (t *DeclType) DefaultValue() ref.Val {
|
||||
return t.defaultValue
|
||||
}
|
||||
|
||||
// FieldTypeMap constructs a map of the field and object types nested within a given type.
|
||||
func FieldTypeMap(path string, t *DeclType) map[string]*DeclType {
|
||||
if t.IsObject() && t.TypeName() != "object" {
|
||||
path = t.TypeName()
|
||||
}
|
||||
types := make(map[string]*DeclType)
|
||||
buildDeclTypes(path, t, types)
|
||||
return types
|
||||
}
|
||||
|
||||
func buildDeclTypes(path string, t *DeclType, types map[string]*DeclType) {
|
||||
// Ensure object types are properly named according to where they appear in the schema.
|
||||
if t.IsObject() {
|
||||
// Hack to ensure that names are uniquely qualified and work well with the type
|
||||
// resolution steps which require fully qualified type names for field resolution
|
||||
// to function properly.
|
||||
types[t.TypeName()] = t
|
||||
for name, field := range t.Fields {
|
||||
fieldPath := fmt.Sprintf("%s.%s", path, name)
|
||||
buildDeclTypes(fieldPath, field.Type, types)
|
||||
}
|
||||
}
|
||||
// Map element properties to type names if needed.
|
||||
if t.IsMap() {
|
||||
mapElemPath := fmt.Sprintf("%s.@elem", path)
|
||||
buildDeclTypes(mapElemPath, t.ElemType, types)
|
||||
types[path] = t
|
||||
}
|
||||
// List element properties.
|
||||
if t.IsList() {
|
||||
listIdxPath := fmt.Sprintf("%s.@idx", path)
|
||||
buildDeclTypes(listIdxPath, t.ElemType, types)
|
||||
types[path] = t
|
||||
}
|
||||
}
|
||||
|
||||
// DeclField describes the name, ordinal, and optionality of a field declaration within a type.
|
||||
type DeclField struct {
|
||||
Name string
|
||||
Type *DeclType
|
||||
Required bool
|
||||
enumValues []interface{}
|
||||
defaultValue interface{}
|
||||
}
|
||||
|
||||
func NewDeclField(name string, declType *DeclType, required bool, enumValues []interface{}, defaultValue interface{}) *DeclField {
|
||||
return &DeclField{
|
||||
Name: name,
|
||||
Type: declType,
|
||||
Required: required,
|
||||
enumValues: enumValues,
|
||||
defaultValue: defaultValue,
|
||||
}
|
||||
}
|
||||
|
||||
// TypeName returns the string type name of the field.
|
||||
func (f *DeclField) TypeName() string {
|
||||
return f.Type.TypeName()
|
||||
}
|
||||
|
||||
// DefaultValue returns the zero value associated with the field.
|
||||
func (f *DeclField) DefaultValue() ref.Val {
|
||||
if f.defaultValue != nil {
|
||||
return types.DefaultTypeAdapter.NativeToValue(f.defaultValue)
|
||||
}
|
||||
return f.Type.DefaultValue()
|
||||
}
|
||||
|
||||
// EnumValues returns the set of values that this field may take.
|
||||
func (f *DeclField) EnumValues() []ref.Val {
|
||||
if f.enumValues == nil || len(f.enumValues) == 0 {
|
||||
return []ref.Val{}
|
||||
}
|
||||
ev := make([]ref.Val, len(f.enumValues))
|
||||
for i, e := range f.enumValues {
|
||||
ev[i] = types.DefaultTypeAdapter.NativeToValue(e)
|
||||
}
|
||||
return ev
|
||||
}
|
||||
|
||||
// NewRuleTypes returns an Open API Schema-based type-system which is CEL compatible.
|
||||
func NewRuleTypes(kind string,
|
||||
declType *DeclType,
|
||||
res Resolver) (*RuleTypes, error) {
|
||||
// Note, if the schema indicates that it's actually based on another proto
|
||||
// then prefer the proto definition. For expressions in the proto, a new field
|
||||
// annotation will be needed to indicate the expected environment and type of
|
||||
// the expression.
|
||||
schemaTypes, err := newSchemaTypeProvider(kind, declType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if schemaTypes == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &RuleTypes{
|
||||
ruleSchemaDeclTypes: schemaTypes,
|
||||
resolver: res,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RuleTypes extends the CEL ref.TypeProvider interface and provides an Open API Schema-based
|
||||
// type-system.
|
||||
type RuleTypes struct {
|
||||
ref.TypeProvider
|
||||
ruleSchemaDeclTypes *schemaTypeProvider
|
||||
typeAdapter ref.TypeAdapter
|
||||
resolver Resolver
|
||||
}
|
||||
|
||||
// EnvOptions returns a set of cel.EnvOption values which includes the declaration set
|
||||
// as well as a custom ref.TypeProvider.
|
||||
//
|
||||
// Note, the standard declaration set includes 'rule' which is defined as the top-level rule-schema
|
||||
// type if one is configured.
|
||||
//
|
||||
// If the RuleTypes value is nil, an empty []cel.EnvOption set is returned.
|
||||
func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
if rt == nil {
|
||||
return []cel.EnvOption{}, nil
|
||||
}
|
||||
var ta ref.TypeAdapter = types.DefaultTypeAdapter
|
||||
tpa, ok := tp.(ref.TypeAdapter)
|
||||
if ok {
|
||||
ta = tpa
|
||||
}
|
||||
rtWithTypes := &RuleTypes{
|
||||
TypeProvider: tp,
|
||||
typeAdapter: ta,
|
||||
ruleSchemaDeclTypes: rt.ruleSchemaDeclTypes,
|
||||
resolver: rt.resolver,
|
||||
}
|
||||
for name, declType := range rt.ruleSchemaDeclTypes.types {
|
||||
tpType, found := tp.FindType(name)
|
||||
expT, err := declType.ExprType()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to get cel type: %s", err)
|
||||
}
|
||||
if found && !proto.Equal(tpType, expT) {
|
||||
return nil, fmt.Errorf(
|
||||
"type %s definition differs between CEL environment and rule", name)
|
||||
}
|
||||
}
|
||||
return []cel.EnvOption{
|
||||
cel.CustomTypeProvider(rtWithTypes),
|
||||
cel.CustomTypeAdapter(rtWithTypes),
|
||||
cel.Variable("rule", rt.ruleSchemaDeclTypes.root.CelType()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FindType attempts to resolve the typeName provided from the rule's rule-schema, or if not
|
||||
// from the embedded ref.TypeProvider.
|
||||
//
|
||||
// FindType overrides the default type-finding behavior of the embedded TypeProvider.
|
||||
//
|
||||
// Note, when the type name is based on the Open API Schema, the name will reflect the object path
|
||||
// where the type definition appears.
|
||||
func (rt *RuleTypes) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
if rt == nil {
|
||||
return nil, false
|
||||
}
|
||||
declType, found := rt.findDeclType(typeName)
|
||||
if found {
|
||||
expT, err := declType.ExprType()
|
||||
if err != nil {
|
||||
return expT, false
|
||||
}
|
||||
return expT, found
|
||||
}
|
||||
return rt.TypeProvider.FindType(typeName)
|
||||
}
|
||||
|
||||
// FindDeclType returns the CPT type description which can be mapped to a CEL type.
|
||||
func (rt *RuleTypes) FindDeclType(typeName string) (*DeclType, bool) {
|
||||
if rt == nil {
|
||||
return nil, false
|
||||
}
|
||||
return rt.findDeclType(typeName)
|
||||
}
|
||||
|
||||
// FindFieldType returns a field type given a type name and field name, if found.
|
||||
//
|
||||
// Note, the type name for an Open API Schema type is likely to be its qualified object path.
|
||||
// If, in the future an object instance rather than a type name were provided, the field
|
||||
// resolution might more accurately reflect the expected type model. However, in this case
|
||||
// concessions were made to align with the existing CEL interfaces.
|
||||
func (rt *RuleTypes) FindFieldType(typeName, fieldName string) (*ref.FieldType, bool) {
|
||||
st, found := rt.findDeclType(typeName)
|
||||
if !found {
|
||||
return rt.TypeProvider.FindFieldType(typeName, fieldName)
|
||||
}
|
||||
|
||||
f, found := st.Fields[fieldName]
|
||||
if found {
|
||||
ft := f.Type
|
||||
expT, err := ft.ExprType()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &ref.FieldType{
|
||||
Type: expT,
|
||||
}, true
|
||||
}
|
||||
// This could be a dynamic map.
|
||||
if st.IsMap() {
|
||||
et := st.ElemType
|
||||
expT, err := et.ExprType()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &ref.FieldType{
|
||||
Type: expT,
|
||||
}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// NativeToValue is an implementation of the ref.TypeAdapater interface which supports conversion
|
||||
// of rule values to CEL ref.Val instances.
|
||||
func (rt *RuleTypes) NativeToValue(val interface{}) ref.Val {
|
||||
return rt.typeAdapter.NativeToValue(val)
|
||||
}
|
||||
|
||||
// TypeNames returns the list of type names declared within the RuleTypes object.
|
||||
func (rt *RuleTypes) TypeNames() []string {
|
||||
typeNames := make([]string, len(rt.ruleSchemaDeclTypes.types))
|
||||
i := 0
|
||||
for name := range rt.ruleSchemaDeclTypes.types {
|
||||
typeNames[i] = name
|
||||
i++
|
||||
}
|
||||
return typeNames
|
||||
}
|
||||
|
||||
func (rt *RuleTypes) findDeclType(typeName string) (*DeclType, bool) {
|
||||
declType, found := rt.ruleSchemaDeclTypes.types[typeName]
|
||||
if found {
|
||||
return declType, true
|
||||
}
|
||||
declType, found = rt.resolver.FindType(typeName)
|
||||
if found {
|
||||
return declType, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func newSchemaTypeProvider(kind string, declType *DeclType) (*schemaTypeProvider, error) {
|
||||
if declType == nil {
|
||||
return nil, nil
|
||||
}
|
||||
root := declType.MaybeAssignTypeName(kind)
|
||||
types := FieldTypeMap(kind, root)
|
||||
return &schemaTypeProvider{
|
||||
root: root,
|
||||
types: types,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type schemaTypeProvider struct {
|
||||
root *DeclType
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
var (
|
||||
// AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the
|
||||
// types supported.
|
||||
AnyType = NewSimpleTypeWithMinSize("any", cel.AnyType, nil, 1)
|
||||
|
||||
// BoolType is equivalent to the CEL 'bool' type.
|
||||
BoolType = NewSimpleTypeWithMinSize("bool", cel.BoolType, types.False, MinBoolSize)
|
||||
|
||||
// BytesType is equivalent to the CEL 'bytes' type.
|
||||
BytesType = NewSimpleTypeWithMinSize("bytes", cel.BytesType, types.Bytes([]byte{}), MinStringSize)
|
||||
|
||||
// DoubleType is equivalent to the CEL 'double' type which is a 64-bit floating point value.
|
||||
DoubleType = NewSimpleTypeWithMinSize("double", cel.DoubleType, types.Double(0), MinNumberSize)
|
||||
|
||||
// DurationType is equivalent to the CEL 'duration' type.
|
||||
DurationType = NewSimpleTypeWithMinSize("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}, MinDurationSizeJSON)
|
||||
|
||||
// DateType is equivalent to the CEL 'date' type.
|
||||
DateType = NewSimpleTypeWithMinSize("date", cel.TimestampType, types.Timestamp{Time: time.Time{}}, JSONDateSize)
|
||||
|
||||
// DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be
|
||||
// determined at runtime rather than compile time.
|
||||
DynType = NewSimpleTypeWithMinSize("dyn", cel.DynType, nil, 1)
|
||||
|
||||
// IntType is equivalent to the CEL 'int' type which is a 64-bit signed int.
|
||||
IntType = NewSimpleTypeWithMinSize("int", cel.IntType, types.IntZero, MinNumberSize)
|
||||
|
||||
// NullType is equivalent to the CEL 'null_type'.
|
||||
NullType = NewSimpleTypeWithMinSize("null_type", cel.NullType, types.NullValue, 4)
|
||||
|
||||
// StringType is equivalent to the CEL 'string' type which is expected to be a UTF-8 string.
|
||||
// StringType values may either be string literals or expression strings.
|
||||
StringType = NewSimpleTypeWithMinSize("string", cel.StringType, types.String(""), MinStringSize)
|
||||
|
||||
// TimestampType corresponds to the well-known protobuf.Timestamp type supported within CEL.
|
||||
// Note that both the OpenAPI date and date-time types map onto TimestampType, so not all types
|
||||
// labeled as Timestamp will necessarily have the same MinSerializedSize.
|
||||
TimestampType = NewSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, JSONDateSize)
|
||||
|
||||
// UintType is equivalent to the CEL 'uint' type.
|
||||
UintType = NewSimpleTypeWithMinSize("uint", cel.UintType, types.Uint(0), 1)
|
||||
|
||||
// ListType is equivalent to the CEL 'list' type.
|
||||
ListType = NewListType(AnyType, noMaxLength)
|
||||
|
||||
// MapType is equivalent to the CEL 'map' type.
|
||||
MapType = NewMapType(AnyType, AnyType, noMaxLength)
|
||||
)
|
80
vendor/k8s.io/apiserver/pkg/cel/url.go
generated
vendored
Normal file
80
vendor/k8s.io/apiserver/pkg/cel/url.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
"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/ref"
|
||||
)
|
||||
|
||||
// URL provides a CEL representation of a URL.
|
||||
type URL struct {
|
||||
*url.URL
|
||||
}
|
||||
|
||||
var (
|
||||
URLObject = decls.NewObjectType("kubernetes.URL")
|
||||
typeValue = types.NewTypeValue("kubernetes.URL")
|
||||
URLType = cel.ObjectType("kubernetes.URL")
|
||||
)
|
||||
|
||||
// ConvertToNative implements ref.Val.ConvertToNative.
|
||||
func (d URL) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
if reflect.TypeOf(d.URL).AssignableTo(typeDesc) {
|
||||
return d.URL, nil
|
||||
}
|
||||
if reflect.TypeOf("").AssignableTo(typeDesc) {
|
||||
return d.URL.String(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("type conversion error from 'URL' to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// ConvertToType implements ref.Val.ConvertToType.
|
||||
func (d URL) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
switch typeVal {
|
||||
case typeValue:
|
||||
return d
|
||||
case types.TypeType:
|
||||
return typeValue
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", typeValue, typeVal)
|
||||
}
|
||||
|
||||
// Equal implements ref.Val.Equal.
|
||||
func (d URL) Equal(other ref.Val) ref.Val {
|
||||
otherDur, ok := other.(URL)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
return types.Bool(d.URL.String() == otherDur.URL.String())
|
||||
}
|
||||
|
||||
// Type implements ref.Val.Type.
|
||||
func (d URL) Type() ref.Type {
|
||||
return typeValue
|
||||
}
|
||||
|
||||
// Value implements ref.Val.Value.
|
||||
func (d URL) Value() interface{} {
|
||||
return d.URL
|
||||
}
|
769
vendor/k8s.io/apiserver/pkg/cel/value.go
generated
vendored
Normal file
769
vendor/k8s.io/apiserver/pkg/cel/value.go
generated
vendored
Normal file
@ -0,0 +1,769 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
)
|
||||
|
||||
// EncodeStyle is a hint for string encoding of parsed values.
|
||||
type EncodeStyle int
|
||||
|
||||
const (
|
||||
// BlockValueStyle is the default string encoding which preserves whitespace and newlines.
|
||||
BlockValueStyle EncodeStyle = iota
|
||||
|
||||
// FlowValueStyle indicates that the string is an inline representation of complex types.
|
||||
FlowValueStyle
|
||||
|
||||
// FoldedValueStyle is a multiline string with whitespace and newlines trimmed to a single
|
||||
// a whitespace. Repeated newlines are replaced with a single newline rather than a single
|
||||
// whitespace.
|
||||
FoldedValueStyle
|
||||
|
||||
// LiteralStyle is a multiline string that preserves newlines, but trims all other whitespace
|
||||
// to a single character.
|
||||
LiteralStyle
|
||||
)
|
||||
|
||||
// NewEmptyDynValue returns the zero-valued DynValue.
|
||||
func NewEmptyDynValue() *DynValue {
|
||||
// note: 0 is not a valid parse node identifier.
|
||||
dv, _ := NewDynValue(0, nil)
|
||||
return dv
|
||||
}
|
||||
|
||||
// NewDynValue returns a DynValue that corresponds to a parse node id and value.
|
||||
func NewDynValue(id int64, val interface{}) (*DynValue, error) {
|
||||
dv := &DynValue{ID: id}
|
||||
err := dv.SetValue(val)
|
||||
return dv, err
|
||||
}
|
||||
|
||||
// DynValue is a dynamically typed value used to describe unstructured content.
|
||||
// Whether the value has the desired type is determined by where it is used within the Instance or
|
||||
// Template, and whether there are schemas which might enforce a more rigid type definition.
|
||||
type DynValue struct {
|
||||
ID int64
|
||||
EncodeStyle EncodeStyle
|
||||
value interface{}
|
||||
exprValue ref.Val
|
||||
declType *DeclType
|
||||
}
|
||||
|
||||
// DeclType returns the policy model type of the dyn value.
|
||||
func (dv *DynValue) DeclType() *DeclType {
|
||||
return dv.declType
|
||||
}
|
||||
|
||||
// ConvertToNative is an implementation of the CEL ref.Val method used to adapt between CEL types
|
||||
// and Go-native types.
|
||||
//
|
||||
// The default behavior of this method is to first convert to a CEL type which has a well-defined
|
||||
// set of conversion behaviors and proxy to the CEL ConvertToNative method for the type.
|
||||
func (dv *DynValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
ev := dv.ExprValue()
|
||||
if types.IsError(ev) {
|
||||
return nil, ev.(*types.Err)
|
||||
}
|
||||
return ev.ConvertToNative(typeDesc)
|
||||
}
|
||||
|
||||
// Equal returns whether the dyn value is equal to a given CEL value.
|
||||
func (dv *DynValue) Equal(other ref.Val) ref.Val {
|
||||
dvType := dv.Type()
|
||||
otherType := other.Type()
|
||||
// Preserve CEL's homogeneous equality constraint.
|
||||
if dvType.TypeName() != otherType.TypeName() {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
switch v := dv.value.(type) {
|
||||
case ref.Val:
|
||||
return v.Equal(other)
|
||||
case PlainTextValue:
|
||||
return celBool(string(v) == other.Value().(string))
|
||||
case *MultilineStringValue:
|
||||
return celBool(v.Value == other.Value().(string))
|
||||
case time.Duration:
|
||||
otherDuration := other.Value().(time.Duration)
|
||||
return celBool(v == otherDuration)
|
||||
case time.Time:
|
||||
otherTimestamp := other.Value().(time.Time)
|
||||
return celBool(v.Equal(otherTimestamp))
|
||||
default:
|
||||
return celBool(reflect.DeepEqual(v, other.Value()))
|
||||
}
|
||||
}
|
||||
|
||||
// ExprValue converts the DynValue into a CEL value.
|
||||
func (dv *DynValue) ExprValue() ref.Val {
|
||||
return dv.exprValue
|
||||
}
|
||||
|
||||
// Value returns the underlying value held by this reference.
|
||||
func (dv *DynValue) Value() interface{} {
|
||||
return dv.value
|
||||
}
|
||||
|
||||
// SetValue updates the underlying value held by this reference.
|
||||
func (dv *DynValue) SetValue(value interface{}) error {
|
||||
dv.value = value
|
||||
var err error
|
||||
dv.exprValue, dv.declType, err = exprValue(value)
|
||||
return err
|
||||
}
|
||||
|
||||
// Type returns the CEL type for the given value.
|
||||
func (dv *DynValue) Type() ref.Type {
|
||||
return dv.ExprValue().Type()
|
||||
}
|
||||
|
||||
func exprValue(value interface{}) (ref.Val, *DeclType, error) {
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
return types.Bool(v), BoolType, nil
|
||||
case []byte:
|
||||
return types.Bytes(v), BytesType, nil
|
||||
case float64:
|
||||
return types.Double(v), DoubleType, nil
|
||||
case int64:
|
||||
return types.Int(v), IntType, nil
|
||||
case string:
|
||||
return types.String(v), StringType, nil
|
||||
case uint64:
|
||||
return types.Uint(v), UintType, nil
|
||||
case time.Duration:
|
||||
return types.Duration{Duration: v}, DurationType, nil
|
||||
case time.Time:
|
||||
return types.Timestamp{Time: v}, TimestampType, nil
|
||||
case types.Null:
|
||||
return v, NullType, nil
|
||||
case *ListValue:
|
||||
return v, ListType, nil
|
||||
case *MapValue:
|
||||
return v, MapType, nil
|
||||
case *ObjectValue:
|
||||
return v, v.objectType, nil
|
||||
default:
|
||||
return nil, unknownType, fmt.Errorf("unsupported type: (%T)%v", v, v)
|
||||
}
|
||||
}
|
||||
|
||||
// PlainTextValue is a text string literal which must not be treated as an expression.
|
||||
type PlainTextValue string
|
||||
|
||||
// MultilineStringValue is a multiline string value which has been parsed in a way which omits
|
||||
// whitespace as well as a raw form which preserves whitespace.
|
||||
type MultilineStringValue struct {
|
||||
Value string
|
||||
Raw string
|
||||
}
|
||||
|
||||
func newStructValue() *structValue {
|
||||
return &structValue{
|
||||
Fields: []*Field{},
|
||||
fieldMap: map[string]*Field{},
|
||||
}
|
||||
}
|
||||
|
||||
type structValue struct {
|
||||
Fields []*Field
|
||||
fieldMap map[string]*Field
|
||||
}
|
||||
|
||||
// AddField appends a MapField to the MapValue and indexes the field by name.
|
||||
func (sv *structValue) AddField(field *Field) {
|
||||
sv.Fields = append(sv.Fields, field)
|
||||
sv.fieldMap[field.Name] = field
|
||||
}
|
||||
|
||||
// ConvertToNative converts the MapValue type to a native go types.
|
||||
func (sv *structValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
if typeDesc.Kind() != reflect.Map &&
|
||||
typeDesc.Kind() != reflect.Struct &&
|
||||
typeDesc.Kind() != reflect.Pointer &&
|
||||
typeDesc.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// Unwrap pointers, but track their use.
|
||||
isPtr := false
|
||||
if typeDesc.Kind() == reflect.Pointer {
|
||||
tk := typeDesc
|
||||
typeDesc = typeDesc.Elem()
|
||||
if typeDesc.Kind() == reflect.Pointer {
|
||||
return nil, fmt.Errorf("unsupported type conversion to '%v'", tk)
|
||||
}
|
||||
isPtr = true
|
||||
}
|
||||
|
||||
if typeDesc.Kind() == reflect.Map {
|
||||
keyType := typeDesc.Key()
|
||||
if keyType.Kind() != reflect.String && keyType.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("object fields cannot be converted to type '%v'", keyType)
|
||||
}
|
||||
elemType := typeDesc.Elem()
|
||||
sz := len(sv.fieldMap)
|
||||
ntvMap := reflect.MakeMapWithSize(typeDesc, sz)
|
||||
for name, val := range sv.fieldMap {
|
||||
refVal, err := val.Ref.ConvertToNative(elemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntvMap.SetMapIndex(reflect.ValueOf(name), reflect.ValueOf(refVal))
|
||||
}
|
||||
return ntvMap.Interface(), nil
|
||||
}
|
||||
|
||||
if typeDesc.Kind() == reflect.Struct {
|
||||
ntvObjPtr := reflect.New(typeDesc)
|
||||
ntvObj := ntvObjPtr.Elem()
|
||||
for name, val := range sv.fieldMap {
|
||||
f := ntvObj.FieldByName(name)
|
||||
if !f.IsValid() {
|
||||
return nil, fmt.Errorf("type conversion error, no such field %s in type %v",
|
||||
name, typeDesc)
|
||||
}
|
||||
fv, err := val.Ref.ConvertToNative(f.Type())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Set(reflect.ValueOf(fv))
|
||||
}
|
||||
if isPtr {
|
||||
return ntvObjPtr.Interface(), nil
|
||||
}
|
||||
return ntvObj.Interface(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// GetField returns a MapField by name if one exists.
|
||||
func (sv *structValue) GetField(name string) (*Field, bool) {
|
||||
field, found := sv.fieldMap[name]
|
||||
return field, found
|
||||
}
|
||||
|
||||
// IsSet returns whether the given field, which is defined, has also been set.
|
||||
func (sv *structValue) IsSet(key ref.Val) ref.Val {
|
||||
k, ok := key.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(key)
|
||||
}
|
||||
name := string(k)
|
||||
_, found := sv.fieldMap[name]
|
||||
return celBool(found)
|
||||
}
|
||||
|
||||
// NewObjectValue creates a struct value with a schema type and returns the empty ObjectValue.
|
||||
func NewObjectValue(sType *DeclType) *ObjectValue {
|
||||
return &ObjectValue{
|
||||
structValue: newStructValue(),
|
||||
objectType: sType,
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectValue is a struct with a custom schema type which indicates the fields and types
|
||||
// associated with the structure.
|
||||
type ObjectValue struct {
|
||||
*structValue
|
||||
objectType *DeclType
|
||||
}
|
||||
|
||||
// ConvertToType is an implementation of the CEL ref.Val interface method.
|
||||
func (o *ObjectValue) ConvertToType(t ref.Type) ref.Val {
|
||||
if t == types.TypeType {
|
||||
return types.NewObjectTypeValue(o.objectType.TypeName())
|
||||
}
|
||||
if t.TypeName() == o.objectType.TypeName() {
|
||||
return o
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", o.Type(), t)
|
||||
}
|
||||
|
||||
// Equal returns true if the two object types are equal and their field values are equal.
|
||||
func (o *ObjectValue) Equal(other ref.Val) ref.Val {
|
||||
// Preserve CEL's homogeneous equality semantics.
|
||||
if o.objectType.TypeName() != other.Type().TypeName() {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
o2 := other.(traits.Indexer)
|
||||
for name := range o.objectType.Fields {
|
||||
k := types.String(name)
|
||||
v := o.Get(k)
|
||||
ov := o2.Get(k)
|
||||
vEq := v.Equal(ov)
|
||||
if vEq != types.True {
|
||||
return vEq
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Get returns the value of the specified field.
|
||||
//
|
||||
// If the field is set, its value is returned. If the field is not set, the default value for the
|
||||
// field is returned thus allowing for safe-traversal and preserving proto-like field traversal
|
||||
// semantics for Open API Schema backed types.
|
||||
func (o *ObjectValue) Get(name ref.Val) ref.Val {
|
||||
n, ok := name.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(n)
|
||||
}
|
||||
nameStr := string(n)
|
||||
field, found := o.fieldMap[nameStr]
|
||||
if found {
|
||||
return field.Ref.ExprValue()
|
||||
}
|
||||
fieldDef, found := o.objectType.Fields[nameStr]
|
||||
if !found {
|
||||
return types.NewErr("no such field: %s", nameStr)
|
||||
}
|
||||
defValue := fieldDef.DefaultValue()
|
||||
if defValue != nil {
|
||||
return defValue
|
||||
}
|
||||
return types.NewErr("no default for type: %s", fieldDef.TypeName())
|
||||
}
|
||||
|
||||
// Type returns the CEL type value of the object.
|
||||
func (o *ObjectValue) Type() ref.Type {
|
||||
return o.objectType
|
||||
}
|
||||
|
||||
// Value returns the Go-native representation of the object.
|
||||
func (o *ObjectValue) Value() interface{} {
|
||||
return o
|
||||
}
|
||||
|
||||
// NewMapValue returns an empty MapValue.
|
||||
func NewMapValue() *MapValue {
|
||||
return &MapValue{
|
||||
structValue: newStructValue(),
|
||||
}
|
||||
}
|
||||
|
||||
// MapValue declares an object with a set of named fields whose values are dynamically typed.
|
||||
type MapValue struct {
|
||||
*structValue
|
||||
}
|
||||
|
||||
// ConvertToObject produces an ObjectValue from the MapValue with the associated schema type.
|
||||
//
|
||||
// The conversion is shallow and the memory shared between the Object and Map as all references
|
||||
// to the map are expected to be replaced with the Object reference.
|
||||
func (m *MapValue) ConvertToObject(declType *DeclType) *ObjectValue {
|
||||
return &ObjectValue{
|
||||
structValue: m.structValue,
|
||||
objectType: declType,
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns whether the given key is contained in the MapValue.
|
||||
func (m *MapValue) Contains(key ref.Val) ref.Val {
|
||||
v, found := m.Find(key)
|
||||
if v != nil && types.IsUnknownOrError(v) {
|
||||
return v
|
||||
}
|
||||
return celBool(found)
|
||||
}
|
||||
|
||||
// ConvertToType converts the MapValue to another CEL type, if possible.
|
||||
func (m *MapValue) ConvertToType(t ref.Type) ref.Val {
|
||||
switch t {
|
||||
case types.MapType:
|
||||
return m
|
||||
case types.TypeType:
|
||||
return types.MapType
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", m.Type(), t)
|
||||
}
|
||||
|
||||
// Equal returns true if the maps are of the same size, have the same keys, and the key-values
|
||||
// from each map are equal.
|
||||
func (m *MapValue) Equal(other ref.Val) ref.Val {
|
||||
oMap, isMap := other.(traits.Mapper)
|
||||
if !isMap {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
if m.Size() != oMap.Size() {
|
||||
return types.False
|
||||
}
|
||||
for name, field := range m.fieldMap {
|
||||
k := types.String(name)
|
||||
ov, found := oMap.Find(k)
|
||||
if !found {
|
||||
return types.False
|
||||
}
|
||||
v := field.Ref.ExprValue()
|
||||
vEq := v.Equal(ov)
|
||||
if vEq != types.True {
|
||||
return vEq
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Find returns the value for the key in the map, if found.
|
||||
func (m *MapValue) Find(name ref.Val) (ref.Val, bool) {
|
||||
// Currently only maps with string keys are supported as this is best aligned with JSON,
|
||||
// and also much simpler to support.
|
||||
n, ok := name.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(n), true
|
||||
}
|
||||
nameStr := string(n)
|
||||
field, found := m.fieldMap[nameStr]
|
||||
if found {
|
||||
return field.Ref.ExprValue(), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Get returns the value for the key in the map, or error if not found.
|
||||
func (m *MapValue) Get(key ref.Val) ref.Val {
|
||||
v, found := m.Find(key)
|
||||
if found {
|
||||
return v
|
||||
}
|
||||
return types.ValOrErr(key, "no such key: %v", key)
|
||||
}
|
||||
|
||||
// Iterator produces a traits.Iterator which walks over the map keys.
|
||||
//
|
||||
// The Iterator is frequently used within comprehensions.
|
||||
func (m *MapValue) Iterator() traits.Iterator {
|
||||
keys := make([]ref.Val, len(m.fieldMap))
|
||||
i := 0
|
||||
for k := range m.fieldMap {
|
||||
keys[i] = types.String(k)
|
||||
i++
|
||||
}
|
||||
return &baseMapIterator{
|
||||
baseVal: &baseVal{},
|
||||
keys: keys,
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the number of keys in the map.
|
||||
func (m *MapValue) Size() ref.Val {
|
||||
return types.Int(len(m.Fields))
|
||||
}
|
||||
|
||||
// Type returns the CEL ref.Type for the map.
|
||||
func (m *MapValue) Type() ref.Type {
|
||||
return types.MapType
|
||||
}
|
||||
|
||||
// Value returns the Go-native representation of the MapValue.
|
||||
func (m *MapValue) Value() interface{} {
|
||||
return m
|
||||
}
|
||||
|
||||
type baseMapIterator struct {
|
||||
*baseVal
|
||||
keys []ref.Val
|
||||
idx int
|
||||
}
|
||||
|
||||
// HasNext implements the traits.Iterator interface method.
|
||||
func (it *baseMapIterator) HasNext() ref.Val {
|
||||
if it.idx < len(it.keys) {
|
||||
return types.True
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
// Next implements the traits.Iterator interface method.
|
||||
func (it *baseMapIterator) Next() ref.Val {
|
||||
key := it.keys[it.idx]
|
||||
it.idx++
|
||||
return key
|
||||
}
|
||||
|
||||
// Type implements the CEL ref.Val interface metohd.
|
||||
func (it *baseMapIterator) Type() ref.Type {
|
||||
return types.IteratorType
|
||||
}
|
||||
|
||||
// NewField returns a MapField instance with an empty DynValue that refers to the
|
||||
// specified parse node id and field name.
|
||||
func NewField(id int64, name string) *Field {
|
||||
return &Field{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Ref: NewEmptyDynValue(),
|
||||
}
|
||||
}
|
||||
|
||||
// Field specifies a field name and a reference to a dynamic value.
|
||||
type Field struct {
|
||||
ID int64
|
||||
Name string
|
||||
Ref *DynValue
|
||||
}
|
||||
|
||||
// NewListValue returns an empty ListValue instance.
|
||||
func NewListValue() *ListValue {
|
||||
return &ListValue{
|
||||
Entries: []*DynValue{},
|
||||
}
|
||||
}
|
||||
|
||||
// ListValue contains a list of dynamically typed entries.
|
||||
type ListValue struct {
|
||||
Entries []*DynValue
|
||||
initValueSet sync.Once
|
||||
valueSet map[ref.Val]struct{}
|
||||
}
|
||||
|
||||
// Add concatenates two lists together to produce a new CEL list value.
|
||||
func (lv *ListValue) Add(other ref.Val) ref.Val {
|
||||
oArr, isArr := other.(traits.Lister)
|
||||
if !isArr {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
szRight := len(lv.Entries)
|
||||
szLeft := int(oArr.Size().(types.Int))
|
||||
sz := szRight + szLeft
|
||||
combo := make([]ref.Val, sz)
|
||||
for i := 0; i < szRight; i++ {
|
||||
combo[i] = lv.Entries[i].ExprValue()
|
||||
}
|
||||
for i := 0; i < szLeft; i++ {
|
||||
combo[i+szRight] = oArr.Get(types.Int(i))
|
||||
}
|
||||
return types.DefaultTypeAdapter.NativeToValue(combo)
|
||||
}
|
||||
|
||||
// Append adds another entry into the ListValue.
|
||||
func (lv *ListValue) Append(entry *DynValue) {
|
||||
lv.Entries = append(lv.Entries, entry)
|
||||
// The append resets all previously built indices.
|
||||
lv.initValueSet = sync.Once{}
|
||||
}
|
||||
|
||||
// Contains returns whether the input `val` is equal to an element in the list.
|
||||
//
|
||||
// If any pair-wise comparison between the input value and the list element is an error, the
|
||||
// operation will return an error.
|
||||
func (lv *ListValue) Contains(val ref.Val) ref.Val {
|
||||
if types.IsUnknownOrError(val) {
|
||||
return val
|
||||
}
|
||||
lv.initValueSet.Do(lv.finalizeValueSet)
|
||||
if lv.valueSet != nil {
|
||||
_, found := lv.valueSet[val]
|
||||
if found {
|
||||
return types.True
|
||||
}
|
||||
// Instead of returning false, ensure that CEL's heterogeneous equality constraint
|
||||
// is satisfied by allowing pair-wise equality behavior to determine the outcome.
|
||||
}
|
||||
var err ref.Val
|
||||
sz := len(lv.Entries)
|
||||
for i := 0; i < sz; i++ {
|
||||
elem := lv.Entries[i]
|
||||
cmp := elem.Equal(val)
|
||||
b, ok := cmp.(types.Bool)
|
||||
if !ok && err == nil {
|
||||
err = types.MaybeNoSuchOverloadErr(cmp)
|
||||
}
|
||||
if b == types.True {
|
||||
return types.True
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
// ConvertToNative is an implementation of the CEL ref.Val method used to adapt between CEL types
|
||||
// and Go-native array-like types.
|
||||
func (lv *ListValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
// Non-list conversion.
|
||||
if typeDesc.Kind() != reflect.Slice &&
|
||||
typeDesc.Kind() != reflect.Array &&
|
||||
typeDesc.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("type conversion error from list to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// If the list is already assignable to the desired type return it.
|
||||
if reflect.TypeOf(lv).AssignableTo(typeDesc) {
|
||||
return lv, nil
|
||||
}
|
||||
|
||||
// List conversion.
|
||||
otherElem := typeDesc.Elem()
|
||||
|
||||
// Allow the element ConvertToNative() function to determine whether conversion is possible.
|
||||
sz := len(lv.Entries)
|
||||
nativeList := reflect.MakeSlice(typeDesc, int(sz), int(sz))
|
||||
for i := 0; i < sz; i++ {
|
||||
elem := lv.Entries[i]
|
||||
nativeElemVal, err := elem.ConvertToNative(otherElem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nativeList.Index(int(i)).Set(reflect.ValueOf(nativeElemVal))
|
||||
}
|
||||
return nativeList.Interface(), nil
|
||||
}
|
||||
|
||||
// ConvertToType converts the ListValue to another CEL type.
|
||||
func (lv *ListValue) ConvertToType(t ref.Type) ref.Val {
|
||||
switch t {
|
||||
case types.ListType:
|
||||
return lv
|
||||
case types.TypeType:
|
||||
return types.ListType
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", ListType, t)
|
||||
}
|
||||
|
||||
// Equal returns true if two lists are of the same size, and the values at each index are also
|
||||
// equal.
|
||||
func (lv *ListValue) Equal(other ref.Val) ref.Val {
|
||||
oArr, isArr := other.(traits.Lister)
|
||||
if !isArr {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
sz := types.Int(len(lv.Entries))
|
||||
if sz != oArr.Size() {
|
||||
return types.False
|
||||
}
|
||||
for i := types.Int(0); i < sz; i++ {
|
||||
cmp := lv.Get(i).Equal(oArr.Get(i))
|
||||
if cmp != types.True {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Get returns the value at the given index.
|
||||
//
|
||||
// If the index is negative or greater than the size of the list, an error is returned.
|
||||
func (lv *ListValue) Get(idx ref.Val) ref.Val {
|
||||
iv, isInt := idx.(types.Int)
|
||||
if !isInt {
|
||||
return types.ValOrErr(idx, "unsupported index: %v", idx)
|
||||
}
|
||||
i := int(iv)
|
||||
if i < 0 || i >= len(lv.Entries) {
|
||||
return types.NewErr("index out of bounds: %v", idx)
|
||||
}
|
||||
return lv.Entries[i].ExprValue()
|
||||
}
|
||||
|
||||
// Iterator produces a traits.Iterator suitable for use in CEL comprehension macros.
|
||||
func (lv *ListValue) Iterator() traits.Iterator {
|
||||
return &baseListIterator{
|
||||
getter: lv.Get,
|
||||
sz: len(lv.Entries),
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the number of elements in the list.
|
||||
func (lv *ListValue) Size() ref.Val {
|
||||
return types.Int(len(lv.Entries))
|
||||
}
|
||||
|
||||
// Type returns the CEL ref.Type for the list.
|
||||
func (lv *ListValue) Type() ref.Type {
|
||||
return types.ListType
|
||||
}
|
||||
|
||||
// Value returns the Go-native value.
|
||||
func (lv *ListValue) Value() interface{} {
|
||||
return lv
|
||||
}
|
||||
|
||||
// finalizeValueSet inspects the ListValue entries in order to make internal optimizations once all list
|
||||
// entries are known.
|
||||
func (lv *ListValue) finalizeValueSet() {
|
||||
valueSet := make(map[ref.Val]struct{})
|
||||
for _, e := range lv.Entries {
|
||||
switch e.value.(type) {
|
||||
case bool, float64, int64, string, uint64, types.Null, PlainTextValue:
|
||||
valueSet[e.ExprValue()] = struct{}{}
|
||||
default:
|
||||
lv.valueSet = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
lv.valueSet = valueSet
|
||||
}
|
||||
|
||||
type baseVal struct{}
|
||||
|
||||
func (*baseVal) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
return nil, fmt.Errorf("unsupported native conversion to: %v", typeDesc)
|
||||
}
|
||||
|
||||
func (*baseVal) ConvertToType(t ref.Type) ref.Val {
|
||||
return types.NewErr("unsupported type conversion to: %v", t)
|
||||
}
|
||||
|
||||
func (*baseVal) Equal(other ref.Val) ref.Val {
|
||||
return types.NewErr("unsupported equality test between instances")
|
||||
}
|
||||
|
||||
func (v *baseVal) Value() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
type baseListIterator struct {
|
||||
*baseVal
|
||||
getter func(idx ref.Val) ref.Val
|
||||
sz int
|
||||
idx int
|
||||
}
|
||||
|
||||
func (it *baseListIterator) HasNext() ref.Val {
|
||||
if it.idx < it.sz {
|
||||
return types.True
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
func (it *baseListIterator) Next() ref.Val {
|
||||
v := it.getter(types.Int(it.idx))
|
||||
it.idx++
|
||||
return v
|
||||
}
|
||||
|
||||
func (it *baseListIterator) Type() ref.Type {
|
||||
return types.IteratorType
|
||||
}
|
||||
|
||||
func celBool(pred bool) ref.Val {
|
||||
if pred {
|
||||
return types.True
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
var unknownType = &DeclType{name: "unknown", MinSerializedSize: 1}
|
Reference in New Issue
Block a user