Pick up dispatcher refactor changes from cel-go

This commit is contained in:
cici37 2022-07-04 16:04:25 -07:00 committed by Cici Huang
parent 35669cc69c
commit ae6243b431
8 changed files with 142 additions and 304 deletions

View File

@ -24,10 +24,6 @@ import (
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/google/cel-go/checker" "github.com/google/cel-go/checker"
"github.com/google/cel-go/checker/decls"
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/protobuf/proto"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library"
@ -104,7 +100,7 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([]
} }
celRules := s.Extensions.XValidations celRules := s.Extensions.XValidations
var propDecls []*expr.Decl var propDecls []cel.EnvOption
var root *celmodel.DeclType var root *celmodel.DeclType
var ok bool var ok bool
baseEnv, err := getBaseEnv() baseEnv, err := getBaseEnv()
@ -132,9 +128,9 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([]
} }
root = rootDecl.MaybeAssignTypeName(scopedTypeName) root = rootDecl.MaybeAssignTypeName(scopedTypeName)
} }
propDecls = append(propDecls, decls.NewVar(ScopedVarName, root.ExprType())) propDecls = append(propDecls, cel.Variable(ScopedVarName, root.CelType()))
propDecls = append(propDecls, decls.NewVar(OldScopedVarName, root.ExprType())) propDecls = append(propDecls, cel.Variable(OldScopedVarName, root.CelType()))
opts = append(opts, cel.Declarations(propDecls...)) opts = append(opts, propDecls...)
env, err := baseEnv.Extend(opts...) env, err := baseEnv.Extend(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -161,7 +157,7 @@ func compileRule(rule apiextensions.ValidationRule, env *cel.Env, perCallLimit u
compilationResult.Error = &Error{ErrorTypeInvalid, "compilation failed: " + issues.String()} compilationResult.Error = &Error{ErrorTypeInvalid, "compilation failed: " + issues.String()}
return return
} }
if !proto.Equal(ast.ResultType(), decls.Bool) { if ast.OutputType() != cel.BoolType {
compilationResult.Error = &Error{ErrorTypeInvalid, "cel expression must evaluate to a bool"} compilationResult.Error = &Error{ErrorTypeInvalid, "cel expression must evaluate to a bool"}
return return
} }

View File

@ -20,13 +20,12 @@ import (
"testing" "testing"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
) )
func TestLibraryCompatibility(t *testing.T) { func TestLibraryCompatibility(t *testing.T) {
functionNames := map[string]struct{}{} functionNames := map[string]struct{}{}
decls := map[cel.Library][]*exprpb.Decl{ decls := map[cel.Library]map[string][]cel.FunctionOpt{
urlsLib: urlLibraryDecls, urlsLib: urlLibraryDecls,
listsLib: listsLibraryDecls, listsLib: listsLibraryDecls,
regexLib: regexLibraryDecls, regexLib: regexLibraryDecls,
@ -34,9 +33,9 @@ func TestLibraryCompatibility(t *testing.T) {
if len(k8sExtensionLibs) != len(decls) { if len(k8sExtensionLibs) != len(decls) {
t.Errorf("Expected the same number of libraries in the ExtensionLibs as are tested for compatibility") t.Errorf("Expected the same number of libraries in the ExtensionLibs as are tested for compatibility")
} }
for _, l := range decls { for _, decl := range decls {
for _, d := range l { for name := range decl {
functionNames[d.GetName()] = struct{}{} functionNames[name] = struct{}{}
} }
} }

View File

@ -20,12 +20,10 @@ import (
"fmt" "fmt"
"github.com/google/cel-go/cel" "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"
"github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits" "github.com/google/cel-go/common/types/traits"
"github.com/google/cel-go/interpreter/functions" "github.com/google/cel-go/interpreter/functions"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
) )
// Lists provides a CEL function library extension of list utility functions. // Lists provides a CEL function library extension of list utility functions.
@ -101,120 +99,64 @@ var listsLib = &lists{}
type lists struct{} type lists struct{}
var paramA = decls.NewTypeParamType("A") 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. // 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>. // But the functions we need to constrain are <list<paramType>>, not just <paramType>.
var summableTypes = map[string]*exprpb.Type{"int": decls.Int, "uint": decls.Uint, "double": decls.Double, "duration": decls.Duration} var summableTypes = map[string]*cel.Type{"int": cel.IntType, "uint": cel.UintType, "double": cel.DoubleType, "duration": cel.DurationType}
var comparableTypes = map[string]*exprpb.Type{"bool": decls.Bool, "int": decls.Int, "uint": decls.Uint, "double": decls.Double, var zeroValuesOfSummableTypes = map[string]ref.Val{
"duration": decls.Duration, "timestamp": decls.Timestamp, "string": decls.String, "bytes": decls.Bytes} "int": types.Int(0),
"uint": types.Uint(0),
"double": types.Double(0.0),
"duration": types.Duration{Duration: 0},
}
var comparableTypes = map[string]*cel.Type{"bool": cel.BoolType, "int": cel.IntType, "uint": cel.UintType, "double": cel.DoubleType,
"duration": cel.DurationType, "timestamp": cel.TimestampType, "string": cel.StringType, "bytes": cel.BytesType}
// WARNING: All library additions or modifications must follow // 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 // https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2876-crd-validation-expression-language#function-library-updates
var listsLibraryDecls = []*exprpb.Decl{ var listsLibraryDecls = map[string][]cel.FunctionOpt{
decls.NewFunction("isSorted", "isSorted": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
templatedOverloads(comparableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { return cel.MemberOverload(fmt.Sprintf("list_%s_is_sorted_bool", name),
return decls.NewInstanceOverload(fmt.Sprintf("list_%s_is_sorted_bool", name), []*cel.Type{cel.ListType(paramType)}, cel.BoolType, cel.UnaryBinding(isSorted))
[]*exprpb.Type{decls.NewListType(paramType)}, }),
decls.Bool) "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 {
decls.NewFunction("sum", return sum(
templatedOverloads(summableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { func() ref.Val {
return decls.NewInstanceOverload(fmt.Sprintf("list_%s_sum_%s", name, name), return zeroValuesOfSummableTypes[name]
[]*exprpb.Type{decls.NewListType(paramType)}, })(list)
paramType) }))
})..., }),
), "max": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
decls.NewFunction("max", return cel.MemberOverload(fmt.Sprintf("list_%s_max_%s", name, name),
templatedOverloads(comparableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { []*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(max()))
return decls.NewInstanceOverload(fmt.Sprintf("list_%s_max_%s", name, name), }),
[]*exprpb.Type{decls.NewListType(paramType)}, "min": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
paramType) return cel.MemberOverload(fmt.Sprintf("list_%s_min_%s", name, name),
})..., []*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(min()))
), }),
decls.NewFunction("min", "indexOf": {
templatedOverloads(comparableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { cel.MemberOverload("list_a_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
return decls.NewInstanceOverload(fmt.Sprintf("list_%s_min_%s", name, name), cel.BinaryBinding(indexOf)),
[]*exprpb.Type{decls.NewListType(paramType)}, },
paramType) "lastIndexOf": {
})..., cel.MemberOverload("list_a_last_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
), cel.BinaryBinding(lastIndexOf)),
decls.NewFunction("indexOf", },
decls.NewInstanceOverload("list_a_index_of_int",
[]*exprpb.Type{decls.NewListType(paramA), paramA},
decls.Int),
),
decls.NewFunction("lastIndexOf",
decls.NewInstanceOverload("list_a_last_index_of_int",
[]*exprpb.Type{decls.NewListType(paramA), paramA},
decls.Int),
),
} }
func (*lists) CompileOptions() []cel.EnvOption { func (*lists) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{ options := []cel.EnvOption{}
cel.Declarations(listsLibraryDecls...), for name, overloads := range listsLibraryDecls {
options = append(options, cel.Function(name, overloads...))
} }
return options
} }
func (*lists) ProgramOptions() []cel.ProgramOption { func (*lists) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{ return []cel.ProgramOption{}
cel.Functions(
&functions.Overload{
Operator: "isSorted",
Unary: isSorted,
},
// if 'sum' is called directly, it is via dynamic dispatch, and we infer the type from the 1st element of the
// list if it has one, otherwise we return int64(0)
&functions.Overload{
Operator: "sum",
Unary: dynSum(),
},
// use overload names for sum so an initial accumulator value can be assigned to each
&functions.Overload{
Operator: "list_int_sum_int",
Unary: sum(func() ref.Val {
return types.Int(0)
}),
},
&functions.Overload{
Operator: "list_uint_sum_uint",
Unary: sum(func() ref.Val {
return types.Uint(0)
}),
},
&functions.Overload{
Operator: "list_double_sum_double",
Unary: sum(func() ref.Val {
return types.Double(0.0)
}),
},
&functions.Overload{
Operator: "list_duration_sum_duration",
Unary: sum(func() ref.Val {
return types.Duration{Duration: 0}
}),
},
&functions.Overload{
Operator: "max",
Unary: max(),
},
&functions.Overload{
Operator: "min",
Unary: min(),
},
// use overload names for indexOf and lastIndexOf to de-conflict with function of same name in strings extension library
&functions.Overload{
Operator: "list_a_index_of_int",
Binary: indexOf,
},
&functions.Overload{
Operator: "list_a_last_index_of_int",
Binary: lastIndexOf,
},
),
}
} }
func isSorted(val ref.Val) ref.Val { func isSorted(val ref.Val) ref.Val {
@ -375,8 +317,8 @@ func lastIndexOf(list ref.Val, item ref.Val) ref.Val {
// templatedOverloads returns overloads for each of the provided types. The template function is called with each type // 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. // name (map key) and type to construct the overloads.
func templatedOverloads(types map[string]*exprpb.Type, template func(name string, t *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload) []*exprpb.Decl_FunctionDecl_Overload { func templatedOverloads(types map[string]*cel.Type, template func(name string, t *cel.Type) cel.FunctionOpt) []cel.FunctionOpt {
overloads := make([]*exprpb.Decl_FunctionDecl_Overload, len(types)) overloads := make([]cel.FunctionOpt, len(types))
i := 0 i := 0
for name, t := range types { for name, t := range types {
overloads[i] = template(name, t) overloads[i] = template(name, t)

View File

@ -20,12 +20,9 @@ import (
"regexp" "regexp"
"github.com/google/cel-go/cel" "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"
"github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter" "github.com/google/cel-go/interpreter"
"github.com/google/cel-go/interpreter/functions"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
) )
// Regex provides a CEL function library extension of regex utility functions. // Regex provides a CEL function library extension of regex utility functions.
@ -55,59 +52,33 @@ var regexLib = &regex{}
type regex struct{} type regex struct{}
var regexLibraryDecls = []*exprpb.Decl{ var regexLibraryDecls = map[string][]cel.FunctionOpt{
"find": {
decls.NewFunction("find", cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
decls.NewInstanceOverload("string_find_string", cel.BinaryBinding(find))},
[]*exprpb.Type{decls.String, decls.String}, "findAll": {
decls.String), cel.MemberOverload("string_find_all_string", []*cel.Type{cel.StringType, cel.StringType},
), cel.ListType(cel.StringType),
decls.NewFunction("findAll", cel.BinaryBinding(func(str, regex ref.Val) ref.Val {
decls.NewInstanceOverload("string_find_all_string", return findAll(str, regex, types.Int(-1))
[]*exprpb.Type{decls.String, decls.String}, })),
decls.NewListType(decls.String)), cel.MemberOverload("string_find_all_string_int",
decls.NewInstanceOverload("string_find_all_string_int", []*cel.Type{cel.StringType, cel.StringType, cel.IntType},
[]*exprpb.Type{decls.String, decls.String, decls.Int}, cel.ListType(cel.StringType),
decls.NewListType(decls.String)), cel.FunctionBinding(findAll)),
), },
} }
func (*regex) CompileOptions() []cel.EnvOption { func (*regex) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{ options := []cel.EnvOption{}
cel.Declarations(regexLibraryDecls...), for name, overloads := range regexLibraryDecls {
options = append(options, cel.Function(name, overloads...))
} }
return options
} }
func (*regex) ProgramOptions() []cel.ProgramOption { func (*regex) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{ return []cel.ProgramOption{}
cel.Functions(
&functions.Overload{
Operator: "find",
Binary: find,
},
&functions.Overload{
Operator: "string_find_string",
Binary: find,
},
&functions.Overload{
Operator: "findAll",
Binary: func(str, regex ref.Val) ref.Val {
return findAll(str, regex, types.Int(-1))
},
Function: findAll,
},
&functions.Overload{
Operator: "string_find_all_string",
Binary: func(str, regex ref.Val) ref.Val {
return findAll(str, regex, types.Int(-1))
},
},
&functions.Overload{
Operator: "string_find_all_string_int",
Function: findAll,
},
),
}
} }
func find(strVal ref.Val, regexVal ref.Val) ref.Val { func find(strVal ref.Val, regexVal ref.Val) ref.Val {

View File

@ -20,12 +20,8 @@ import (
"net/url" "net/url"
"github.com/google/cel-go/cel" "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"
"github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter/functions"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model" "k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model"
) )
@ -111,124 +107,44 @@ var urlsLib = &urls{}
type urls struct{} type urls struct{}
var urlLibraryDecls = []*exprpb.Decl{ var urlLibraryDecls = map[string][]cel.FunctionOpt{
decls.NewFunction("url", "url": {
decls.NewOverload("string_to_url", cel.Overload("string_to_url", []*cel.Type{cel.StringType}, model.URLType,
[]*exprpb.Type{decls.String}, cel.UnaryBinding(stringToUrl))},
model.URLObject), "getScheme": {
), cel.MemberOverload("url_get_scheme", []*cel.Type{model.URLType}, cel.StringType,
decls.NewFunction("getScheme", cel.UnaryBinding(getScheme))},
decls.NewInstanceOverload("url_get_scheme", "getHost": {
[]*exprpb.Type{model.URLObject}, cel.MemberOverload("url_get_host", []*cel.Type{model.URLType}, cel.StringType,
decls.String), cel.UnaryBinding(getHost))},
), "getHostname": {
decls.NewFunction("getHost", cel.MemberOverload("url_get_hostname", []*cel.Type{model.URLType}, cel.StringType,
decls.NewInstanceOverload("url_get_host", cel.UnaryBinding(getHostname))},
[]*exprpb.Type{model.URLObject}, "getPort": {
decls.String), cel.MemberOverload("url_get_port", []*cel.Type{model.URLType}, cel.StringType,
), cel.UnaryBinding(getPort))},
decls.NewFunction("getHostname", "getEscapedPath": {
decls.NewInstanceOverload("url_get_hostname", cel.MemberOverload("url_get_escaped_path", []*cel.Type{model.URLType}, cel.StringType,
[]*exprpb.Type{model.URLObject}, cel.UnaryBinding(getEscapedPath))},
decls.String), "getQuery": {
), cel.MemberOverload("url_get_query", []*cel.Type{model.URLType},
decls.NewFunction("getPort", cel.MapType(cel.StringType, cel.ListType(cel.StringType)),
decls.NewInstanceOverload("url_get_port", cel.UnaryBinding(getQuery))},
[]*exprpb.Type{model.URLObject}, "isURL": {
decls.String), cel.Overload("is_url_string", []*cel.Type{cel.StringType}, cel.BoolType,
), cel.UnaryBinding(isURL))},
decls.NewFunction("getEscapedPath",
decls.NewInstanceOverload("url_get_escaped_path",
[]*exprpb.Type{model.URLObject},
decls.String),
),
decls.NewFunction("getQuery",
decls.NewInstanceOverload("url_get_query",
[]*exprpb.Type{model.URLObject},
decls.NewMapType(decls.String, decls.NewListType(decls.String))),
),
decls.NewFunction("isURL",
decls.NewOverload("is_url_string",
[]*exprpb.Type{decls.String},
decls.Bool),
),
} }
func (*urls) CompileOptions() []cel.EnvOption { func (*urls) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{ options := []cel.EnvOption{}
cel.Declarations(urlLibraryDecls...), for name, overloads := range urlLibraryDecls {
options = append(options, cel.Function(name, overloads...))
} }
return options
} }
func (*urls) ProgramOptions() []cel.ProgramOption { func (*urls) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{ return []cel.ProgramOption{}
cel.Functions(
&functions.Overload{
Operator: "url",
Unary: stringToUrl,
},
&functions.Overload{
Operator: "string_to_url",
Unary: stringToUrl,
},
&functions.Overload{
Operator: "getScheme",
Unary: getScheme,
},
&functions.Overload{
Operator: "url_get_scheme",
Unary: getScheme,
},
&functions.Overload{
Operator: "getHost",
Unary: getHost,
},
&functions.Overload{
Operator: "url_get_host",
Unary: getHost,
},
&functions.Overload{
Operator: "getHostname",
Unary: getHostname,
},
&functions.Overload{
Operator: "url_get_hostname",
Unary: getHostname,
},
&functions.Overload{
Operator: "getPort",
Unary: getPort,
},
&functions.Overload{
Operator: "url_get_port",
Unary: getPort,
},
&functions.Overload{
Operator: "getEscapedPath",
Unary: getEscapedPath,
},
&functions.Overload{
Operator: "url_get_escaped_path",
Unary: getEscapedPath,
},
&functions.Overload{
Operator: "getQuery",
Unary: getQuery,
},
&functions.Overload{
Operator: "url_get_query",
Unary: getQuery,
},
&functions.Overload{
Operator: "isURL",
Unary: isURL,
},
&functions.Overload{
Operator: "is_url_string",
Unary: isURL,
},
),
}
} }
func stringToUrl(arg ref.Val) ref.Val { func stringToUrl(arg ref.Val) ref.Val {

View File

@ -15,6 +15,7 @@
package model package model
import ( import (
"github.com/google/cel-go/cel"
"time" "time"
"github.com/google/cel-go/checker/decls" "github.com/google/cel-go/checker/decls"
@ -72,7 +73,7 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
// To validate requirements on both the int and string representation: // To validate requirements on both the int and string representation:
// `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5 // `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5
// //
dyn := newSimpleType("dyn", decls.Dyn, nil) dyn := newSimpleType("dyn", decls.Dyn, cel.DynType, nil)
// handle x-kubernetes-int-or-string by returning the max length of the largest possible string // handle x-kubernetes-int-or-string by returning the max length of the largest possible string
dyn.MaxElements = maxRequestSizeBytes - 2 dyn.MaxElements = maxRequestSizeBytes - 2
return dyn return dyn
@ -149,7 +150,7 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
if s.ValueValidation != nil { if s.ValueValidation != nil {
switch s.ValueValidation.Format { switch s.ValueValidation.Format {
case "byte": case "byte":
byteWithMaxLength := newSimpleType("bytes", decls.Bytes, types.Bytes([]byte{})) byteWithMaxLength := newSimpleType("bytes", decls.Bytes, cel.BytesType, types.Bytes([]byte{}))
if s.ValueValidation.MaxLength != nil { if s.ValueValidation.MaxLength != nil {
byteWithMaxLength.MaxElements = zeroIfNegative(*s.ValueValidation.MaxLength) byteWithMaxLength.MaxElements = zeroIfNegative(*s.ValueValidation.MaxLength)
} else { } else {
@ -157,16 +158,16 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
} }
return byteWithMaxLength return byteWithMaxLength
case "duration": case "duration":
durationWithMaxLength := newSimpleType("duration", decls.Duration, types.Duration{Duration: time.Duration(0)}) durationWithMaxLength := newSimpleType("duration", decls.Duration, cel.DurationType, types.Duration{Duration: time.Duration(0)})
durationWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s) durationWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
return durationWithMaxLength return durationWithMaxLength
case "date", "date-time": case "date", "date-time":
timestampWithMaxLength := newSimpleType("timestamp", decls.Timestamp, types.Timestamp{Time: time.Time{}}) timestampWithMaxLength := newSimpleType("timestamp", decls.Timestamp, cel.TimestampType, types.Timestamp{Time: time.Time{}})
timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s) timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
return timestampWithMaxLength return timestampWithMaxLength
} }
} }
strWithMaxLength := newSimpleType("string", decls.String, types.String("")) strWithMaxLength := newSimpleType("string", decls.String, cel.StringType, types.String(""))
if s.ValueValidation != nil && s.ValueValidation.MaxLength != nil { if s.ValueValidation != nil && s.ValueValidation.MaxLength != nil {
// multiply the user-provided max length by 4 in the case of an otherwise-untyped string // multiply the user-provided max length by 4 in the case of an otherwise-untyped string
// we do this because the OpenAPIv3 spec indicates that maxLength is specified in runes/code points, // we do this because the OpenAPIv3 spec indicates that maxLength is specified in runes/code points,

View File

@ -42,6 +42,7 @@ func NewListType(elem *DeclType, maxItems int64) *DeclType {
ElemType: elem, ElemType: elem,
MaxElements: maxItems, MaxElements: maxItems,
exprType: decls.NewListType(elem.ExprType()), exprType: decls.NewListType(elem.ExprType()),
celType: cel.ListType(elem.CelType()),
defaultValue: NewListValue(), defaultValue: NewListValue(),
} }
} }
@ -54,6 +55,7 @@ func NewMapType(key, elem *DeclType, maxProperties int64) *DeclType {
ElemType: elem, ElemType: elem,
MaxElements: maxProperties, MaxElements: maxProperties,
exprType: decls.NewMapType(key.ExprType(), elem.ExprType()), exprType: decls.NewMapType(key.ExprType(), elem.ExprType()),
celType: cel.MapType(key.CelType(), elem.CelType()),
defaultValue: NewMapValue(), defaultValue: NewMapValue(),
} }
} }
@ -64,6 +66,7 @@ func NewObjectType(name string, fields map[string]*DeclField) *DeclType {
name: name, name: name,
Fields: fields, Fields: fields,
exprType: decls.NewObjectType(name), exprType: decls.NewObjectType(name),
celType: cel.ObjectType(name),
traitMask: traits.FieldTesterType | traits.IndexerType, traitMask: traits.FieldTesterType | traits.IndexerType,
} }
t.defaultValue = NewObjectValue(t) t.defaultValue = NewObjectValue(t)
@ -75,6 +78,7 @@ func NewObjectTypeRef(name string) *DeclType {
t := &DeclType{ t := &DeclType{
name: name, name: name,
exprType: decls.NewObjectType(name), exprType: decls.NewObjectType(name),
celType: cel.ObjectType(name),
traitMask: traits.FieldTesterType | traits.IndexerType, traitMask: traits.FieldTesterType | traits.IndexerType,
} }
return t return t
@ -89,13 +93,15 @@ func NewTypeParam(name string) *DeclType {
name: name, name: name,
TypeParam: true, TypeParam: true,
exprType: decls.NewTypeParamType(name), exprType: decls.NewTypeParamType(name),
celType: cel.TypeParamType(name),
} }
} }
func newSimpleType(name string, exprType *exprpb.Type, zeroVal ref.Val) *DeclType { func newSimpleType(name string, exprType *exprpb.Type, celType *cel.Type, zeroVal ref.Val) *DeclType {
return &DeclType{ return &DeclType{
name: name, name: name,
exprType: exprType, exprType: exprType,
celType: celType,
defaultValue: zeroVal, defaultValue: zeroVal,
} }
} }
@ -113,6 +119,7 @@ type DeclType struct {
MaxElements int64 MaxElements int64
exprType *exprpb.Type exprType *exprpb.Type
celType *cel.Type
traitMask int traitMask int
defaultValue ref.Val defaultValue ref.Val
} }
@ -158,6 +165,7 @@ func (t *DeclType) MaybeAssignTypeName(name string) *DeclType {
TypeParam: t.TypeParam, TypeParam: t.TypeParam,
Metadata: t.Metadata, Metadata: t.Metadata,
exprType: decls.NewObjectType(name), exprType: decls.NewObjectType(name),
celType: cel.ObjectType(name),
traitMask: t.traitMask, traitMask: t.traitMask,
defaultValue: t.defaultValue, defaultValue: t.defaultValue,
} }
@ -186,6 +194,11 @@ func (t *DeclType) ExprType() *exprpb.Type {
return t.exprType return t.exprType
} }
// 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. // FindField returns the DeclField with the given name if present.
func (t *DeclType) FindField(name string) (*DeclField, bool) { func (t *DeclType) FindField(name string) (*DeclField, bool) {
f, found := t.Fields[name] f, found := t.Fields[name]
@ -377,9 +390,7 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
return []cel.EnvOption{ return []cel.EnvOption{
cel.CustomTypeProvider(rtWithTypes), cel.CustomTypeProvider(rtWithTypes),
cel.CustomTypeAdapter(rtWithTypes), cel.CustomTypeAdapter(rtWithTypes),
cel.Declarations( cel.Variable("rule", rt.ruleSchemaDeclTypes.root.CelType()),
decls.NewVar("rule", rt.ruleSchemaDeclTypes.root.ExprType()),
),
}, nil }, nil
} }
@ -517,42 +528,42 @@ type schemaTypeProvider struct {
var ( var (
// AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the // AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the
// types supported. // types supported.
AnyType = newSimpleType("any", decls.Any, nil) AnyType = newSimpleType("any", decls.Any, cel.AnyType, nil)
// BoolType is equivalent to the CEL 'bool' type. // BoolType is equivalent to the CEL 'bool' type.
BoolType = newSimpleType("bool", decls.Bool, types.False) BoolType = newSimpleType("bool", decls.Bool, cel.BoolType, types.False)
// BytesType is equivalent to the CEL 'bytes' type. // BytesType is equivalent to the CEL 'bytes' type.
BytesType = newSimpleType("bytes", decls.Bytes, types.Bytes([]byte{})) BytesType = newSimpleType("bytes", decls.Bytes, cel.BytesType, types.Bytes([]byte{}))
// DoubleType is equivalent to the CEL 'double' type which is a 64-bit floating point value. // DoubleType is equivalent to the CEL 'double' type which is a 64-bit floating point value.
DoubleType = newSimpleType("double", decls.Double, types.Double(0)) DoubleType = newSimpleType("double", decls.Double, cel.DoubleType, types.Double(0))
// DurationType is equivalent to the CEL 'duration' type. // DurationType is equivalent to the CEL 'duration' type.
DurationType = newSimpleType("duration", decls.Duration, types.Duration{Duration: time.Duration(0)}) DurationType = newSimpleType("duration", decls.Duration, cel.DurationType, types.Duration{Duration: time.Duration(0)})
// DateType is equivalent to the CEL 'date' type. // DateType is equivalent to the CEL 'date' type.
DateType = newSimpleType("date", decls.Timestamp, types.Timestamp{Time: time.Time{}}) DateType = newSimpleType("date", decls.Timestamp, cel.TimestampType, types.Timestamp{Time: time.Time{}})
// DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be // DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be
// determined at runtime rather than compile time. // determined at runtime rather than compile time.
DynType = newSimpleType("dyn", decls.Dyn, nil) DynType = newSimpleType("dyn", decls.Dyn, cel.DynType, nil)
// IntType is equivalent to the CEL 'int' type which is a 64-bit signed int. // IntType is equivalent to the CEL 'int' type which is a 64-bit signed int.
IntType = newSimpleType("int", decls.Int, types.IntZero) IntType = newSimpleType("int", decls.Int, cel.IntType, types.IntZero)
// NullType is equivalent to the CEL 'null_type'. // NullType is equivalent to the CEL 'null_type'.
NullType = newSimpleType("null_type", decls.Null, types.NullValue) NullType = newSimpleType("null_type", decls.Null, cel.NullType, types.NullValue)
// StringType is equivalent to the CEL 'string' type which is expected to be a UTF-8 string. // 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 values may either be string literals or expression strings.
StringType = newSimpleType("string", decls.String, types.String("")) StringType = newSimpleType("string", decls.String, cel.StringType, types.String(""))
// TimestampType corresponds to the well-known protobuf.Timestamp type supported within CEL. // TimestampType corresponds to the well-known protobuf.Timestamp type supported within CEL.
TimestampType = newSimpleType("timestamp", decls.Timestamp, types.Timestamp{Time: time.Time{}}) TimestampType = newSimpleType("timestamp", decls.Timestamp, cel.TimestampType, types.Timestamp{Time: time.Time{}})
// UintType is equivalent to the CEL 'uint' type. // UintType is equivalent to the CEL 'uint' type.
UintType = newSimpleType("uint", decls.Uint, types.Uint(0)) UintType = newSimpleType("uint", decls.Uint, cel.UintType, types.Uint(0))
// ListType is equivalent to the CEL 'list' type. // ListType is equivalent to the CEL 'list' type.
ListType = NewListType(AnyType, noMaxLength) ListType = NewListType(AnyType, noMaxLength)

View File

@ -16,6 +16,7 @@ package model
import ( import (
"fmt" "fmt"
"github.com/google/cel-go/cel"
"net/url" "net/url"
"reflect" "reflect"
@ -32,6 +33,7 @@ type URL struct {
var ( var (
URLObject = decls.NewObjectType("kubernetes.URL") URLObject = decls.NewObjectType("kubernetes.URL")
typeValue = types.NewTypeValue("kubernetes.URL") typeValue = types.NewTypeValue("kubernetes.URL")
URLType = cel.ObjectType("kubernetes.URL")
) )
// ConvertToNative implements ref.Val.ConvertToNative. // ConvertToNative implements ref.Val.ConvertToNative.