func (a *TokenAuthenticator) AuthenticateToken(ctx context.Context, value string) (*authenticator.Response, bool, error) {
	user, ok := a.tokens[value]
	if !ok {
		return nil, false, nil
	}
	return &authenticator.Response{User: user}, true, nil
}


// New returns a token authenticator that validates credentials using a chain of authenticator.Token objects.
// The entire chain is tried until one succeeds. If all fail, an aggregate error is returned.
func New(authTokenHandlers ...authenticator.Token) authenticator.Token {
	if len(authTokenHandlers) == 1 {
		return authTokenHandlers[0]
	}
	return &unionAuthTokenHandler{Handlers: authTokenHandlers, FailOnError: false}
}


// NewFailOnError returns a token authenticator that validates credentials using a chain of authenticator.Token objects.
// The first error short-circuits the chain.
func NewFailOnError(authTokenHandlers ...authenticator.Token) authenticator.Token {
	if len(authTokenHandlers) == 1 {
		return authTokenHandlers[0]
	}
	return &unionAuthTokenHandler{Handlers: authTokenHandlers, FailOnError: true}
}


// AuthenticateToken authenticates the token using a chain of authenticator.Token objects.
func (authHandler *unionAuthTokenHandler) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
	var errlist []error
	for _, currAuthRequestHandler := range authHandler.Handlers {
		info, ok, err := currAuthRequestHandler.AuthenticateToken(ctx, token)
		if err != nil {
			if authHandler.FailOnError {
				return info, ok, err
			}
			errlist = append(errlist, err)
			continue
		}

		if ok {
			return info, ok, err
		}
	}

	return nil, false, utilerrors.NewAggregate(errlist)
}


func (i *DefaultInfo) GetName() string

func (i *DefaultInfo) GetName() string {
	return i.Name
}


func (i *DefaultInfo) GetUID() string

func (i *DefaultInfo) GetUID() string {
	return i.UID
}


func (i *DefaultInfo) GetGroups() []string {
	return i.Groups
}


func (i *DefaultInfo) GetExtra() map[string][]string {
	return i.Extra
}


func (f AuthorizerFunc) Authorize(ctx context.Context, a Attributes) (Decision, string, error) {
	return f(ctx, a)
}


func (a AttributesRecord) GetUser() user.Info {
	return a.User
}


func (a AttributesRecord) GetVerb() string {
	return a.Verb
}


func (a AttributesRecord) IsReadOnly() bool {
	return a.Verb == "get" || a.Verb == "list" || a.Verb == "watch"
}


func (a AttributesRecord) GetNamespace() string {
	return a.Namespace
}


func (a AttributesRecord) GetResource() string {
	return a.Resource
}


func (a AttributesRecord) GetSubresource() string {
	return a.Subresource
}


func (a AttributesRecord) GetName() string {
	return a.Name
}


func (a AttributesRecord) GetAPIGroup() string {
	return a.APIGroup
}


func (a AttributesRecord) GetAPIVersion() string {
	return a.APIVersion
}


func (a AttributesRecord) IsResourceRequest() bool {
	return a.ResourceRequest
}


func (a AttributesRecord) GetPath() string {
	return a.Path
}


func (i *DefaultResourceRuleInfo) GetVerbs() []string {
	return i.Verbs
}


func (i *DefaultResourceRuleInfo) GetAPIGroups() []string {
	return i.APIGroups
}


func (i *DefaultResourceRuleInfo) GetResources() []string {
	return i.Resources
}


func (i *DefaultResourceRuleInfo) GetResourceNames() []string {
	return i.ResourceNames
}


func (i *DefaultNonResourceRuleInfo) GetVerbs() []string {
	return i.Verbs
}


func (i *DefaultNonResourceRuleInfo) GetNonResourceURLs() []string {
	return i.NonResourceURLs
}


func (alwaysAllowAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
	return authorizer.DecisionAllow, "", nil
}


func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
	return []authorizer.ResourceRuleInfo{
			&authorizer.DefaultResourceRuleInfo{
				Verbs:     []string{"*"},
				APIGroups: []string{"*"},
				Resources: []string{"*"},
			},
		}, []authorizer.NonResourceRuleInfo{
			&authorizer.DefaultNonResourceRuleInfo{
				Verbs:           []string{"*"},
				NonResourceURLs: []string{"*"},
			},
		}, false, nil
}


func NewAlwaysAllowAuthorizer() *alwaysAllowAuthorizer {
	return new(alwaysAllowAuthorizer)
}


func (alwaysDenyAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
	return authorizer.DecisionNoOpinion, "Everything is forbidden.", nil
}


func (alwaysDenyAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
	return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, nil
}


func NewAlwaysDenyAuthorizer() *alwaysDenyAuthorizer {
	return new(alwaysDenyAuthorizer)
}


func (r *privilegedGroupAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
	if attr.GetUser() == nil {
		return authorizer.DecisionNoOpinion, "Error", errors.New("no user on request.")
	}
	for _, attr_group := range attr.GetUser().GetGroups() {
		for _, priv_group := range r.groups {
			if priv_group == attr_group {
				return authorizer.DecisionAllow, "", nil
			}
		}
	}
	return authorizer.DecisionNoOpinion, "", nil
}


// NewPrivilegedGroups is for use in loopback scenarios
func NewPrivilegedGroups(groups ...string) *privilegedGroupAuthorizer {
	return &privilegedGroupAuthorizer{
		groups: groups,
	}
}


func (c DelegatingAuthorizerConfig) New() (authorizer.Authorizer, error) {
	if c.WebhookRetryBackoff == nil {
		return nil, errors.New("retry backoff parameters for delegating authorization webhook has not been specified")
	}

	return webhook.NewFromInterface(
		c.SubjectAccessReviewClient,
		c.AllowCacheTTL,
		c.DenyCacheTTL,
		*c.WebhookRetryBackoff,
		webhook.AuthorizerMetrics{
			RecordRequestTotal:   RecordRequestTotal,
			RecordRequestLatency: RecordRequestLatency,
		},
	)
}


func init()

// init registers all metrics
func init() {
	for _, metric := range metrics {
		legacyregistry.MustRegister(metric)
	}
}


// RecordRequestTotal increments the total number of requests for the delegated authorization.
func RecordRequestTotal(ctx context.Context, code string) {
	requestTotal.WithContext(ctx).WithLabelValues(code).Add(1)
}


// RecordRequestLatency measures request latency in seconds for the delegated authorization. Broken down by status code.
func RecordRequestLatency(ctx context.Context, code string, latency float64) {
	requestLatency.WithContext(ctx).WithLabelValues(code).Observe(latency)
}


// NewAuthorizer returns an authorizer which accepts a given set of paths.
// Each path is either a fully matching path or it ends in * in case a prefix match is done. A leading / is optional.
func NewAuthorizer(alwaysAllowPaths []string) (authorizer.Authorizer, error) {
	var prefixes []string
	paths := sets.NewString()
	for _, p := range alwaysAllowPaths {
		p = strings.TrimPrefix(p, "/")
		if len(p) == 0 {
			// matches "/"
			paths.Insert(p)
			continue
		}
		if strings.ContainsRune(p[:len(p)-1], '*') {
			return nil, fmt.Errorf("only trailing * allowed in %q", p)
		}
		if strings.HasSuffix(p, "*") {
			prefixes = append(prefixes, p[:len(p)-1])
		} else {
			paths.Insert(p)
		}
	}

	return authorizer.AuthorizerFunc(func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
		if a.IsResourceRequest() {
			return authorizer.DecisionNoOpinion, "", nil
		}

		pth := strings.TrimPrefix(a.GetPath(), "/")
		if paths.Has(pth) {
			return authorizer.DecisionAllow, "", nil
		}

		for _, prefix := range prefixes {
			if strings.HasPrefix(pth, prefix) {
				return authorizer.DecisionAllow, "", nil
			}
		}

		return authorizer.DecisionNoOpinion, "", nil
	}), nil
}


// New returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer {
	return unionAuthzHandler(authorizationHandlers)
}


// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful
func (authzHandler unionAuthzHandler) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
	var (
		errlist    []error
		reasonlist []string
	)

	for _, currAuthzHandler := range authzHandler {
		decision, reason, err := currAuthzHandler.Authorize(ctx, a)

		if err != nil {
			errlist = append(errlist, err)
		}
		if len(reason) != 0 {
			reasonlist = append(reasonlist, reason)
		}
		switch decision {
		case authorizer.DecisionAllow, authorizer.DecisionDeny:
			return decision, reason, err
		case authorizer.DecisionNoOpinion:
			// continue to the next authorizer
		}
	}

	return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
}


// NewRuleResolvers returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
func NewRuleResolvers(authorizationHandlers ...authorizer.RuleResolver) authorizer.RuleResolver {
	return unionAuthzRulesHandler(authorizationHandlers)
}


// RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful
func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
	var (
		errList              []error
		resourceRulesList    []authorizer.ResourceRuleInfo
		nonResourceRulesList []authorizer.NonResourceRuleInfo
	)
	incompleteStatus := false

	for _, currAuthzHandler := range authzHandler {
		resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace)

		if incomplete {
			incompleteStatus = true
		}
		if err != nil {
			errList = append(errList, err)
		}
		if len(resourceRules) > 0 {
			resourceRulesList = append(resourceRulesList, resourceRules...)
		}
		if len(nonResourceRules) > 0 {
			nonResourceRulesList = append(nonResourceRulesList, nonResourceRules...)
		}
	}

	return resourceRulesList, nonResourceRulesList, incompleteStatus, utilerrors.NewAggregate(errList)
}


// CompositeKeyFor directly returns the value of the single key  to
// use as a composite key.
func (ks *singleKeyStrategy) CompositeKeyFor(obj map[string]interface{}) (interface{}, bool) {
	v, ok := obj[ks.key]
	if !ok {
		return nil, false
	}

	switch v.(type) {
	case bool, float64, int64, string:
		return v, true
	default:
		return nil, false // non-scalar
	}
}


// CompositeKeyFor returns a composite key computed from the values of all
// keys.
func (ks *multiKeyStrategy) CompositeKeyFor(obj map[string]interface{}) (interface{}, bool) {
	const keyDelimiter = "\x00" // 0 byte should never appear in the composite key except as delimiter

	var delimited strings.Builder
	for _, key := range ks.sts.XListMapKeys() {
		v, ok := obj[key]
		if !ok {
			return nil, false
		}

		switch v.(type) {
		case bool:
			fmt.Fprintf(&delimited, keyDelimiter+"%t", v)
		case float64:
			fmt.Fprintf(&delimited, keyDelimiter+"%f", v)
		case int64:
			fmt.Fprintf(&delimited, keyDelimiter+"%d", v)
		case string:
			fmt.Fprintf(&delimited, keyDelimiter+"%q", v)
		default:
			return nil, false // values must be scalars
		}
	}
	return delimited.String(), true
}


func (emptyMapList) Get(interface{}) interface{} {
	return nil
}


func (a *mapListImpl) Get(obj interface{}) interface{} {
	mobj, ok := obj.(map[string]interface{})
	if !ok {
		return nil
	}

	key, ok := a.ks.CompositeKeyFor(mobj)
	if !ok {
		return nil
	}
	if match, ok := a.keyedItems[key]; ok {
		return match
	}
	// keep keying items until we either find a match or run out of unkeyed items
	for len(a.unkeyedItems) > 0 {
		// dequeue an unkeyed item
		item := a.unkeyedItems[0]
		a.unkeyedItems = a.unkeyedItems[1:]

		// key the item
		mitem, ok := item.(map[string]interface{})
		if !ok {
			continue
		}
		itemKey, ok := a.ks.CompositeKeyFor(mitem)
		if !ok {
			continue
		}
		if _, exists := a.keyedItems[itemKey]; !exists {
			a.keyedItems[itemKey] = mitem
		}

		// if it matches, short-circuit
		if itemKey == key {
			return mitem
		}
	}

	return nil
}


func makeKeyStrategy(sts Schema) keyStrategy {
	listMapKeys := sts.XListMapKeys()
	if len(listMapKeys) == 1 {
		key := listMapKeys[0]
		return &singleKeyStrategy{
			key: key,
		}
	}

	return &multiKeyStrategy{
		sts: sts,
	}
}


// MakeMapList returns a queryable interface over the provided x-kubernetes-list-type=map
// keyedItems. If the provided schema is _not_ an array with x-kubernetes-list-type=map, returns an
// empty mapList.
func MakeMapList(sts Schema, items []interface{}) (rv MapList) {
	if sts.Type() != "array" || sts.XListType() != "map" || len(sts.XListMapKeys()) == 0 || len(items) == 0 {
		return emptyMapList{}
	}
	ks := makeKeyStrategy(sts)
	return &mapListImpl{
		sts:          sts,
		ks:           ks,
		keyedItems:   map[interface{}]interface{}{},
		unkeyedItems: items,
	}
}


// SchemaDeclType converts the structural schema to a CEL declaration, or returns nil if the
// structural schema should not be exposed in CEL expressions.
// Set isResourceRoot to true for the root of a custom resource or embedded resource.
//
// Schemas with XPreserveUnknownFields not exposed unless they are objects. Array and "maps" schemas
// are not exposed if their items or additionalProperties schemas are not exposed. Object Properties are not exposed
// if their schema is not exposed.
//
// The CEL declaration for objects with XPreserveUnknownFields does not expose unknown fields.
func SchemaDeclType(s Schema, isResourceRoot bool) *apiservercel.DeclType {
	if s == nil {
		return nil
	}
	if s.IsXIntOrString() {
		// schemas using XIntOrString are not required to have a type.

		// intOrStringType represents the x-kubernetes-int-or-string union type in CEL expressions.
		// In CEL, the type is represented as dynamic value, which can be thought of as a union type of all types.
		// All type checking for XIntOrString is deferred to runtime, so all access to values of this type must
		// be guarded with a type check, e.g.:
		//
		// To require that the string representation be a percentage:
		//  `type(intOrStringField) == string && intOrStringField.matches(r'(\d+(\.\d+)?%)')`
		// To validate requirements on both the int and string representation:
		//  `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5
		//
		dyn := apiservercel.NewSimpleTypeWithMinSize("dyn", cel.DynType, nil, 1) // smallest value for a serialized x-kubernetes-int-or-string is 0
		// handle x-kubernetes-int-or-string by returning the max length/min serialized size of the largest possible string
		dyn.MaxElements = maxRequestSizeBytes - 2
		return dyn
	}

	// We ignore XPreserveUnknownFields since we don't support validation rules on
	// data that we don't have schema information for.

	if isResourceRoot {
		// 'apiVersion', 'kind', 'metadata.name' and 'metadata.generateName' are always accessible to validator rules
		// at the root of resources, even if not specified in the schema.
		// This includes the root of a custom resource and the root of XEmbeddedResource objects.
		s = s.WithTypeAndObjectMeta()
	}

	switch s.Type() {
	case "array":
		if s.Items() != nil {
			itemsType := SchemaDeclType(s.Items(), s.Items().IsXEmbeddedResource())
			if itemsType == nil {
				return nil
			}
			var maxItems int64
			if s.MaxItems() != nil {
				maxItems = zeroIfNegative(*s.MaxItems())
			} else {
				maxItems = estimateMaxArrayItemsFromMinSize(itemsType.MinSerializedSize)
			}
			return apiservercel.NewListType(itemsType, maxItems)
		}
		return nil
	case "object":
		if s.AdditionalProperties() != nil && s.AdditionalProperties().Schema() != nil {
			propsType := SchemaDeclType(s.AdditionalProperties().Schema(), s.AdditionalProperties().Schema().IsXEmbeddedResource())
			if propsType != nil {
				var maxProperties int64
				if s.MaxProperties() != nil {
					maxProperties = zeroIfNegative(*s.MaxProperties())
				} else {
					maxProperties = estimateMaxAdditionalPropertiesFromMinSize(propsType.MinSerializedSize)
				}
				return apiservercel.NewMapType(apiservercel.StringType, propsType, maxProperties)
			}
			return nil
		}
		fields := make(map[string]*apiservercel.DeclField, len(s.Properties()))

		required := map[string]bool{}
		if s.Required() != nil {
			for _, f := range s.Required() {
				required[f] = true
			}
		}
		// an object will always be serialized at least as {}, so account for that
		minSerializedSize := int64(2)
		for name, prop := range s.Properties() {
			var enumValues []interface{}
			if prop.Enum() != nil {
				for _, e := range prop.Enum() {
					enumValues = append(enumValues, e)
				}
			}
			if fieldType := SchemaDeclType(prop, prop.IsXEmbeddedResource()); fieldType != nil {
				if propName, ok := apiservercel.Escape(name); ok {
					fields[propName] = apiservercel.NewDeclField(propName, fieldType, required[name], enumValues, prop.Default())
				}
				// the min serialized size for an object is 2 (for {}) plus the min size of all its required
				// properties
				// only include required properties without a default value; default values are filled in
				// server-side
				if required[name] && prop.Default() == nil {
					minSerializedSize += int64(len(name)) + fieldType.MinSerializedSize + 4
				}
			}
		}
		objType := apiservercel.NewObjectType("object", fields)
		objType.MinSerializedSize = minSerializedSize
		return objType
	case "string":
		switch s.Format() {
		case "byte":
			byteWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("bytes", cel.BytesType, types.Bytes([]byte{}), apiservercel.MinStringSize)
			if s.MaxLength() != nil {
				byteWithMaxLength.MaxElements = zeroIfNegative(*s.MaxLength())
			} else {
				byteWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
			}
			return byteWithMaxLength
		case "duration":
			durationWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}, int64(apiservercel.MinDurationSizeJSON))
			durationWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
			return durationWithMaxLength
		case "date":
			timestampWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, int64(apiservercel.JSONDateSize))
			timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
			return timestampWithMaxLength
		case "date-time":
			timestampWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, int64(apiservercel.MinDatetimeSizeJSON))
			timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
			return timestampWithMaxLength
		}

		strWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("string", cel.StringType, types.String(""), apiservercel.MinStringSize)
		if s.MaxLength() != nil {
			// 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,
			// but we need to reason about length for things like request size, so we use bytes in this code (and an individual
			// unicode code point can be up to 4 bytes long)
			strWithMaxLength.MaxElements = zeroIfNegative(*s.MaxLength()) * 4
		} else {
			strWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
		}
		return strWithMaxLength
	case "boolean":
		return apiservercel.BoolType
	case "number":
		return apiservercel.DoubleType
	case "integer":
		return apiservercel.IntType
	}
	return nil
}


func zeroIfNegative(v int64) int64

func zeroIfNegative(v int64) int64 {
	if v < 0 {
		return 0
	}
	return v
}


// WithTypeAndObjectMeta ensures the kind, apiVersion and
// metadata.name and metadata.generateName properties are specified, making a shallow copy of the provided schema if needed.
func WithTypeAndObjectMeta(s *spec.Schema) *spec.Schema {
	if s.Properties != nil &&
		s.Properties["kind"].Type.Contains("string") &&
		s.Properties["apiVersion"].Type.Contains("string") &&
		s.Properties["metadata"].Type.Contains("object") &&
		s.Properties["metadata"].Properties != nil &&
		s.Properties["metadata"].Properties["name"].Type.Contains("string") &&
		s.Properties["metadata"].Properties["generateName"].Type.Contains("string") {
		return s
	}
	result := *s
	props := make(map[string]spec.Schema, len(s.Properties))
	for k, prop := range s.Properties {
		props[k] = prop
	}
	stringType := spec.StringProperty()
	props["kind"] = *stringType
	props["apiVersion"] = *stringType
	props["metadata"] = spec.Schema{
		SchemaProps: spec.SchemaProps{
			Type: []string{"object"},
			Properties: map[string]spec.Schema{
				"name":         *stringType,
				"generateName": *stringType,
			},
		},
	}
	result.Properties = props

	return &result
}


// estimateMaxStringLengthPerRequest estimates the maximum string length (in characters)
// of a string compatible with the format requirements in the provided schema.
// must only be called on schemas of type "string" or x-kubernetes-int-or-string: true
func estimateMaxStringLengthPerRequest(s Schema) int64 {
	if s.IsXIntOrString() {
		return maxRequestSizeBytes - 2
	}
	switch s.Format() {
	case "duration":
		return apiservercel.MaxDurationSizeJSON
	case "date":
		return apiservercel.JSONDateSize
	case "date-time":
		return apiservercel.MaxDatetimeSizeJSON
	default:
		// subtract 2 to account for ""
		return maxRequestSizeBytes - 2
	}
}


// estimateMaxArrayItemsPerRequest estimates the maximum number of array items with
// the provided minimum serialized size that can fit into a single request.
func estimateMaxArrayItemsFromMinSize(minSize int64) int64 {
	// subtract 2 to account for [ and ]
	return (maxRequestSizeBytes - 2) / (minSize + 1)
}


// estimateMaxAdditionalPropertiesPerRequest estimates the maximum number of additional properties
// with the provided minimum serialized size that can fit into a single request.
func estimateMaxAdditionalPropertiesFromMinSize(minSize int64) int64 {
	// 2 bytes for key + "" + colon + comma + smallest possible value, realistically the actual keys
	// will all vary in length
	keyValuePairSize := minSize + 6
	// subtract 2 to account for { and }
	return (maxRequestSizeBytes - 2) / keyValuePairSize
}


// UnstructuredToVal converts a Kubernetes unstructured data element to a CEL Val.
// The root schema of custom resource schema is expected contain type meta and object meta schemas.
// If Embedded resources do not contain type meta and object meta schemas, they will be added automatically.
func UnstructuredToVal(unstructured interface{}, schema Schema) ref.Val {
	if unstructured == nil {
		if schema.Nullable() {
			return types.NullValue
		}
		return types.NewErr("invalid data, got null for schema with nullable=false")
	}
	if schema.IsXIntOrString() {
		switch v := unstructured.(type) {
		case string:
			return types.String(v)
		case int:
			return types.Int(v)
		case int32:
			return types.Int(v)
		case int64:
			return types.Int(v)
		}
		return types.NewErr("invalid data, expected XIntOrString value to be either a string or integer")
	}
	if schema.Type() == "object" {
		m, ok := unstructured.(map[string]interface{})
		if !ok {
			return types.NewErr("invalid data, expected a map for the provided schema with type=object")
		}
		if schema.IsXEmbeddedResource() || schema.Properties() != nil {
			if schema.IsXEmbeddedResource() {
				schema = schema.WithTypeAndObjectMeta()
			}
			return &unstructuredMap{
				value:  m,
				schema: schema,
				propSchema: func(key string) (Schema, bool) {
					if schema, ok := schema.Properties()[key]; ok {
						return schema, true
					}
					return nil, false
				},
			}
		}
		if schema.AdditionalProperties() != nil && schema.AdditionalProperties().Schema() != nil {
			return &unstructuredMap{
				value:  m,
				schema: schema,
				propSchema: func(key string) (Schema, bool) {
					return schema.AdditionalProperties().Schema(), true
				},
			}
		}
		// A object with x-kubernetes-preserve-unknown-fields but no properties or additionalProperties is treated
		// as an empty object.
		if schema.IsXPreserveUnknownFields() {
			return &unstructuredMap{
				value:  m,
				schema: schema,
				propSchema: func(key string) (Schema, bool) {
					return nil, false
				},
			}
		}
		return types.NewErr("invalid object type, expected either Properties or AdditionalProperties with Allows=true and non-empty Schema")
	}

	if schema.Type() == "array" {
		l, ok := unstructured.([]interface{})
		if !ok {
			return types.NewErr("invalid data, expected an array for the provided schema with type=array")
		}
		if schema.Items() == nil {
			return types.NewErr("invalid array type, expected Items with a non-empty Schema")
		}
		typedList := unstructuredList{elements: l, itemsSchema: schema.Items()}
		listType := schema.XListType()
		if listType != "" {
			switch listType {
			case "map":
				mapKeys := schema.XListMapKeys()
				return &unstructuredMapList{unstructuredList: typedList, escapedKeyProps: escapeKeyProps(mapKeys)}
			case "set":
				return &unstructuredSetList{unstructuredList: typedList}
			case "atomic":
				return &typedList
			default:
				return types.NewErr("invalid x-kubernetes-list-type, expected 'map', 'set' or 'atomic' but got %s", listType)
			}
		}
		return &typedList
	}

	if schema.Type() == "string" {
		str, ok := unstructured.(string)
		if !ok {
			return types.NewErr("invalid data, expected string, got %T", unstructured)
		}
		switch schema.Format() {
		case "duration":
			d, err := strfmt.ParseDuration(str)
			if err != nil {
				return types.NewErr("Invalid duration %s: %v", str, err)
			}
			return types.Duration{Duration: d}
		case "date":
			d, err := time.Parse(strfmt.RFC3339FullDate, str) // strfmt uses this format for OpenAPIv3 value validation
			if err != nil {
				return types.NewErr("Invalid date formatted string %s: %v", str, err)
			}
			return types.Timestamp{Time: d}
		case "date-time":
			d, err := strfmt.ParseDateTime(str)
			if err != nil {
				return types.NewErr("Invalid date-time formatted string %s: %v", str, err)
			}
			return types.Timestamp{Time: time.Time(d)}
		case "byte":
			base64 := strfmt.Base64{}
			err := base64.UnmarshalText([]byte(str))
			if err != nil {
				return types.NewErr("Invalid byte formatted string %s: %v", str, err)
			}
			return types.Bytes(base64)
		}

		return types.String(str)
	}
	if schema.Type() == "number" {
		switch v := unstructured.(type) {
		// float representations of whole numbers (e.g. 1.0, 0.0) can convert to int representations (e.g. 1, 0) in yaml
		// to json translation, and then get parsed as int64s
		case int:
			return types.Double(v)
		case int32:
			return types.Double(v)
		case int64:
			return types.Double(v)

		case float32:
			return types.Double(v)
		case float64:
			return types.Double(v)
		default:
			return types.NewErr("invalid data, expected float, got %T", unstructured)
		}
	}
	if schema.Type() == "integer" {
		switch v := unstructured.(type) {
		case int:
			return types.Int(v)
		case int32:
			return types.Int(v)
		case int64:
			return types.Int(v)
		default:
			return types.NewErr("invalid data, expected int, got %T", unstructured)
		}
	}
	if schema.Type() == "boolean" {
		b, ok := unstructured.(bool)
		if !ok {
			return types.NewErr("invalid data, expected bool, got %T", unstructured)
		}
		return types.Bool(b)
	}

	if schema.IsXPreserveUnknownFields() {
		return &unknownPreserved{u: unstructured}
	}

	return types.NewErr("invalid type, expected object, array, number, integer, boolean or string, or no type with x-kubernetes-int-or-string or x-kubernetes-preserve-unknown-fields is true, got %s", schema.Type())
}


func (t *unknownPreserved) ConvertToNative(refType reflect.Type) (interface{}, error) {
	return nil, fmt.Errorf("type conversion to '%s' not supported for values preserved by x-kubernetes-preserve-unknown-fields", refType)
}


func (t *unknownPreserved) ConvertToType(typeValue ref.Type) ref.Val {
	return types.NewErr("type conversion to '%s' not supported for values preserved by x-kubernetes-preserve-unknown-fields", typeValue.TypeName())
}


func (t *unknownPreserved) Equal(other ref.Val) ref.Val {
	return types.Bool(equality.Semantic.DeepEqual(t.u, other.Value()))
}


func (t *unknownPreserved) Type() ref.Type {
	return types.UnknownType
}


func (t *unknownPreserved) Value() interface{} {
	return t.u // used by Equal checks
}


func (t *unstructuredMapList) getMap() map[interface{}]interface{} {
	t.Do(func() {
		t.mapOfList = make(map[interface{}]interface{}, len(t.elements))
		for _, e := range t.elements {
			t.mapOfList[t.toMapKey(e)] = e
		}
	})
	return t.mapOfList
}


// toMapKey returns a valid golang map key for the given element of the map list.
// element must be a valid map list entry where all map key props are scalar types (which are comparable in go
// and valid for use in a golang map key).
func (t *unstructuredMapList) toMapKey(element interface{}) interface{} {
	eObj, ok := element.(map[string]interface{})
	if !ok {
		return types.NewErr("unexpected data format for element of array with x-kubernetes-list-type=map: %T", element)
	}
	// Arrays are comparable in go and may be used as map keys, but maps and slices are not.
	// So we can special case small numbers of key props as arrays and fall back to serialization
	// for larger numbers of key props
	if len(t.escapedKeyProps) == 1 {
		return eObj[t.escapedKeyProps[0]]
	}
	if len(t.escapedKeyProps) == 2 {
		return [2]interface{}{eObj[t.escapedKeyProps[0]], eObj[t.escapedKeyProps[1]]}
	}
	if len(t.escapedKeyProps) == 3 {
		return [3]interface{}{eObj[t.escapedKeyProps[0]], eObj[t.escapedKeyProps[1]], eObj[t.escapedKeyProps[2]]}
	}

	key := make([]interface{}, len(t.escapedKeyProps))
	for i, kf := range t.escapedKeyProps {
		key[i] = eObj[kf]
	}
	return fmt.Sprintf("%v", key)
}


// Equal on a map list ignores list element order.
func (t *unstructuredMapList) Equal(other ref.Val) ref.Val {
	oMapList, ok := other.(traits.Lister)
	if !ok {
		return types.MaybeNoSuchOverloadErr(other)
	}
	sz := types.Int(len(t.elements))
	if sz != oMapList.Size() {
		return types.False
	}
	tMap := t.getMap()
	for it := oMapList.Iterator(); it.HasNext() == types.True; {
		v := it.Next()
		k := t.toMapKey(v.Value())
		tVal, ok := tMap[k]
		if !ok {
			return types.False
		}
		eq := UnstructuredToVal(tVal, t.itemsSchema).Equal(v)
		if eq != types.True {
			return eq // either false or error
		}
	}
	return types.True
}


// Add for a map list `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
// non-intersecting keys are appended, retaining their partial order.
func (t *unstructuredMapList) Add(other ref.Val) ref.Val {
	oMapList, ok := other.(traits.Lister)
	if !ok {
		return types.MaybeNoSuchOverloadErr(other)
	}
	elements := make([]interface{}, len(t.elements))
	keyToIdx := map[interface{}]int{}
	for i, e := range t.elements {
		k := t.toMapKey(e)
		keyToIdx[k] = i
		elements[i] = e
	}
	for it := oMapList.Iterator(); it.HasNext() == types.True; {
		v := it.Next().Value()
		k := t.toMapKey(v)
		if overwritePosition, ok := keyToIdx[k]; ok {
			elements[overwritePosition] = v
		} else {
			elements = append(elements, v)
		}
	}
	return &unstructuredMapList{
		unstructuredList: unstructuredList{elements: elements, itemsSchema: t.itemsSchema},
		escapedKeyProps:  t.escapedKeyProps,
	}
}


// escapeKeyProps returns identifiers with Escape applied to each.
// Identifiers that cannot be escaped are left as-is. They are inaccessible to CEL programs but are
// are still needed internally to perform equality checks.
func escapeKeyProps(idents []string) []string {
	result := make([]string, len(idents))
	for i, prop := range idents {
		if escaped, ok := cel.Escape(prop); ok {
			result[i] = escaped
		} else {
			result[i] = prop
		}
	}
	return result
}


func (t *unstructuredSetList) getSet() map[interface{}]struct{} {
	// sets are only allowed to contain scalar elements, which are comparable in go, and can safely be used as
	// golang map keys
	t.Do(func() {
		t.set = make(map[interface{}]struct{}, len(t.elements))
		for _, e := range t.elements {
			t.set[e] = struct{}{}
		}
	})
	return t.set
}


// Equal on a map list ignores list element order.
func (t *unstructuredSetList) Equal(other ref.Val) ref.Val {
	oSetList, ok := other.(traits.Lister)
	if !ok {
		return types.MaybeNoSuchOverloadErr(other)
	}
	sz := types.Int(len(t.elements))
	if sz != oSetList.Size() {
		return types.False
	}
	tSet := t.getSet()
	for it := oSetList.Iterator(); it.HasNext() == types.True; {
		next := it.Next().Value()
		_, ok := tSet[next]
		if !ok {
			return types.False
		}
	}
	return types.True
}


// Add for a set list `X + Y` performs a union where the array positions of all elements in `X` are preserved and
// non-intersecting elements in `Y` are appended, retaining their partial order.
func (t *unstructuredSetList) Add(other ref.Val) ref.Val {
	oSetList, ok := other.(traits.Lister)
	if !ok {
		return types.MaybeNoSuchOverloadErr(other)
	}
	elements := t.elements
	set := t.getSet()
	for it := oSetList.Iterator(); it.HasNext() == types.True; {
		next := it.Next().Value()
		if _, ok := set[next]; !ok {
			set[next] = struct{}{}
			elements = append(elements, next)
		}
	}
	return &unstructuredSetList{
		unstructuredList: unstructuredList{elements: elements, itemsSchema: t.itemsSchema},
		escapedKeyProps:  t.escapedKeyProps,
	}
}


func (t *unstructuredList) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
	switch typeDesc.Kind() {
	case reflect.Slice:
		switch t.itemsSchema.Type() {
		// Workaround for https://github.com/kubernetes/kubernetes/issues/117590 until we
		// resolve the desired behavior in cel-go via https://github.com/google/cel-go/issues/688
		case "string":
			var result []string
			for _, e := range t.elements {
				s, ok := e.(string)
				if !ok {
					return nil, fmt.Errorf("expected all elements to be of type string, but got %T", e)
				}
				result = append(result, s)
			}
			return result, nil
		default:
			return t.elements, nil
		}
	}
	return nil, fmt.Errorf("type conversion error from '%s' to '%s'", t.Type(), typeDesc)
}


func (t *unstructuredList) ConvertToType(typeValue ref.Type) ref.Val {
	switch typeValue {
	case types.ListType:
		return t
	case types.TypeType:
		return types.ListType
	}
	return types.NewErr("type conversion error from '%s' to '%s'", t.Type(), typeValue.TypeName())
}


func (t *unstructuredList) Equal(other ref.Val) ref.Val {
	oList, ok := other.(traits.Lister)
	if !ok {
		return types.MaybeNoSuchOverloadErr(other)
	}
	sz := types.Int(len(t.elements))
	if sz != oList.Size() {
		return types.False
	}
	for i := types.Int(0); i < sz; i++ {
		eq := t.Get(i).Equal(oList.Get(i))
		if eq != types.True {
			return eq // either false or error
		}
	}
	return types.True
}


func (t *unstructuredList) Type() ref.Type {
	return types.ListType
}


func (t *unstructuredList) Value() interface{} {
	return t.elements
}


func (t *unstructuredList) Add(other ref.Val) ref.Val {
	oList, ok := other.(traits.Lister)
	if !ok {
		return types.MaybeNoSuchOverloadErr(other)
	}
	elements := t.elements
	for it := oList.Iterator(); it.HasNext() == types.True; {
		next := it.Next().Value()
		elements = append(elements, next)
	}

	return &unstructuredList{elements: elements, itemsSchema: t.itemsSchema}
}


func (t *unstructuredList) Contains(val ref.Val) ref.Val {
	if types.IsUnknownOrError(val) {
		return val
	}
	var err ref.Val
	sz := len(t.elements)
	for i := 0; i < sz; i++ {
		elem := UnstructuredToVal(t.elements[i], t.itemsSchema)
		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
}


func (t *unstructuredList) 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(t.elements) {
		return types.NewErr("index out of bounds: %v", idx)
	}
	return UnstructuredToVal(t.elements[i], t.itemsSchema)
}


func (t *unstructuredList) Iterator() traits.Iterator {
	items := make([]ref.Val, len(t.elements))
	for i, item := range t.elements {
		itemCopy := item
		items[i] = UnstructuredToVal(itemCopy, t.itemsSchema)
	}
	return &listIterator{unstructuredList: t, items: items}
}


func (it *listIterator) HasNext() ref.Val {
	return types.Bool(it.idx < len(it.items))
}


func (it *listIterator) Next() ref.Val

func (it *listIterator) Next() ref.Val {
	item := it.items[it.idx]
	it.idx++
	return item
}


func (t *unstructuredList) Size() ref.Val {
	return types.Int(len(t.elements))
}


func (t *unstructuredMap) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
	switch typeDesc.Kind() {
	case reflect.Map:
		return t.value, nil
	}
	return nil, fmt.Errorf("type conversion error from '%s' to '%s'", t.Type(), typeDesc)
}


func (t *unstructuredMap) ConvertToType(typeValue ref.Type) ref.Val {
	switch typeValue {
	case types.MapType:
		return t
	case types.TypeType:
		return types.MapType
	}
	return types.NewErr("type conversion error from '%s' to '%s'", t.Type(), typeValue.TypeName())
}


func (t *unstructuredMap) Equal(other ref.Val) ref.Val {
	oMap, isMap := other.(traits.Mapper)
	if !isMap {
		return types.MaybeNoSuchOverloadErr(other)
	}
	if t.Size() != oMap.Size() {
		return types.False
	}
	for key, value := range t.value {
		if propSchema, ok := t.propSchema(key); ok {
			ov, found := oMap.Find(types.String(key))
			if !found {
				return types.False
			}
			v := UnstructuredToVal(value, propSchema)
			vEq := v.Equal(ov)
			if vEq != types.True {
				return vEq // either false or error
			}
		} else {
			// Must be an object with properties.
			// Since we've encountered an unknown field, fallback to unstructured equality checking.
			ouMap, ok := other.(*unstructuredMap)
			if !ok {
				// The compiler ensures equality is against the same type of object, so this should be unreachable
				return types.MaybeNoSuchOverloadErr(other)
			}
			if oValue, ok := ouMap.value[key]; ok {
				if !equality.Semantic.DeepEqual(value, oValue) {
					return types.False
				}
			}
		}
	}
	return types.True
}


func (t *unstructuredMap) Type() ref.Type {
	return types.MapType
}


func (t *unstructuredMap) Value() interface{} {
	return t.value
}


func (t *unstructuredMap) Contains(key ref.Val) ref.Val {
	v, found := t.Find(key)
	if v != nil && types.IsUnknownOrError(v) {
		return v
	}

	return types.Bool(found)
}


func (t *unstructuredMap) Get(key ref.Val) ref.Val {
	v, found := t.Find(key)
	if found {
		return v
	}
	return types.ValOrErr(key, "no such key: %v", key)
}


func (t *unstructuredMap) Iterator() traits.Iterator {
	isObject := t.schema.Properties() != nil
	keys := make([]ref.Val, len(t.value))
	i := 0
	for k := range t.value {
		if _, ok := t.propSchema(k); ok {
			mapKey := k
			if isObject {
				if escaped, ok := cel.Escape(k); ok {
					mapKey = escaped
				}
			}
			keys[i] = types.String(mapKey)
			i++
		}
	}
	return &mapIterator{unstructuredMap: t, keys: keys}
}


func (it *mapIterator) HasNext() ref.Val {
	return types.Bool(it.idx < len(it.keys))
}


func (it *mapIterator) Next() ref.Val

func (it *mapIterator) Next() ref.Val {
	key := it.keys[it.idx]
	it.idx++
	return key
}


func (t *unstructuredMap) Size() ref.Val {
	return types.Int(len(t.value))
}


func (t *unstructuredMap) Find(key ref.Val) (ref.Val, bool) {
	isObject := t.schema.Properties() != nil
	keyStr, ok := key.(types.String)
	if !ok {
		return types.MaybeNoSuchOverloadErr(key), true
	}
	k := keyStr.Value().(string)
	if isObject {
		k, ok = cel.Unescape(k)
		if !ok {
			return nil, false
		}
	}
	if v, ok := t.value[k]; ok {
		// If this is an object with properties, not an object with additionalProperties,
		// then null valued nullable fields are treated the same as absent optional fields.
		if isObject && v == nil {
			return nil, false
		}
		if propSchema, ok := t.propSchema(k); ok {
			return UnstructuredToVal(v, propSchema), true
		}
	}

	return nil, false
}


// EnumValue finds out the numeric value of the given enum name.
// The result comes from first provider that returns non-nil.
func (c *CompositedTypeProvider) EnumValue(enumName string) ref.Val {
	for _, p := range c.Providers {
		val := p.EnumValue(enumName)
		if val != nil {
			return val
		}
	}
	return nil
}


// FindIdent takes a qualified identifier name and returns a Value if one
// exists. The result comes from first provider that returns non-nil.
func (c *CompositedTypeProvider) FindIdent(identName string) (ref.Val, bool) {
	for _, p := range c.Providers {
		val, ok := p.FindIdent(identName)
		if ok {
			return val, ok
		}
	}
	return nil, false
}


// FindType finds the Type given a qualified type name, or return false
// if none of the providers finds the type.
// If any of the providers find the type, the first provider that returns true
// will be the result.
func (c *CompositedTypeProvider) FindType(typeName string) (*exprpb.Type, bool) {
	for _, p := range c.Providers {
		typ, ok := p.FindType(typeName)
		if ok {
			return typ, ok
		}
	}
	return nil, false
}


// FindFieldType returns the field type for a checked type value. Returns
// false if none of the providers can find the type.
// If multiple providers can find the field, the result is taken from
// the first that does.
func (c *CompositedTypeProvider) FindFieldType(messageType string, fieldName string) (*ref.FieldType, bool) {
	for _, p := range c.Providers {
		ft, ok := p.FindFieldType(messageType, fieldName)
		if ok {
			return ft, ok
		}
	}
	return nil, false
}


// NewValue creates a new type value from a qualified name and map of field
// name to value.
// If multiple providers can create the new type, the first that returns
// non-nil will decide the result.
func (c *CompositedTypeProvider) NewValue(typeName string, fields map[string]ref.Val) ref.Val {
	for _, p := range c.Providers {
		v := p.NewValue(typeName, fields)
		if v != nil {
			return v
		}
	}
	return nil
}


// NativeToValue takes the value and convert it into a ref.Val
// The result comes from the first TypeAdapter that returns non-nil.
func (c *CompositedTypeAdapter) NativeToValue(value interface{}) ref.Val {
	for _, a := range c.Adapters {
		v := a.NativeToValue(value)
		if v != nil {
			return v
		}
	}
	return nil
}


func (v *Error) Error() string

// Error implements the error interface.
func (v *Error) Error() string {
	return v.Detail
}


// 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
}


// 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
}


// 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
}


// 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
}


func Authz() cel.EnvOption

// Authz provides a CEL function library extension for performing authorization checks.
// Note that authorization checks are only supported for CEL expression fields in the API
// where an 'authorizer' variable is provided to the CEL expression. See the
// documentation of API fields where CEL expressions are used to learn if the 'authorizer'
// variable is provided.
//
// path
//
// Returns a PathCheck configured to check authorization for a non-resource request
// path (e.g. /healthz). If path is an empty string, an error is returned.
// Note that the leading '/' is not required.
//
//	<Authorizer>.path(<string>) <PathCheck>
//
// Examples:
//
//	authorizer.path('/healthz') // returns a PathCheck for the '/healthz' API path
//	authorizer.path('') // results in "path must not be empty" error
//	authorizer.path('  ') // results in "path must not be empty" error
//
// group
//
// Returns a GroupCheck configured to check authorization for the API resources for
// a particular API group.
// Note that authorization checks are only supported for CEL expression fields in the API
// where an 'authorizer' variable is provided to the CEL expression. Check the
// documentation of API fields where CEL expressions are used to learn if the 'authorizer'
// variable is provided.
//
//	<Authorizer>.group(<string>) <GroupCheck>
//
// Examples:
//
//	authorizer.group('apps') // returns a GroupCheck for the 'apps' API group
//	authorizer.group('') // returns a GroupCheck for the core API group
//	authorizer.group('example.com') // returns a GroupCheck for the custom resources in the 'example.com' API group
//
// serviceAccount
//
// Returns an Authorizer configured to check authorization for the provided service account namespace and name.
// If the name is not a valid DNS subdomain string (as defined by RFC 1123), an error is returned.
// If the namespace is not a valid DNS label (as defined by RFC 1123), an error is returned.
//
//	<Authorizer>.serviceAccount(<string>, <string>) <Authorizer>
//
// Examples:
//
//	authorizer.serviceAccount('default', 'myserviceaccount') // returns an Authorizer for the service account with namespace 'default' and name 'myserviceaccount'
//	authorizer.serviceAccount('not@a#valid!namespace', 'validname') // returns an error
//	authorizer.serviceAccount('valid.example.com', 'invalid@*name') // returns an error
//
// resource
//
// Returns a ResourceCheck configured to check authorization for a particular API resource.
// Note that the provided resource string should be a lower case plural name of a Kubernetes API resource.
//
//	<GroupCheck>.resource(<string>) <ResourceCheck>
//
// Examples:
//
//	authorizer.group('apps').resource('deployments') // returns a ResourceCheck for the 'deployments' resources in the 'apps' group.
//	authorizer.group('').resource('pods') // returns a ResourceCheck for the 'pods' resources in the core group.
//	authorizer.group('apps').resource('') // results in "resource must not be empty" error
//	authorizer.group('apps').resource('  ') // results in "resource must not be empty" error
//
// subresource
//
// Returns a ResourceCheck configured to check authorization for a particular subresource of an API resource.
// If subresource is set to "", the subresource field of this ResourceCheck is considered unset.
//
//	<ResourceCheck>.subresource(<string>) <ResourceCheck>
//
// Examples:
//
//	authorizer.group('').resource('pods').subresource('status') // returns a ResourceCheck the 'status' subresource of 'pods'
//	authorizer.group('apps').resource('deployments').subresource('scale') // returns a ResourceCheck the 'scale' subresource of 'deployments'
//	authorizer.group('example.com').resource('widgets').subresource('scale') // returns a ResourceCheck for the 'scale' subresource of the 'widgets' custom resource
//	authorizer.group('example.com').resource('widgets').subresource('') // returns a ResourceCheck for the 'widgets' resource.
//
// namespace
//
// Returns a ResourceCheck configured to check authorization for a particular namespace.
// For cluster scoped resources, namespace() does not need to be called; namespace defaults
// to "", which is the correct namespace value to use to check cluster scoped resources.
// If namespace is set to "", the ResourceCheck will check authorization for the cluster scope.
//
//	<ResourceCheck>.namespace(<string>) <ResourceCheck>
//
// Examples:
//
//	authorizer.group('apps').resource('deployments').namespace('test') // returns a ResourceCheck for 'deployments' in the 'test' namespace
//	authorizer.group('').resource('pods').namespace('default') // returns a ResourceCheck for 'pods' in the 'default' namespace
//	authorizer.group('').resource('widgets').namespace('') // returns a ResourceCheck for 'widgets' in the cluster scope
//
// name
//
// Returns a ResourceCheck configured to check authorization for a particular resource name.
// If name is set to "", the name field of this ResourceCheck is considered unset.
//
//	<ResourceCheck>.name(<name>) <ResourceCheck>
//
// Examples:
//
//	authorizer.group('apps').resource('deployments').namespace('test').name('backend') // returns a ResourceCheck for the 'backend' 'deployments' resource in the 'test' namespace
//	authorizer.group('apps').resource('deployments').namespace('test').name('') // returns a ResourceCheck for the 'deployments' resource in the 'test' namespace
//
// check
//
// For PathCheck, checks if the principal (user or service account) that sent the request is authorized for the HTTP request verb of the path.
// For ResourceCheck, checks if the principal (user or service account) that sent the request is authorized for the API verb and the configured authorization checks of the ResourceCheck.
// The check operation can be expensive, particularly in clusters using the webhook authorization mode.
//
//	<PathCheck>.check(<check>) <Decision>
//	<ResourceCheck>.check(<check>) <Decision>
//
// Examples:
//
//	authorizer.group('').resource('pods').namespace('default').check('create') // Checks if the principal (user or service account) is authorized create pods in the 'default' namespace.
//	authorizer.path('/healthz').check('get') // Checks if the principal (user or service account) is authorized to make HTTP GET requests to the /healthz API path.
//
// allowed
//
// Returns true if the authorizer's decision for the check is "allow".  Note that if the authorizer's decision is
// "no opinion", that the 'allowed' function will return false.
//
//	<Decision>.allowed() <bool>
//
// Examples:
//
//	authorizer.group('').resource('pods').namespace('default').check('create').allowed() // Returns true if the principal (user or service account) is allowed create pods in the 'default' namespace.
//	authorizer.path('/healthz').check('get').allowed()  // Returns true if the principal (user or service account) is allowed to make HTTP GET requests to the /healthz API path.
//
// reason
//
// Returns a string reason for the authorization decision
//
//	<Decision>.reason() <string>
//
// Examples:
//
//	authorizer.path('/healthz').check('GET').reason()
func Authz() cel.EnvOption {
	return cel.Lib(authzLib)
}


func (*authz) CompileOptions() []cel.EnvOption {
	options := make([]cel.EnvOption, 0, len(authzLibraryDecls))
	for name, overloads := range authzLibraryDecls {
		options = append(options, cel.Function(name, overloads...))
	}
	return options
}


func (*authz) ProgramOptions() []cel.ProgramOption {
	return []cel.ProgramOption{}
}


func authorizerPath(arg1, arg2 ref.Val) ref.Val {
	authz, ok := arg1.(authorizerVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	path, ok := arg2.Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	if len(strings.TrimSpace(path)) == 0 {
		return types.NewErr("path must not be empty")
	}

	return authz.pathCheck(path)
}


func authorizerGroup(arg1, arg2 ref.Val) ref.Val {
	authz, ok := arg1.(authorizerVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	group, ok := arg2.Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	return authz.groupCheck(group)
}


func authorizerServiceAccount(args ...ref.Val) ref.Val {
	argn := len(args)
	if argn != 3 {
		return types.NoSuchOverloadErr()
	}

	authz, ok := args[0].(authorizerVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(args[0])
	}

	namespace, ok := args[1].Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(args[1])
	}

	name, ok := args[2].Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(args[2])
	}

	if errors := apimachineryvalidation.ValidateServiceAccountName(name, false); len(errors) > 0 {
		return types.NewErr("Invalid service account name")
	}
	if errors := apimachineryvalidation.ValidateNamespaceName(namespace, false); len(errors) > 0 {
		return types.NewErr("Invalid service account namespace")
	}
	return authz.serviceAccount(namespace, name)
}


func groupCheckResource(arg1, arg2 ref.Val) ref.Val {
	groupCheck, ok := arg1.(groupCheckVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	resource, ok := arg2.Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	if len(strings.TrimSpace(resource)) == 0 {
		return types.NewErr("resource must not be empty")
	}
	return groupCheck.resourceCheck(resource)
}


func resourceCheckSubresource(arg1, arg2 ref.Val) ref.Val {
	resourceCheck, ok := arg1.(resourceCheckVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	subresource, ok := arg2.Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	result := resourceCheck
	result.subresource = subresource
	return result
}


func resourceCheckNamespace(arg1, arg2 ref.Val) ref.Val {
	resourceCheck, ok := arg1.(resourceCheckVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	namespace, ok := arg2.Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	result := resourceCheck
	result.namespace = namespace
	return result
}


func resourceCheckName(arg1, arg2 ref.Val) ref.Val {
	resourceCheck, ok := arg1.(resourceCheckVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	name, ok := arg2.Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	result := resourceCheck
	result.name = name
	return result
}


func pathCheckCheck(arg1, arg2 ref.Val) ref.Val {
	pathCheck, ok := arg1.(pathCheckVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	httpRequestVerb, ok := arg2.Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	return pathCheck.Authorize(context.TODO(), httpRequestVerb)
}


func resourceCheckCheck(arg1, arg2 ref.Val) ref.Val {
	resourceCheck, ok := arg1.(resourceCheckVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	apiVerb, ok := arg2.Value().(string)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg1)
	}

	return resourceCheck.Authorize(context.TODO(), apiVerb)
}


func decisionAllowed(arg ref.Val) ref.Val {
	decision, ok := arg.(decisionVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg)
	}

	return types.Bool(decision.authDecision == authorizer.DecisionAllow)
}


func decisionReason(arg ref.Val) ref.Val {
	decision, ok := arg.(decisionVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(arg)
	}

	return types.String(decision.reason)
}


func NewAuthorizerVal(userInfo user.Info, authorizer authorizer.Authorizer) ref.Val {
	return authorizerVal{receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType), userInfo: userInfo, authAuthorizer: authorizer}
}


func NewResourceAuthorizerVal(userInfo user.Info, authorizer authorizer.Authorizer, requestResource Resource) ref.Val {
	a := authorizerVal{receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType), userInfo: userInfo, authAuthorizer: authorizer}
	resource := requestResource.GetResource()
	g := a.groupCheck(resource.Group)
	r := g.resourceCheck(resource.Resource)
	r.subresource = requestResource.GetSubresource()
	r.namespace = requestResource.GetNamespace()
	r.name = requestResource.GetName()
	return r
}


func (a authorizerVal) pathCheck(path string) pathCheckVal {
	return pathCheckVal{receiverOnlyObjectVal: receiverOnlyVal(PathCheckType), authorizer: a, path: path}
}


func (a authorizerVal) groupCheck(group string) groupCheckVal {
	return groupCheckVal{receiverOnlyObjectVal: receiverOnlyVal(GroupCheckType), authorizer: a, group: group}
}


func (a authorizerVal) serviceAccount(namespace, name string) authorizerVal {
	sa := &serviceaccount.ServiceAccountInfo{Name: name, Namespace: namespace}
	return authorizerVal{
		receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType),
		userInfo:              sa.UserInfo(),
		authAuthorizer:        a.authAuthorizer,
	}
}


func (a pathCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
	attr := &authorizer.AttributesRecord{
		Path: a.path,
		Verb: verb,
		User: a.authorizer.userInfo,
	}

	decision, reason, err := a.authorizer.authAuthorizer.Authorize(ctx, attr)
	if err != nil {
		return types.NewErr("error in authorization check: %v", err)
	}
	return newDecision(decision, reason)
}


func (g groupCheckVal) resourceCheck(resource string) resourceCheckVal {
	return resourceCheckVal{receiverOnlyObjectVal: receiverOnlyVal(ResourceCheckType), groupCheck: g, resource: resource}
}


func (a resourceCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
	attr := &authorizer.AttributesRecord{
		ResourceRequest: true,
		APIGroup:        a.groupCheck.group,
		APIVersion:      "*",
		Resource:        a.resource,
		Subresource:     a.subresource,
		Namespace:       a.namespace,
		Name:            a.name,
		Verb:            verb,
		User:            a.groupCheck.authorizer.userInfo,
	}
	decision, reason, err := a.groupCheck.authorizer.authAuthorizer.Authorize(ctx, attr)
	if err != nil {
		return types.NewErr("error in authorization check: %v", err)
	}
	return newDecision(decision, reason)
}


func newDecision(authDecision authorizer.Decision, reason string) decisionVal {
	return decisionVal{receiverOnlyObjectVal: receiverOnlyVal(DecisionType), authDecision: authDecision, reason: reason}
}


// receiverOnlyVal returns a receiverOnlyObjectVal for the given type.
func receiverOnlyVal(objectType *cel.Type) receiverOnlyObjectVal {
	return receiverOnlyObjectVal{typeValue: types.NewTypeValue(objectType.String())}
}


// ConvertToNative implements ref.Val.ConvertToNative.
func (a receiverOnlyObjectVal) ConvertToNative(typeDesc reflect.Type) (any, error) {
	return nil, fmt.Errorf("type conversion error from '%s' to '%v'", a.typeValue.String(), typeDesc)
}


// ConvertToType implements ref.Val.ConvertToType.
func (a receiverOnlyObjectVal) ConvertToType(typeVal ref.Type) ref.Val {
	switch typeVal {
	case a.typeValue:
		return a
	case types.TypeType:
		return a.typeValue
	}
	return types.NewErr("type conversion error from '%s' to '%s'", a.typeValue, typeVal)
}


// Equal implements ref.Val.Equal.
func (a receiverOnlyObjectVal) Equal(other ref.Val) ref.Val {
	o, ok := other.(receiverOnlyObjectVal)
	if !ok {
		return types.MaybeNoSuchOverloadErr(other)
	}
	return types.Bool(a == o)
}


// Type implements ref.Val.Type.
func (a receiverOnlyObjectVal) Type() ref.Type {
	return a.typeValue
}


// Value implements ref.Val.Value.
func (a receiverOnlyObjectVal) Value() any {
	return types.NoSuchOverloadErr()
}


func (l *CostEstimator) CallCost(function, overloadId string, args []ref.Val, result ref.Val) *uint64 {
	switch function {
	case "check":
		// An authorization check has a fixed cost
		// This cost is set to allow for only two authorization checks per expression
		cost := uint64(350000)
		return &cost
	case "serviceAccount", "path", "group", "resource", "subresource", "namespace", "name", "allowed", "denied", "reason":
		// All authorization builder and accessor functions have a nominal cost
		cost := uint64(1)
		return &cost
	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 "check":
		// An authorization check has a fixed cost
		// This cost is set to allow for only two authorization checks per expression
		return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 350000, Max: 350000}}
	case "serviceAccount", "path", "group", "resource", "subresource", "namespace", "name", "allowed", "denied", "reason":
		// All authorization builder and accessor functions have a nominal cost
		return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
	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

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
}


func (i *itemsNode) Path() []string

func (i *itemsNode) Path() []string {
	return i.path
}


func (i *itemsNode) Type() *exprpb.Type

func (i *itemsNode) Type() *exprpb.Type {
	return i.t
}


func (i *itemsNode) Expr() *exprpb.Expr

func (i *itemsNode) Expr() *exprpb.Expr {
	return i.expr
}


func (i *itemsNode) ComputedSize() *checker.SizeEstimate {
	return nil
}


func traversalCost(v ref.Val) uint64

// 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
	}
}


func Lists() cel.EnvOption

// 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)
}


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

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

func min() functions.UnaryOp {
	return cmp("min", types.IntOne)
}


func max() functions.UnaryOp

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
}


func Regex() cel.EnvOption

// 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)
}


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

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)
}


func URLs() cel.EnvOption

// 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 without braces, e.g. "::1". If absent in the URL, returns an empty string.
//
//   - getHost: IPv6 addresses are returned with 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)
}


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

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

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

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

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

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

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

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)
}


func newCelMetrics() *CelMetrics

func newCelMetrics() *CelMetrics {
	m := &CelMetrics{
		compilationTime: metrics.NewHistogram(&metrics.HistogramOpts{
			Namespace:      namespace,
			Subsystem:      subsystem,
			Name:           "compilation_duration_seconds",
			Help:           "CEL compilation time in seconds.",
			StabilityLevel: metrics.ALPHA,
		}),
		evaluationTime: metrics.NewHistogram(&metrics.HistogramOpts{
			Namespace:      namespace,
			Subsystem:      subsystem,
			Name:           "evaluation_duration_seconds",
			Help:           "CEL evaluation time in seconds.",
			StabilityLevel: metrics.ALPHA,
		}),
	}

	legacyregistry.MustRegister(m.compilationTime)
	legacyregistry.MustRegister(m.evaluationTime)

	return m
}


// ObserveCompilation records a CEL compilation with the time the compilation took.
func (m *CelMetrics) ObserveCompilation(elapsed time.Duration) {
	seconds := elapsed.Seconds()
	m.compilationTime.Observe(seconds)
}


// ObserveEvaluation records a CEL evaluation with the time the evaluation took.
func (m *CelMetrics) ObserveEvaluation(elapsed time.Duration) {
	seconds := elapsed.Seconds()
	m.evaluationTime.Observe(seconds)
}


func (sb *SchemaOrBool) Schema() common.Schema {
	return &Schema{Schema: sb.SchemaOrBool.Schema}
}


func (sb *SchemaOrBool) Allows() bool

func (sb *SchemaOrBool) Allows() bool {
	return sb.SchemaOrBool.Allows
}


func (s *Schema) Type() string

func (s *Schema) Type() string {
	if len(s.Schema.Type) == 0 {
		return ""
	}
	return s.Schema.Type[0]
}


func (s *Schema) Format() string

func (s *Schema) Format() string {
	return s.Schema.Format
}


func (s *Schema) Items() common.Schema

func (s *Schema) Items() common.Schema {
	if s.Schema.Items == nil || s.Schema.Items.Schema == nil {
		return nil
	}
	return &Schema{Schema: s.Schema.Items.Schema}
}


func (s *Schema) Properties() map[string]common.Schema {
	if s.Schema.Properties == nil {
		return nil
	}
	res := make(map[string]common.Schema, len(s.Schema.Properties))
	for n, prop := range s.Schema.Properties {
		// map value is unaddressable, create a shallow copy
		// this is a shallow non-recursive copy
		s := prop
		res[n] = &Schema{Schema: &s}
	}
	return res
}


func (s *Schema) AdditionalProperties() common.SchemaOrBool {
	if s.Schema.AdditionalProperties == nil {
		return nil
	}
	return &SchemaOrBool{SchemaOrBool: s.Schema.AdditionalProperties}
}


func (s *Schema) Default() any

func (s *Schema) Default() any {
	return s.Schema.Default
}


func (s *Schema) MaxItems() *int64

func (s *Schema) MaxItems() *int64 {
	return s.Schema.MaxItems
}


func (s *Schema) MaxLength() *int64

func (s *Schema) MaxLength() *int64 {
	return s.Schema.MaxLength
}


func (s *Schema) MaxProperties() *int64

func (s *Schema) MaxProperties() *int64 {
	return s.Schema.MaxProperties
}


func (s *Schema) Required() []string

func (s *Schema) Required() []string {
	return s.Schema.Required
}


func (s *Schema) Enum() []any

func (s *Schema) Enum() []any {
	return s.Schema.Enum
}


func (s *Schema) Nullable() bool

func (s *Schema) Nullable() bool {
	return s.Schema.Nullable
}


func (s *Schema) IsXIntOrString() bool

func (s *Schema) IsXIntOrString() bool {
	return isXIntOrString(s.Schema)
}


func (s *Schema) IsXEmbeddedResource() bool {
	return isXEmbeddedResource(s.Schema)
}


func (s *Schema) IsXPreserveUnknownFields() bool {
	return isXPreserveUnknownFields(s.Schema)
}


func (s *Schema) XListType() string

func (s *Schema) XListType() string {
	return getXListType(s.Schema)
}


func (s *Schema) XListMapKeys() []string {
	return getXListMapKeys(s.Schema)
}


func (s *Schema) WithTypeAndObjectMeta() common.Schema {
	return &Schema{common.WithTypeAndObjectMeta(s.Schema)}
}


func UnstructuredToVal(unstructured any, schema *spec.Schema) ref.Val {
	return common.UnstructuredToVal(unstructured, &Schema{schema})
}


func SchemaDeclType(s *spec.Schema, isResourceRoot bool) *apiservercel.DeclType {
	return common.SchemaDeclType(&Schema{Schema: s}, isResourceRoot)
}


func MakeMapList(sts *spec.Schema, items []interface{}) (rv common.MapList) {
	return common.MakeMapList(&Schema{Schema: sts}, items)
}


func isExtension(schema *spec.Schema, key string) bool {
	v, ok := schema.Extensions.GetBool(key)
	return v && ok
}


func isXIntOrString(schema *spec.Schema) bool {
	// built-in types have the Format while CRDs use extension
	// both are valid, checking both
	return schema.Format == intOrStringFormat || isExtension(schema, extIntOrString)
}


func isXEmbeddedResource(schema *spec.Schema) bool {
	return isExtension(schema, extEmbeddedResource)
}


func isXPreserveUnknownFields(schema *spec.Schema) bool {
	return isExtension(schema, extPreserveUnknownFields)
}


func getXListType(schema *spec.Schema) string {
	s, _ := schema.Extensions.GetString(extListType)
	return s
}


func getXListMapKeys(schema *spec.Schema) []string {
	mapKeys, ok := schema.Extensions.GetStringSlice(extListMapKeys)
	if !ok {
		return nil
	}
	return mapKeys
}


// NewDefinitionsSchemaResolver creates a new DefinitionsSchemaResolver.
// An example working setup:
// scheme         = "k8s.io/client-go/kubernetes/scheme".Scheme
// getDefinitions = "k8s.io/kubernetes/pkg/generated/openapi".GetOpenAPIDefinitions
func NewDefinitionsSchemaResolver(scheme *runtime.Scheme, getDefinitions common.GetOpenAPIDefinitions) *DefinitionsSchemaResolver {
	gvkToSchema := make(map[schema.GroupVersionKind]*spec.Schema)
	namer := openapi.NewDefinitionNamer(scheme)
	defs := getDefinitions(func(path string) spec.Ref {
		return spec.MustCreateRef(path)
	})
	for name, def := range defs {
		_, e := namer.GetDefinitionName(name)
		gvks := extensionsToGVKs(e)
		s := def.Schema // map value not addressable, make copy
		for _, gvk := range gvks {
			gvkToSchema[gvk] = &s
		}
	}
	return &DefinitionsSchemaResolver{
		gvkToSchema: gvkToSchema,
		defs:        defs,
	}
}


func (d *DefinitionsSchemaResolver) ResolveSchema(gvk schema.GroupVersionKind) (*spec.Schema, error) {
	s, ok := d.gvkToSchema[gvk]
	if !ok {
		return nil, fmt.Errorf("cannot resolve %v: %w", gvk, ErrSchemaNotFound)
	}
	s, err := populateRefs(func(ref string) (*spec.Schema, bool) {
		// find the schema by the ref string, and return a deep copy
		def, ok := d.defs[ref]
		if !ok {
			return nil, false
		}
		s := def.Schema
		return &s, true
	}, s)
	if err != nil {
		return nil, err
	}
	return s, nil
}


func extensionsToGVKs(extensions spec.Extensions) []schema.GroupVersionKind {
	gvksAny, ok := extensions[extGVK]
	if !ok {
		return nil
	}
	gvks, ok := gvksAny.([]any)
	if !ok {
		return nil
	}
	result := make([]schema.GroupVersionKind, 0, len(gvks))
	for _, gvkAny := range gvks {
		// type check the map and all fields
		gvkMap, ok := gvkAny.(map[string]any)
		if !ok {
			return nil
		}
		g, ok := gvkMap["group"].(string)
		if !ok {
			return nil
		}
		v, ok := gvkMap["version"].(string)
		if !ok {
			return nil
		}
		k, ok := gvkMap["kind"].(string)
		if !ok {
			return nil
		}
		result = append(result, schema.GroupVersionKind{
			Group:   g,
			Version: v,
			Kind:    k,
		})
	}
	return result
}


func (r *ClientDiscoveryResolver) ResolveSchema(gvk schema.GroupVersionKind) (*spec.Schema, error) {
	p, err := r.Discovery.OpenAPIV3().Paths()
	if err != nil {
		return nil, err
	}
	resourcePath := resourcePathFromGV(gvk.GroupVersion())
	c, ok := p[resourcePath]
	if !ok {
		return nil, fmt.Errorf("cannot resolve group version %q: %w", gvk.GroupVersion(), ErrSchemaNotFound)
	}
	b, err := c.Schema(runtime.ContentTypeJSON)
	if err != nil {
		return nil, err
	}
	resp := new(schemaResponse)
	err = json.Unmarshal(b, resp)
	if err != nil {
		return nil, err
	}
	s, err := resolveType(resp, gvk)
	if err != nil {
		return nil, err
	}
	s, err = populateRefs(func(ref string) (*spec.Schema, bool) {
		s, ok := resp.Components.Schemas[strings.TrimPrefix(ref, refPrefix)]
		return s, ok
	}, s)
	if err != nil {
		return nil, err
	}
	return s, nil
}


func resolveType(resp *schemaResponse, gvk schema.GroupVersionKind) (*spec.Schema, error) {
	for _, s := range resp.Components.Schemas {
		var gvks []schema.GroupVersionKind
		err := s.Extensions.GetObject(extGVK, &gvks)
		if err != nil {
			return nil, err
		}
		for _, g := range gvks {
			if g == gvk {
				return s, nil
			}
		}
	}
	return nil, fmt.Errorf("cannot resolve group version kind %q: %w", gvk, ErrSchemaNotFound)
}


func resourcePathFromGV(gv schema.GroupVersion) string {
	var resourcePath string
	if len(gv.Group) == 0 {
		resourcePath = fmt.Sprintf("api/%s", gv.Version)
	} else {
		resourcePath = fmt.Sprintf("apis/%s/%s", gv.Group, gv.Version)
	}
	return resourcePath
}


// populateRefs recursively replaces Refs in the schema with the referred one.
// schemaOf is the callback to find the corresponding schema by the ref.
// This function will not mutate the original schema. If the schema needs to be
// mutated, a copy will be returned, otherwise it returns the original schema.
func populateRefs(schemaOf func(ref string) (*spec.Schema, bool), schema *spec.Schema) (*spec.Schema, error) {
	result := *schema
	changed := false

	ref, isRef := refOf(schema)
	if isRef {
		// replace the whole schema with the referred one.
		resolved, ok := schemaOf(ref)
		if !ok {
			return nil, fmt.Errorf("internal error: cannot resolve Ref %q: %w", ref, ErrSchemaNotFound)
		}
		result = *resolved
		changed = true
	}
	// schema is an object, populate its properties and additionalProperties
	props := make(map[string]spec.Schema, len(schema.Properties))
	propsChanged := false
	for name, prop := range result.Properties {
		populated, err := populateRefs(schemaOf, &prop)
		if err != nil {
			return nil, err
		}
		if populated != &prop {
			propsChanged = true
		}
		props[name] = *populated
	}
	if propsChanged {
		changed = true
		result.Properties = props
	}
	if result.AdditionalProperties != nil && result.AdditionalProperties.Schema != nil {
		populated, err := populateRefs(schemaOf, result.AdditionalProperties.Schema)
		if err != nil {
			return nil, err
		}
		if populated != result.AdditionalProperties.Schema {
			changed = true
			result.AdditionalProperties.Schema = populated
		}
	}
	// schema is a list, populate its items
	if result.Items != nil && result.Items.Schema != nil {
		populated, err := populateRefs(schemaOf, result.Items.Schema)
		if err != nil {
			return nil, err
		}
		if populated != result.Items.Schema {
			changed = true
			result.Items.Schema = populated
		}
	}
	if changed {
		return &result, nil
	}
	return schema, nil
}


func refOf(schema *spec.Schema) (string, bool) {
	if schema.Ref.GetURL() != nil {
		return schema.Ref.String(), true
	}
	// A Ref may be wrapped in allOf to preserve its description
	// see https://github.com/kubernetes/kubernetes/issues/106387
	// For kube-openapi, allOf is only used for wrapping a Ref.
	for _, allOf := range schema.AllOf {
		if ref, isRef := refOf(&allOf); isRef {
			return ref, isRef
		}
	}
	return "", false
}


// 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,
		},
	}
}


// 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
}


// 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,
	}
}