compileRule

func compileRule(rule apiextensions.ValidationRule, env *cel.Env, perCallLimit uint64, estimator *library.CostEstimator, maxCardinality uint64) (compilationResult CompilationResult) {
	if len(strings.TrimSpace(rule.Rule)) == 0 {
		// include a compilation result, but leave both program and error nil per documented return semantics of this
		// function
		return
	}
	ast, issues := env.Compile(rule.Rule)
	if issues != nil {
		compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "compilation failed: " + issues.String()}
		return
	}
	if ast.OutputType() != cel.BoolType {
		compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "cel expression must evaluate to a bool"}
		return
	}

	checkedExpr, err := cel.AstToCheckedExpr(ast)
	if err != nil {
		// should be impossible since env.Compile returned no issues
		compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInternal, Detail: "unexpected compilation error: " + err.Error()}
		return
	}
	for _, ref := range checkedExpr.ReferenceMap {
		if ref.Name == OldScopedVarName {
			compilationResult.TransitionRule = true
			break
		}
	}

	// TODO: Ideally we could configure the per expression limit at validation time and set it to the remaining overall budget, but we would either need a way to pass in a limit at evaluation time or move program creation to validation time
	prog, err := env.Program(ast,
		cel.EvalOptions(cel.OptOptimize, cel.OptTrackCost),
		cel.CostLimit(perCallLimit),
		cel.CostTracking(estimator),
		cel.OptimizeRegex(library.ExtensionLibRegexOptimizations...),
		cel.InterruptCheckFrequency(celconfig.CheckFrequency),
	)
	if err != nil {
		compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "program instantiation failed: " + err.Error()}
		return
	}
	costEst, err := env.EstimateCost(ast, estimator)
	if err != nil {
		compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInternal, Detail: "cost estimation failed: " + err.Error()}
		return
	}
	compilationResult.MaxCost = costEst.Max
	compilationResult.MaxCardinality = maxCardinality
	compilationResult.Program = prog
	if rule.MessageExpression != "" {
		ast, issues := env.Compile(rule.MessageExpression)
		if issues != nil {
			compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "messageExpression compilation failed: " + issues.String()}
			return
		}
		if ast.OutputType() != cel.StringType {
			compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "messageExpression must evaluate to a string"}
			return
		}

		_, err := cel.AstToCheckedExpr(ast)
		if err != nil {
			compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInternal, Detail: "unexpected messageExpression compilation error: " + err.Error()}
			return
		}

		msgProg, err := env.Program(ast,
			cel.EvalOptions(cel.OptOptimize, cel.OptTrackCost),
			cel.CostLimit(perCallLimit),
			cel.CostTracking(estimator),
			cel.OptimizeRegex(library.ExtensionLibRegexOptimizations...),
			cel.InterruptCheckFrequency(celconfig.CheckFrequency),
		)
		if err != nil {
			compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "messageExpression instantiation failed: " + err.Error()}
			return
		}
		costEst, err := env.EstimateCost(ast, estimator)
		if err != nil {
			compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInternal, Detail: "cost estimation failed for messageExpression: " + err.Error()}
			return
		}
		compilationResult.MessageExpression = msgProg
		compilationResult.MessageExpressionMaxCost = costEst.Max
	}
	return
}


generateUniqueSelfTypeName

// generateUniqueSelfTypeName creates a placeholder type name to use in a CEL programs for cases
// where we do not wish to expose a stable type name to CEL validator rule authors. For this to effectively prevent
// developers from depending on the generated name (i.e. using it in CEL programs), it must be changed each time a
// CRD is created or updated.
func generateUniqueSelfTypeName() string {
	return fmt.Sprintf("selfType%d", time.Now().Nanosecond())
}


newCostEstimator

func newCostEstimator(root *apiservercel.DeclType) *library.CostEstimator {
	return &library.CostEstimator{SizeEstimator: &sizeEstimator{root: root}}
}


EstimateSize

func (c *sizeEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate {
	if len(element.Path()) == 0 {
		// Path() can return an empty list, early exit if it does since we can't
		// provide size estimates when that happens
		return nil
	}
	currentNode := c.root
	// cut off "self" from path, since we always start there
	for _, name := range element.Path()[1:] {
		switch name {
		case "@items", "@values":
			if currentNode.ElemType == nil {
				return nil
			}
			currentNode = currentNode.ElemType
		case "@keys":
			if currentNode.KeyType == nil {
				return nil
			}
			currentNode = currentNode.KeyType
		default:
			field, ok := currentNode.Fields[name]
			if !ok {
				return nil
			}
			if field.Type == nil {
				return nil
			}
			currentNode = field.Type
		}
	}
	return &checker.SizeEstimate{Min: 0, Max: uint64(currentNode.MaxElements)}
}


EstimateCallCost

func (c *sizeEstimator) EstimateCallCost(function, overloadID string, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
	return nil
}


maxCardinality

// maxCardinality returns the maximum number of times data conforming to the minimum size given could possibly exist in
// an object serialized to JSON. For cases where a schema is contained under map or array schemas of unbounded
// size, this can be used as an estimate as the worst case number of times data matching the schema could be repeated.
// Note that this only assumes a single comma between data elements, so if the schema is contained under only maps,
// this estimates a higher cardinality that would be possible. DeclType.MinSerializedSize is meant to be passed to
// this function.
func maxCardinality(minSize int64) uint64 {
	sz := minSize + 1 // assume at least one comma between elements
	return uint64(celconfig.MaxRequestSizeBytes / sz)
}


makeMapList

// 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.Structural, items []interface{}) (rv common.MapList) {
	return common.MakeMapList(&model.Structural{Structural: sts}, items)
}


Schema

func (sb *StructuralOrBool) Schema() common.Schema {
	if sb.StructuralOrBool.Structural == nil {
		return nil
	}
	return &Structural{Structural: sb.StructuralOrBool.Structural}
}


Allows

func (sb *StructuralOrBool) Allows() bool {
	return sb.StructuralOrBool.Bool
}


Type

func (s *Structural) Type() string {
	return s.Structural.Type
}


Format

func (s *Structural) Format() string {
	if s.Structural.ValueValidation == nil {
		return ""
	}
	return s.Structural.ValueValidation.Format
}


Items

func (s *Structural) Items() common.Schema {
	return &Structural{Structural: s.Structural.Items}
}


Properties

func (s *Structural) Properties() map[string]common.Schema {
	if s.Structural.Properties == nil {
		return nil
	}
	res := make(map[string]common.Schema, len(s.Structural.Properties))
	for n, prop := range s.Structural.Properties {
		s := prop
		res[n] = &Structural{Structural: &s}
	}
	return res
}


AdditionalProperties

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


Default

func (s *Structural) Default() any {
	return s.Structural.Default.Object
}


MaxItems

func (s *Structural) MaxItems() *int64 {
	if s.Structural.ValueValidation == nil {
		return nil
	}
	return s.Structural.ValueValidation.MaxItems
}


MaxLength

func (s *Structural) MaxLength() *int64 {
	if s.Structural.ValueValidation == nil {
		return nil
	}
	return s.Structural.ValueValidation.MaxLength
}


MaxProperties

func (s *Structural) MaxProperties() *int64 {
	if s.Structural.ValueValidation == nil {
		return nil
	}
	return s.Structural.ValueValidation.MaxProperties
}


Required

func (s *Structural) Required() []string {
	if s.Structural.ValueValidation == nil {
		return nil
	}
	return s.Structural.ValueValidation.Required
}


Enum

func (s *Structural) Enum() []any {
	if s.Structural.ValueValidation == nil {
		return nil
	}
	ret := make([]any, 0, len(s.Structural.ValueValidation.Enum))
	for _, e := range s.Structural.ValueValidation.Enum {
		ret = append(ret, e.Object)
	}
	return ret
}


Nullable

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


IsXIntOrString

func (s *Structural) IsXIntOrString() bool {
	return s.Structural.XIntOrString
}


IsXEmbeddedResource

func (s *Structural) IsXEmbeddedResource() bool {
	return s.Structural.XEmbeddedResource
}


IsXPreserveUnknownFields

func (s *Structural) IsXPreserveUnknownFields() bool {
	return s.Structural.XPreserveUnknownFields
}


XListType

func (s *Structural) XListType() string {
	if s.Structural.XListType == nil {
		return ""
	}
	return *s.Structural.XListType
}


XListMapKeys

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


WithTypeAndObjectMeta

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


SchemaDeclType

// 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.Structural, isResourceRoot bool) *apiservercel.DeclType {
	return common.SchemaDeclType(&Structural{Structural: s}, isResourceRoot)
}


WithTypeAndObjectMeta

// 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 *schema.Structural) *schema.Structural {
	if s.Properties != nil &&
		s.Properties["kind"].Type == "string" &&
		s.Properties["apiVersion"].Type == "string" &&
		s.Properties["metadata"].Type == "object" &&
		s.Properties["metadata"].Properties != nil &&
		s.Properties["metadata"].Properties["name"].Type == "string" &&
		s.Properties["metadata"].Properties["generateName"].Type == "string" {
		return s
	}
	result := &schema.Structural{
		Generic:         s.Generic,
		Extensions:      s.Extensions,
		ValueValidation: s.ValueValidation,
	}
	props := make(map[string]schema.Structural, len(s.Properties))
	for k, prop := range s.Properties {
		props[k] = prop
	}
	stringType := schema.Structural{Generic: schema.Generic{Type: "string"}}
	props["kind"] = stringType
	props["apiVersion"] = stringType
	props["metadata"] = schema.Structural{
		Generic: schema.Generic{Type: "object"},
		Properties: map[string]schema.Structural{
			"name":         stringType,
			"generateName": stringType,
		},
	}
	result.Properties = props

	return result
}


NewValidator

// NewValidator returns compiles all the CEL programs defined in x-kubernetes-validations extensions
// of the Structural schema and returns a custom resource validator that contains nested
// validators for all items, properties and additionalProperties that transitively contain validator rules.
// Returns nil if there are no validator rules in the Structural schema. May return a validator containing only errors.
// Adding perCallLimit as input arg for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input
func NewValidator(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) *Validator {
	if !hasXValidations(s) {
		return nil
	}
	return validator(s, isResourceRoot, model.SchemaDeclType(s, isResourceRoot), perCallLimit)
}


validator

// validator creates a Validator for all x-kubernetes-validations at the level of the provided schema and lower and
// returns the Validator if any x-kubernetes-validations exist in the schema, or nil if no x-kubernetes-validations
// exist. declType is expected to be a CEL DeclType corresponding to the structural schema.
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType, perCallLimit uint64) *Validator {
	compiledRules, err := Compile(s, declType, perCallLimit)
	var itemsValidator, additionalPropertiesValidator *Validator
	var propertiesValidators map[string]Validator
	if s.Items != nil {
		itemsValidator = validator(s.Items, s.Items.XEmbeddedResource, declType.ElemType, perCallLimit)
	}
	if len(s.Properties) > 0 {
		propertiesValidators = make(map[string]Validator, len(s.Properties))
		for k, p := range s.Properties {
			prop := p
			var fieldType *cel.DeclType
			if escapedPropName, ok := cel.Escape(k); ok {
				if f, ok := declType.Fields[escapedPropName]; ok {
					fieldType = f.Type
				} else {
					// fields with unknown types are omitted from CEL validation entirely
					continue
				}
			} else {
				// field may be absent from declType if the property name is unescapable, in which case we should convert
				// the field value type to a DeclType.
				fieldType = model.SchemaDeclType(&prop, prop.XEmbeddedResource)
				if fieldType == nil {
					continue
				}
			}
			if p := validator(&prop, prop.XEmbeddedResource, fieldType, perCallLimit); p != nil {
				propertiesValidators[k] = *p
			}
		}
	}
	if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
		additionalPropertiesValidator = validator(s.AdditionalProperties.Structural, s.AdditionalProperties.Structural.XEmbeddedResource, declType.ElemType, perCallLimit)
	}
	if len(compiledRules) > 0 || err != nil || itemsValidator != nil || additionalPropertiesValidator != nil || len(propertiesValidators) > 0 {
		var activationFactory func(*schema.Structural, interface{}, interface{}) interpreter.Activation = validationActivationWithoutOldSelf
		for _, rule := range compiledRules {
			if rule.TransitionRule {
				activationFactory = validationActivationWithOldSelf
				break
			}
		}

		return &Validator{
			compiledRules:        compiledRules,
			compilationErr:       err,
			isResourceRoot:       isResourceRoot,
			Items:                itemsValidator,
			AdditionalProperties: additionalPropertiesValidator,
			Properties:           propertiesValidators,
			celActivationFactory: activationFactory,
		}
	}

	return nil
}


Validate

// Validate validates all x-kubernetes-validations rules in Validator against obj and returns any errors.
// If the validation rules exceed the costBudget, subsequent evaluations will be skipped, the list of errs returned will not be empty, and a negative remainingBudget will be returned.
// Most callers can ignore the returned remainingBudget value unless another validate call is going to be made
// context is passed for supporting context cancellation during cel validation
func (s *Validator) Validate(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj interface{}, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
	t := time.Now()
	defer func() {
		metrics.Metrics.ObserveEvaluation(time.Since(t))
	}()
	remainingBudget = costBudget
	if s == nil || obj == nil {
		return nil, remainingBudget
	}

	errs, remainingBudget = s.validateExpressions(ctx, fldPath, sts, obj, oldObj, remainingBudget)
	if remainingBudget < 0 {
		return errs, remainingBudget
	}
	switch obj := obj.(type) {
	case []interface{}:
		oldArray, _ := oldObj.([]interface{})
		var arrayErrs field.ErrorList
		arrayErrs, remainingBudget = s.validateArray(ctx, fldPath, sts, obj, oldArray, remainingBudget)
		errs = append(errs, arrayErrs...)
		return errs, remainingBudget
	case map[string]interface{}:
		oldMap, _ := oldObj.(map[string]interface{})
		var mapErrs field.ErrorList
		mapErrs, remainingBudget = s.validateMap(ctx, fldPath, sts, obj, oldMap, remainingBudget)
		errs = append(errs, mapErrs...)
		return errs, remainingBudget
	}
	return errs, remainingBudget
}


validateExpressions

func (s *Validator) validateExpressions(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj interface{}, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
	// guard against oldObj being a non-nil interface with a nil value
	if oldObj != nil {
		v := reflect.ValueOf(oldObj)
		switch v.Kind() {
		case reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
			if v.IsNil() {
				oldObj = nil // +k8s:verify-mutation:reason=clone
			}
		}
	}

	remainingBudget = costBudget
	if obj == nil {
		// We only validate non-null values. Rules that need to check for the state of a nullable value or the presence of an optional
		// field must do so from the surrounding schema. E.g. if an array has nullable string items, a rule on the array
		// schema can check if items are null, but a rule on the nullable string schema only validates the non-null strings.
		return nil, remainingBudget
	}
	if s.compilationErr != nil {
		errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("rule compiler initialization error: %v", s.compilationErr)))
		return errs, remainingBudget
	}
	if len(s.compiledRules) == 0 {
		return nil, remainingBudget // nothing to do
	}
	if remainingBudget <= 0 {
		errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run")))
		return errs, -1
	}
	if s.isResourceRoot {
		sts = model.WithTypeAndObjectMeta(sts)
	}
	activation := s.celActivationFactory(sts, obj, oldObj)
	for i, compiled := range s.compiledRules {
		rule := sts.XValidations[i]
		if compiled.Error != nil {
			errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("rule compile error: %v", compiled.Error)))
			continue
		}
		if compiled.Program == nil {
			// rule is empty
			continue
		}
		if compiled.TransitionRule && oldObj == nil {
			// transition rules are evaluated only if there is a comparable existing value
			continue
		}
		evalResult, evalDetails, err := compiled.Program.ContextEval(ctx, activation)
		if evalDetails == nil {
			errs = append(errs, field.InternalError(fldPath, fmt.Errorf("runtime cost could not be calculated for validation rule: %v, no further validation rules will be run", ruleErrorString(rule))))
			return errs, -1
		} else {
			rtCost := evalDetails.ActualCost()
			if rtCost == nil {
				errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("runtime cost could not be calculated for validation rule: %v, no further validation rules will be run", ruleErrorString(rule))))
				return errs, -1
			} else {
				if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget {
					errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run")))
					return errs, -1
				}
				remainingBudget -= int64(*rtCost)
			}
		}
		if err != nil {
			// see types.Err for list of well defined error types
			if strings.HasPrefix(err.Error(), "no such overload") {
				// Most overload errors are caught by the compiler, which provides details on where exactly in the rule
				// error was found. Here, an overload error has occurred at runtime no details are provided, so we
				// append a more descriptive error message. This error can only occur when static type checking has
				// been bypassed. int-or-string is typed as dynamic and so bypasses compiler type checking.
				errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("'%v': call arguments did not match a supported operator, function or macro signature for rule: %v", err, ruleErrorString(rule))))
			} else if strings.HasPrefix(err.Error(), "operation cancelled: actual cost limit exceeded") {
				errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("'%v': no further validation rules will be run due to call cost exceeds limit for rule: %v", err, ruleErrorString(rule))))
				return errs, -1
			} else {
				// no such key: {key}, index out of bounds: {index}, integer overflow, division by zero, ...
				errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("%v evaluating rule: %v", err, ruleErrorString(rule))))
			}
			continue
		}
		if evalResult != types.True {
			if compiled.MessageExpression != nil {
				messageExpression, newRemainingBudget, msgErr := evalMessageExpression(ctx, compiled.MessageExpression, rule.MessageExpression, activation, remainingBudget)
				if msgErr != nil {
					if msgErr.Type == cel.ErrorTypeInternal {
						errs = append(errs, field.InternalError(fldPath, msgErr))
						return errs, -1
					} else if msgErr.Type == cel.ErrorTypeInvalid {
						errs = append(errs, field.Invalid(fldPath, sts.Type, msgErr.Error()))
						return errs, -1
					} else {
						klog.V(2).ErrorS(msgErr, "messageExpression evaluation failed")
						errs = append(errs, field.Invalid(fldPath, sts.Type, ruleMessageOrDefault(rule)))
						remainingBudget = newRemainingBudget
					}
				} else {
					errs = append(errs, field.Invalid(fldPath, sts.Type, messageExpression))
					remainingBudget = newRemainingBudget
				}
			} else {
				errs = append(errs, field.Invalid(fldPath, sts.Type, ruleMessageOrDefault(rule)))
			}
		}
	}
	return errs, remainingBudget
}


evalMessageExpression

// evalMessageExpression evaluates the given message expression and returns the evaluated string form and the remaining budget, or an error if one
// occurred during evaluation.
func evalMessageExpression(ctx context.Context, expr celgo.Program, exprSrc string, activation interpreter.Activation, remainingBudget int64) (string, int64, *cel.Error) {
	evalResult, evalDetails, err := expr.ContextEval(ctx, activation)
	if evalDetails == nil {
		return "", -1, &cel.Error{
			Type:   cel.ErrorTypeInternal,
			Detail: fmt.Sprintf("runtime cost could not be calculated for messageExpression: %q", exprSrc),
		}
	}
	rtCost := evalDetails.ActualCost()
	if rtCost == nil {
		return "", -1, &cel.Error{
			Type:   cel.ErrorTypeInternal,
			Detail: fmt.Sprintf("runtime cost could not be calculated for messageExpression: %q", exprSrc),
		}
	} else if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget {
		return "", -1, &cel.Error{
			Type:   cel.ErrorTypeInvalid,
			Detail: "messageExpression evaluation failed due to running out of cost budget, no further validation rules will be run",
		}
	}
	if err != nil {
		if strings.HasPrefix(err.Error(), "operation cancelled: actual cost limit exceeded") {
			return "", -1, &cel.Error{
				Type:   cel.ErrorTypeInvalid,
				Detail: fmt.Sprintf("no further validation rules will be run due to call cost exceeds limit for messageExpression: %q", exprSrc),
			}
		}
		return "", remainingBudget - int64(*rtCost), &cel.Error{
			Detail: fmt.Sprintf("messageExpression evaluation failed due to: %v", err.Error()),
		}
	}
	messageStr, ok := evalResult.Value().(string)
	if !ok {
		return "", remainingBudget - int64(*rtCost), &cel.Error{
			Detail: "messageExpression failed to convert to string",
		}
	}
	trimmedMsgStr := strings.TrimSpace(messageStr)
	if len(trimmedMsgStr) > celconfig.MaxEvaluatedMessageExpressionSizeBytes {
		return "", remainingBudget - int64(*rtCost), &cel.Error{
			Detail: fmt.Sprintf("messageExpression beyond allowable length of %d", celconfig.MaxEvaluatedMessageExpressionSizeBytes),
		}
	} else if hasNewlines(trimmedMsgStr) {
		return "", remainingBudget - int64(*rtCost), &cel.Error{
			Detail: "messageExpression should not contain line breaks",
		}
	} else if len(trimmedMsgStr) == 0 {
		return "", remainingBudget - int64(*rtCost), &cel.Error{
			Detail: "messageExpression should evaluate to a non-empty string",
		}
	}
	return trimmedMsgStr, remainingBudget - int64(*rtCost), nil
}


hasNewlines

func hasNewlines(s string) bool {
	return newlineMatcher.MatchString(s)
}


ruleMessageOrDefault

func ruleMessageOrDefault(rule apiextensions.ValidationRule) string {
	if len(rule.Message) == 0 {
		return fmt.Sprintf("failed rule: %s", ruleErrorString(rule))
	} else {
		return strings.TrimSpace(rule.Message)
	}
}


ruleErrorString

func ruleErrorString(rule apiextensions.ValidationRule) string {
	if len(rule.Message) > 0 {
		return strings.TrimSpace(rule.Message)
	}
	return strings.TrimSpace(rule.Rule)
}


validationActivationWithOldSelf

func validationActivationWithOldSelf(sts *schema.Structural, obj, oldObj interface{}) interpreter.Activation {
	va := &validationActivation{
		self: UnstructuredToVal(obj, sts),
	}
	if oldObj != nil {
		va.oldSelf = UnstructuredToVal(oldObj, sts) // +k8s:verify-mutation:reason=clone
		va.hasOldSelf = true                        // +k8s:verify-mutation:reason=clone
	}
	return va
}


validationActivationWithoutOldSelf

func validationActivationWithoutOldSelf(sts *schema.Structural, obj, _ interface{}) interpreter.Activation {
	return &validationActivation{
		self: UnstructuredToVal(obj, sts),
	}
}


ResolveName

func (a *validationActivation) ResolveName(name string) (interface{}, bool) {
	switch name {
	case ScopedVarName:
		return a.self, true
	case OldScopedVarName:
		return a.oldSelf, a.hasOldSelf
	default:
		return nil, false
	}
}


Parent

func (a *validationActivation) Parent() interpreter.Activation {
	return nil
}


validateMap

func (s *Validator) validateMap(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj map[string]interface{}, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
	remainingBudget = costBudget
	if remainingBudget < 0 {
		return errs, remainingBudget
	}
	if s == nil || obj == nil {
		return nil, remainingBudget
	}

	correlatable := MapIsCorrelatable(sts.XMapType)

	if s.AdditionalProperties != nil && sts.AdditionalProperties != nil && sts.AdditionalProperties.Structural != nil {
		for k, v := range obj {
			var oldV interface{}
			if correlatable {
				oldV = oldObj[k] // +k8s:verify-mutation:reason=clone
			}

			var err field.ErrorList
			err, remainingBudget = s.AdditionalProperties.Validate(ctx, fldPath.Key(k), sts.AdditionalProperties.Structural, v, oldV, remainingBudget)
			errs = append(errs, err...)
			if remainingBudget < 0 {
				return errs, remainingBudget
			}
		}
	}
	if s.Properties != nil && sts.Properties != nil {
		for k, v := range obj {
			stsProp, stsOk := sts.Properties[k]
			sub, ok := s.Properties[k]
			if ok && stsOk {
				var oldV interface{}
				if correlatable {
					oldV = oldObj[k] // +k8s:verify-mutation:reason=clone
				}

				var err field.ErrorList
				err, remainingBudget = sub.Validate(ctx, fldPath.Child(k), &stsProp, v, oldV, remainingBudget)
				errs = append(errs, err...)
				if remainingBudget < 0 {
					return errs, remainingBudget
				}
			}
		}
	}

	return errs, remainingBudget
}


validateArray

func (s *Validator) validateArray(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj []interface{}, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
	remainingBudget = costBudget
	if remainingBudget < 0 {
		return errs, remainingBudget
	}

	if s.Items != nil && sts.Items != nil {
		// only map-type lists support self-oldSelf correlation for cel rules. if this isn't a
		// map-type list, then makeMapList returns an implementation that always returns nil
		correlatableOldItems := makeMapList(sts, oldObj)
		for i := range obj {
			var err field.ErrorList
			err, remainingBudget = s.Items.Validate(ctx, fldPath.Index(i), sts.Items, obj[i], correlatableOldItems.Get(obj[i]), remainingBudget)
			errs = append(errs, err...)
			if remainingBudget < 0 {
				return errs, remainingBudget
			}
		}
	}

	return errs, remainingBudget
}


MapIsCorrelatable

// MapIsCorrelatable returns true if the mapType can be used to correlate the data elements of a map after an update
// with the data elements of the map from before the updated.
func MapIsCorrelatable(mapType *string) bool {
	// if a third map type is introduced, assume it's not correlatable. granular is the default if unspecified.
	return mapType == nil || *mapType == "granular" || *mapType == "atomic"
}


hasXValidations

func hasXValidations(s *schema.Structural) bool {
	if s == nil {
		return false
	}
	if len(s.XValidations) > 0 {
		return true
	}
	if hasXValidations(s.Items) {
		return true
	}
	if s.AdditionalProperties != nil && hasXValidations(s.AdditionalProperties.Structural) {
		return true
	}
	if s.Properties != nil {
		for _, prop := range s.Properties {
			if hasXValidations(&prop) {
				return true
			}
		}
	}
	return false
}


UnstructuredToVal

// 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 *structuralschema.Structural) ref.Val {
	return celopenapi.UnstructuredToVal(unstructured, &model.Structural{Structural: schema})
}


validateStructuralCompleteness

// validateStructuralCompleteness checks that every specified field or array in s is also specified
// outside of value validation.
func validateStructuralCompleteness(s *Structural, fldPath *field.Path) field.ErrorList {
	if s == nil {
		return nil
	}

	return validateValueValidationCompleteness(s.ValueValidation, s, fldPath, fldPath)
}


validateValueValidationCompleteness

func validateValueValidationCompleteness(v *ValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
	if v == nil {
		return nil
	}
	if s == nil {
		return field.ErrorList{field.Required(sPath, fmt.Sprintf("because it is defined in %s", vPath.String()))}
	}

	allErrs := field.ErrorList{}

	allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Not, s, sPath, vPath.Child("not"))...)
	for i := range v.AllOf {
		allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AllOf[i], s, sPath, vPath.Child("allOf").Index(i))...)
	}
	for i := range v.AnyOf {
		allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AnyOf[i], s, sPath, vPath.Child("anyOf").Index(i))...)
	}
	for i := range v.OneOf {
		allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.OneOf[i], s, sPath, vPath.Child("oneOf").Index(i))...)
	}

	return allErrs
}


func validateNestedValueValidationCompleteness(v *NestedValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
	if v == nil {
		return nil
	}
	if s == nil {
		return field.ErrorList{field.Required(sPath, fmt.Sprintf("because it is defined in %s", vPath.String()))}
	}

	allErrs := field.ErrorList{}

	allErrs = append(allErrs, validateValueValidationCompleteness(&v.ValueValidation, s, sPath, vPath)...)
	allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Items, s.Items, sPath.Child("items"), vPath.Child("items"))...)
	for k, vFld := range v.Properties {
		if sFld, ok := s.Properties[k]; !ok {
			allErrs = append(allErrs, field.Required(sPath.Child("properties").Key(k), fmt.Sprintf("because it is defined in %s", vPath.Child("properties").Key(k))))
		} else {
			allErrs = append(allErrs, validateNestedValueValidationCompleteness(&vFld, &sFld, sPath.Child("properties").Key(k), vPath.Child("properties").Key(k))...)
		}
	}

	// don't check additionalProperties as this is not allowed (and checked during validation)

	return allErrs
}


NewStructural

// NewStructural converts an OpenAPI v3 schema into a structural schema. A pre-validated JSONSchemaProps will
// not fail on NewStructural. This means that we require that:
//
// - items is not an array of schemas
// - the following fields are not set:
//   - id
//   - schema
//   - $ref
//   - patternProperties
//   - dependencies
//   - additionalItems
//   - definitions.
//
// The follow fields are not preserved:
// - externalDocs
// - example.
func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
	if s == nil {
		return nil, nil
	}

	if err := validateUnsupportedFields(s); err != nil {
		return nil, err
	}

	vv, err := newValueValidation(s)
	if err != nil {
		return nil, err
	}

	g, err := newGenerics(s)
	if err != nil {
		return nil, err
	}

	x, err := newExtensions(s)
	if err != nil {
		return nil, err
	}

	ss := &Structural{
		Generic:         *g,
		Extensions:      *x,
		ValueValidation: vv,
	}

	if s.Items != nil {
		if len(s.Items.JSONSchemas) > 0 {
			// we validate that it is not an array
			return nil, fmt.Errorf("OpenAPIV3Schema 'items' must be a schema, but is an array")
		}
		item, err := NewStructural(s.Items.Schema)
		if err != nil {
			return nil, err
		}
		ss.Items = item
	}

	if len(s.Properties) > 0 {
		ss.Properties = make(map[string]Structural, len(s.Properties))
		for k, x := range s.Properties {
			fld, err := NewStructural(&x)
			if err != nil {
				return nil, err
			}
			ss.Properties[k] = *fld
		}
	}

	return ss, nil
}


newGenerics

func newGenerics(s *apiextensions.JSONSchemaProps) (*Generic, error) {
	if s == nil {
		return nil, nil
	}
	g := &Generic{
		Type:        s.Type,
		Description: s.Description,
		Title:       s.Title,
		Nullable:    s.Nullable,
	}
	if s.Default != nil {
		g.Default = JSON{interface{}(*s.Default)}
	}

	if s.AdditionalProperties != nil {
		if s.AdditionalProperties.Schema != nil {
			ss, err := NewStructural(s.AdditionalProperties.Schema)
			if err != nil {
				return nil, err
			}
			g.AdditionalProperties = &StructuralOrBool{Structural: ss, Bool: true}
		} else {
			g.AdditionalProperties = &StructuralOrBool{Bool: s.AdditionalProperties.Allows}
		}
	}

	return g, nil
}


newValueValidation

func newValueValidation(s *apiextensions.JSONSchemaProps) (*ValueValidation, error) {
	if s == nil {
		return nil, nil
	}
	not, err := newNestedValueValidation(s.Not)
	if err != nil {
		return nil, err
	}
	v := &ValueValidation{
		Format:           s.Format,
		Maximum:          s.Maximum,
		ExclusiveMaximum: s.ExclusiveMaximum,
		Minimum:          s.Minimum,
		ExclusiveMinimum: s.ExclusiveMinimum,
		MaxLength:        s.MaxLength,
		MinLength:        s.MinLength,
		Pattern:          s.Pattern,
		MaxItems:         s.MaxItems,
		MinItems:         s.MinItems,
		UniqueItems:      s.UniqueItems,
		MultipleOf:       s.MultipleOf,
		MaxProperties:    s.MaxProperties,
		MinProperties:    s.MinProperties,
		Required:         s.Required,
		Not:              not,
	}

	for _, e := range s.Enum {
		v.Enum = append(v.Enum, JSON{e})
	}

	for _, x := range s.AllOf {
		clause, err := newNestedValueValidation(&x)
		if err != nil {
			return nil, err
		}
		v.AllOf = append(v.AllOf, *clause)
	}

	for _, x := range s.AnyOf {
		clause, err := newNestedValueValidation(&x)
		if err != nil {
			return nil, err
		}
		v.AnyOf = append(v.AnyOf, *clause)
	}

	for _, x := range s.OneOf {
		clause, err := newNestedValueValidation(&x)
		if err != nil {
			return nil, err
		}
		v.OneOf = append(v.OneOf, *clause)
	}

	return v, nil
}


newNestedValueValidation

func newNestedValueValidation(s *apiextensions.JSONSchemaProps) (*NestedValueValidation, error) {
	if s == nil {
		return nil, nil
	}

	if err := validateUnsupportedFields(s); err != nil {
		return nil, err
	}

	vv, err := newValueValidation(s)
	if err != nil {
		return nil, err
	}

	g, err := newGenerics(s)
	if err != nil {
		return nil, err
	}

	x, err := newExtensions(s)
	if err != nil {
		return nil, err
	}

	v := &NestedValueValidation{
		ValueValidation:     *vv,
		ForbiddenGenerics:   *g,
		ForbiddenExtensions: *x,
	}

	if s.Items != nil {
		if len(s.Items.JSONSchemas) > 0 {
			// we validate that it is not an array
			return nil, fmt.Errorf("OpenAPIV3Schema 'items' must be a schema, but is an array")
		}
		nvv, err := newNestedValueValidation(s.Items.Schema)
		if err != nil {
			return nil, err
		}
		v.Items = nvv
	}
	if s.Properties != nil {
		v.Properties = make(map[string]NestedValueValidation, len(s.Properties))
		for k, x := range s.Properties {
			nvv, err := newNestedValueValidation(&x)
			if err != nil {
				return nil, err
			}
			v.Properties[k] = *nvv
		}
	}

	return v, nil
}


newExtensions

func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
	if s == nil {
		return nil, nil
	}

	ret := &Extensions{
		XEmbeddedResource: s.XEmbeddedResource,
		XIntOrString:      s.XIntOrString,
		XListMapKeys:      s.XListMapKeys,
		XListType:         s.XListType,
		XMapType:          s.XMapType,
	}
	if err := apiextensionsv1.Convert_apiextensions_ValidationRules_To_v1_ValidationRules(&s.XValidations, &ret.XValidations, nil); err != nil {
		return nil, err
	}

	if s.XPreserveUnknownFields != nil {
		if !*s.XPreserveUnknownFields {
			return nil, fmt.Errorf("internal error: 'x-kubernetes-preserve-unknown-fields' must be true or undefined")
		}
		ret.XPreserveUnknownFields = true
	}

	return ret, nil
}


validateUnsupportedFields

// validateUnsupportedFields checks that those fields rejected by validation are actually unset.
func validateUnsupportedFields(s *apiextensions.JSONSchemaProps) error {
	if len(s.ID) > 0 {
		return fmt.Errorf("OpenAPIV3Schema 'id' is not supported")
	}
	if len(s.Schema) > 0 {
		return fmt.Errorf("OpenAPIV3Schema 'schema' is not supported")
	}
	if s.Ref != nil && len(*s.Ref) > 0 {
		return fmt.Errorf("OpenAPIV3Schema '$ref' is not supported")
	}
	if len(s.PatternProperties) > 0 {
		return fmt.Errorf("OpenAPIV3Schema 'patternProperties' is not supported")
	}
	if len(s.Dependencies) > 0 {
		return fmt.Errorf("OpenAPIV3Schema 'dependencies' is not supported")
	}
	if s.AdditionalItems != nil {
		return fmt.Errorf("OpenAPIV3Schema 'additionalItems' is not supported")
	}
	if len(s.Definitions) > 0 {
		return fmt.Errorf("OpenAPIV3Schema 'definitions' is not supported")
	}

	return nil
}


isNonNullableNull

// isNonNullalbeNull returns true if the item is nil AND it's nullable
func isNonNullableNull(x interface{}, s *structuralschema.Structural) bool {
	return x == nil && s != nil && s.Generic.Nullable == false
}


Default

// Default does defaulting of x depending on default values in s.
// Default values from s are deep-copied.
//
// PruneNonNullableNullsWithoutDefaults has left the non-nullable nulls
// that have a default here.
func Default(x interface{}, s *structuralschema.Structural) {
	if s == nil {
		return
	}

	switch x := x.(type) {
	case map[string]interface{}:
		for k, prop := range s.Properties {
			if prop.Default.Object == nil {
				continue
			}
			if _, found := x[k]; !found || isNonNullableNull(x[k], &prop) {
				x[k] = runtime.DeepCopyJSONValue(prop.Default.Object)
			}
		}
		for k := range x {
			if prop, found := s.Properties[k]; found {
				Default(x[k], &prop)
			} else if s.AdditionalProperties != nil {
				if isNonNullableNull(x[k], s.AdditionalProperties.Structural) {
					x[k] = runtime.DeepCopyJSONValue(s.AdditionalProperties.Structural.Default.Object)
				}
				Default(x[k], s.AdditionalProperties.Structural)
			}
		}
	case []interface{}:
		for i := range x {
			if isNonNullableNull(x[i], s.Items) {
				x[i] = runtime.DeepCopyJSONValue(s.Items.Default.Object)
			}
			Default(x[i], s.Items)
		}
	default:
		// scalars, do nothing
	}
}


PruneDefaults

// PruneDefaults prunes default values according to the schema and according to
// the ObjectMeta definition of the running server. It mutates the passed schema.
func PruneDefaults(s *structuralschema.Structural) error {
	p := pruner{s}
	_, err := p.pruneDefaults(s, NewRootObjectFunc())
	return err
}


pruneDefaults

func (p *pruner) pruneDefaults(s *structuralschema.Structural, f SurroundingObjectFunc) (changed bool, err error) {
	if s == nil {
		return false, nil
	}

	if s.Default.Object != nil {
		orig := runtime.DeepCopyJSONValue(s.Default.Object)

		obj, acc, err := f(s.Default.Object)
		if err != nil {
			return false, fmt.Errorf("failed to prune default value: %v", err)
		}
		if err := structuralobjectmeta.Coerce(nil, obj, p.rootSchema, true, true); err != nil {
			return false, fmt.Errorf("failed to prune default value: %v", err)
		}
		pruning.Prune(obj, p.rootSchema, true)
		s.Default.Object, _, err = acc(obj)
		if err != nil {
			return false, fmt.Errorf("failed to prune default value: %v", err)
		}

		changed = changed || !reflect.DeepEqual(orig, s.Default.Object)
	}

	if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
		c, err := p.pruneDefaults(s.AdditionalProperties.Structural, f.Child("*"))
		if err != nil {
			return false, err
		}
		changed = changed || c
	}
	if s.Items != nil {
		c, err := p.pruneDefaults(s.Items, f.Index())
		if err != nil {
			return false, err
		}
		changed = changed || c
	}
	for k, subSchema := range s.Properties {
		c, err := p.pruneDefaults(&subSchema, f.Child(k))
		if err != nil {
			return false, err
		}
		if c {
			s.Properties[k] = subSchema
			changed = true
		}
	}

	return changed, nil
}


isNonNullableNonDefaultableNull

func isNonNullableNonDefaultableNull(x interface{}, s *structuralschema.Structural) bool {
	return x == nil && s != nil && s.Generic.Nullable == false && s.Default.Object == nil
}


getSchemaForField

func getSchemaForField(field string, s *structuralschema.Structural) *structuralschema.Structural {
	if s == nil {
		return nil
	}
	schema, ok := s.Properties[field]
	if ok {
		return &schema
	}
	if s.AdditionalProperties != nil {
		return s.AdditionalProperties.Structural
	}
	return nil
}


PruneNonNullableNullsWithoutDefaults

// PruneNonNullableNullsWithoutDefaults removes non-nullable
// non-defaultable null values from object.
//
// Non-nullable nulls that have a default are left alone here and will
// be defaulted later.
func PruneNonNullableNullsWithoutDefaults(x interface{}, s *structuralschema.Structural) {
	switch x := x.(type) {
	case map[string]interface{}:
		for k, v := range x {
			schema := getSchemaForField(k, s)
			if isNonNullableNonDefaultableNull(v, schema) {
				delete(x, k)
			} else {
				PruneNonNullableNullsWithoutDefaults(v, schema)
			}
		}
	case []interface{}:
		var schema *structuralschema.Structural
		if s != nil {
			schema = s.Items
		}
		for i := range x {
			PruneNonNullableNullsWithoutDefaults(x[i], schema)
		}
	default:
		// scalars, do nothing
	}
}


NewRootObjectFunc

// NewRootObjectFunc returns the identity function. The passed focus value
// must be an object.
func NewRootObjectFunc() SurroundingObjectFunc {
	return func(x interface{}) (map[string]interface{}, AccessorFunc, error) {
		obj, ok := x.(map[string]interface{})
		if !ok {
			return nil, nil, fmt.Errorf("object root default value must be of object type")
		}
		return obj, func(root map[string]interface{}) (interface{}, bool, error) {
			return root, true, nil
		}, nil
	}
}


WithTypeMeta

// WithTypeMeta returns a closure with the TypeMeta fields set if they are defined.
// This mutates f(x).
func (f SurroundingObjectFunc) WithTypeMeta(meta metav1.TypeMeta) SurroundingObjectFunc {
	return func(x interface{}) (map[string]interface{}, AccessorFunc, error) {
		obj, acc, err := f(x)
		if err != nil {
			return nil, nil, err
		}
		if obj == nil {
			obj = map[string]interface{}{}
		}
		if _, found := obj["kind"]; !found {
			obj["kind"] = meta.Kind
		}
		if _, found := obj["apiVersion"]; !found {
			obj["apiVersion"] = meta.APIVersion
		}
		return obj, acc, err
	}
}


Child

// Child returns a function x => f({k: x}) and the corresponding accessor.
func (f SurroundingObjectFunc) Child(k string) SurroundingObjectFunc {
	return func(x interface{}) (map[string]interface{}, AccessorFunc, error) {
		obj, acc, err := f(map[string]interface{}{k: x})
		if err != nil {
			return nil, nil, err
		}
		return obj, func(obj map[string]interface{}) (interface{}, bool, error) {
			x, found, err := acc(obj)
			if err != nil {
				return nil, false, fmt.Errorf(".%s%v", k, err)
			}
			if !found {
				return nil, false, nil
			}
			if x, ok := x.(map[string]interface{}); !ok {
				return nil, false, fmt.Errorf(".%s must be of object type", k)
			} else if v, found := x[k]; !found {
				return nil, false, nil
			} else {
				return v, true, nil
			}
		}, err
	}
}


Index

// Index returns a function x => f([x]) and the corresponding accessor.
func (f SurroundingObjectFunc) Index() SurroundingObjectFunc {
	return func(focus interface{}) (map[string]interface{}, AccessorFunc, error) {
		obj, acc, err := f([]interface{}{focus})
		if err != nil {
			return nil, nil, err
		}
		return obj, func(obj map[string]interface{}) (interface{}, bool, error) {
			x, found, err := acc(obj)
			if err != nil {
				return nil, false, fmt.Errorf("[]%v", err)
			}
			if !found {
				return nil, false, nil
			}
			if x, ok := x.([]interface{}); !ok {
				return nil, false, fmt.Errorf("[] must be of array type")
			} else if len(x) == 0 {
				return nil, false, nil
			} else {
				return x[0], true, nil
			}
		}, err
	}
}


ValidateDefaults

// ValidateDefaults checks that default values validate and are properly pruned.
// context is passed for supporting context cancellation during cel validation
func ValidateDefaults(ctx context.Context, pth *field.Path, s *structuralschema.Structural, isResourceRoot, requirePrunedDefaults bool) (field.ErrorList, error) {
	f := NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"})

	if isResourceRoot {
		if s == nil {
			s = &structuralschema.Structural{}
		}
		if !s.XEmbeddedResource {
			clone := *s
			clone.XEmbeddedResource = true
			s = &clone
		}
	}

	allErr, error, _ := validate(ctx, pth, s, s, f, false, requirePrunedDefaults, celconfig.RuntimeCELCostBudget)
	return allErr, error
}


validate

// validate is the recursive step func for the validation. insideMeta is true if s specifies
// TypeMeta or ObjectMeta. The SurroundingObjectFunc f is used to validate defaults of
// TypeMeta or ObjectMeta fields.
// context is passed for supporting context cancellation during cel validation
func validate(ctx context.Context, pth *field.Path, s *structuralschema.Structural, rootSchema *structuralschema.Structural, f SurroundingObjectFunc, insideMeta, requirePrunedDefaults bool, costBudget int64) (allErrs field.ErrorList, error error, remainingCost int64) {
	remainingCost = costBudget
	if s == nil {
		return nil, nil, remainingCost
	}

	if s.XEmbeddedResource {
		insideMeta = false
		f = NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"})
		rootSchema = s
	}

	isResourceRoot := s == rootSchema

	if s.Default.Object != nil {
		validator := kubeopenapivalidate.NewSchemaValidator(s.ToKubeOpenAPI(), nil, "", strfmt.Default)

		if insideMeta {
			obj, _, err := f(runtime.DeepCopyJSONValue(s.Default.Object))
			if err != nil {
				// this should never happen. f(s.Default.Object) only gives an error if f is the
				// root object func, but the default value is not a map. But then we wouldn't be
				// in this case.
				return nil, fmt.Errorf("failed to validate default value inside metadata: %v", err), remainingCost
			}

			// check ObjectMeta/TypeMeta and everything else
			if err := schemaobjectmeta.Coerce(nil, obj, rootSchema, true, false); err != nil {
				allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", err)))
			} else if errs := schemaobjectmeta.Validate(nil, obj, rootSchema, true); len(errs) > 0 {
				allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", errs.ToAggregate())))
			} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
				allErrs = append(allErrs, errs...)
			} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
				celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
				remainingCost = rmCost
				allErrs = append(allErrs, celErrs...)
				if remainingCost < 0 {
					return allErrs, nil, remainingCost
				}
			}
		} else {
			// check whether default is pruned
			if requirePrunedDefaults {
				pruned := runtime.DeepCopyJSONValue(s.Default.Object)
				pruning.Prune(pruned, s, s.XEmbeddedResource)
				if !reflect.DeepEqual(pruned, s.Default.Object) {
					allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, "must not have unknown fields"))
				}
			}

			// check ObjectMeta/TypeMeta and everything else
			if err := schemaobjectmeta.Coerce(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource, false); err != nil {
				allErrs = append(allErrs, err)
			} else if errs := schemaobjectmeta.Validate(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource); len(errs) > 0 {
				allErrs = append(allErrs, errs...)
			} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
				allErrs = append(allErrs, errs...)
			} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
				celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
				remainingCost = rmCost
				allErrs = append(allErrs, celErrs...)
				if remainingCost < 0 {
					return allErrs, nil, remainingCost
				}
			}
		}
	}

	// do not follow additionalProperties because defaults are forbidden there

	if s.Items != nil {
		errs, err, rCost := validate(ctx, pth.Child("items"), s.Items, rootSchema, f.Index(), insideMeta, requirePrunedDefaults, remainingCost)
		remainingCost = rCost
		allErrs = append(allErrs, errs...)
		if err != nil {
			return nil, err, remainingCost
		}
		if remainingCost < 0 {
			return allErrs, nil, remainingCost
		}
	}

	for k, subSchema := range s.Properties {
		subInsideMeta := insideMeta
		if s.XEmbeddedResource && (k == "metadata" || k == "apiVersion" || k == "kind") {
			subInsideMeta = true
		}
		errs, err, rCost := validate(ctx, pth.Child("properties").Key(k), &subSchema, rootSchema, f.Child(k), subInsideMeta, requirePrunedDefaults, remainingCost)
		remainingCost = rCost
		allErrs = append(allErrs, errs...)
		if err != nil {
			return nil, err, remainingCost
		}
		if remainingCost < 0 {
			return allErrs, nil, remainingCost
		}
	}

	return allErrs, nil, remainingCost
}


ToKubeOpenAPI

// ToKubeOpenAPI converts a structural schema to go-openapi schema. It is faithful and roundtrippable.
func (s *Structural) ToKubeOpenAPI() *spec.Schema {
	if s == nil {
		return nil
	}

	ret := &spec.Schema{}

	if s.Items != nil {
		ret.Items = &spec.SchemaOrArray{Schema: s.Items.ToKubeOpenAPI()}
	}
	if s.Properties != nil {
		ret.Properties = make(map[string]spec.Schema, len(s.Properties))
		for k, v := range s.Properties {
			ret.Properties[k] = *v.ToKubeOpenAPI()
		}
	}
	s.Generic.toKubeOpenAPI(ret)
	s.Extensions.toKubeOpenAPI(ret)
	s.ValueValidation.toKubeOpenAPI(ret)

	return ret
}


toKubeOpenAPI

func (g *Generic) toKubeOpenAPI(ret *spec.Schema) {
	if g == nil {
		return
	}

	if len(g.Type) != 0 {
		ret.Type = spec.StringOrArray{g.Type}
	}
	ret.Nullable = g.Nullable
	if g.AdditionalProperties != nil {
		ret.AdditionalProperties = &spec.SchemaOrBool{
			Allows: g.AdditionalProperties.Bool,
			Schema: g.AdditionalProperties.Structural.ToKubeOpenAPI(),
		}
	}
	ret.Description = g.Description
	ret.Title = g.Title
	ret.Default = g.Default.Object
}


func (x *Extensions) toKubeOpenAPI(ret *spec.Schema) {
	if x == nil {
		return
	}

	if x.XPreserveUnknownFields {
		ret.VendorExtensible.AddExtension("x-kubernetes-preserve-unknown-fields", true)
	}
	if x.XEmbeddedResource {
		ret.VendorExtensible.AddExtension("x-kubernetes-embedded-resource", true)
	}
	if x.XIntOrString {
		ret.VendorExtensible.AddExtension("x-kubernetes-int-or-string", true)
	}
	if len(x.XListMapKeys) > 0 {
		ret.VendorExtensible.AddExtension("x-kubernetes-list-map-keys", x.XListMapKeys)
	}
	if x.XListType != nil {
		ret.VendorExtensible.AddExtension("x-kubernetes-list-type", *x.XListType)
	}
	if x.XMapType != nil {
		ret.VendorExtensible.AddExtension("x-kubernetes-map-type", *x.XMapType)
	}
	if len(x.XValidations) > 0 {
		ret.VendorExtensible.AddExtension("x-kubernetes-validations", x.XValidations)
	}
}


func (v *ValueValidation) toKubeOpenAPI(ret *spec.Schema) {
	if v == nil {
		return
	}

	ret.Format = v.Format
	ret.Maximum = v.Maximum
	ret.ExclusiveMaximum = v.ExclusiveMaximum
	ret.Minimum = v.Minimum
	ret.ExclusiveMinimum = v.ExclusiveMinimum
	ret.MaxLength = v.MaxLength
	ret.MinLength = v.MinLength
	ret.Pattern = v.Pattern
	ret.MaxItems = v.MaxItems
	ret.MinItems = v.MinItems
	ret.UniqueItems = v.UniqueItems
	ret.MultipleOf = v.MultipleOf
	if v.Enum != nil {
		ret.Enum = make([]interface{}, 0, len(v.Enum))
		for i := range v.Enum {
			ret.Enum = append(ret.Enum, v.Enum[i].Object)
		}
	}
	ret.MaxProperties = v.MaxProperties
	ret.MinProperties = v.MinProperties
	ret.Required = v.Required
	for i := range v.AllOf {
		ret.AllOf = append(ret.AllOf, *v.AllOf[i].toKubeOpenAPI())
	}
	for i := range v.AnyOf {
		ret.AnyOf = append(ret.AnyOf, *v.AnyOf[i].toKubeOpenAPI())
	}
	for i := range v.OneOf {
		ret.OneOf = append(ret.OneOf, *v.OneOf[i].toKubeOpenAPI())
	}
	ret.Not = v.Not.toKubeOpenAPI()
}


func (vv *NestedValueValidation) toKubeOpenAPI() *spec.Schema {
	if vv == nil {
		return nil
	}

	ret := &spec.Schema{}

	vv.ValueValidation.toKubeOpenAPI(ret)
	if vv.Items != nil {
		ret.Items = &spec.SchemaOrArray{Schema: vv.Items.toKubeOpenAPI()}
	}
	if vv.Properties != nil {
		ret.Properties = make(map[string]spec.Schema, len(vv.Properties))
		for k, v := range vv.Properties {
			ret.Properties[k] = *v.toKubeOpenAPI()
		}
	}
	vv.ForbiddenGenerics.toKubeOpenAPI(ret)   // normally empty. Exception: int-or-string
	vv.ForbiddenExtensions.toKubeOpenAPI(ret) // shouldn't do anything

	return ret
}


ValidateListSetsAndMaps

// ValidateListSetsAndMaps validates that arrays with x-kubernetes-list-type "map" and "set" fulfill the uniqueness
// invariants for the keys (maps) and whole elements (sets).
func ValidateListSetsAndMaps(fldPath *field.Path, s *schema.Structural, obj map[string]interface{}) field.ErrorList {
	if s == nil || obj == nil {
		return nil
	}

	var errs field.ErrorList

	if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
		for k, v := range obj {
			errs = append(errs, validationListSetAndMaps(fldPath.Key(k), s.AdditionalProperties.Structural, v)...)
		}
	}
	if s.Properties != nil {
		for k, v := range obj {
			if sub, ok := s.Properties[k]; ok {
				errs = append(errs, validationListSetAndMaps(fldPath.Child(k), &sub, v)...)
			}
		}
	}

	return errs
}


validationListSetAndMaps

func validationListSetAndMaps(fldPath *field.Path, s *schema.Structural, obj interface{}) field.ErrorList {
	switch obj := obj.(type) {
	case []interface{}:
		return validateListSetsAndMapsArray(fldPath, s, obj)
	case map[string]interface{}:
		return ValidateListSetsAndMaps(fldPath, s, obj)
	}
	return nil
}


validateListSetsAndMapsArray

func validateListSetsAndMapsArray(fldPath *field.Path, s *schema.Structural, obj []interface{}) field.ErrorList {
	var errs field.ErrorList

	if s.XListType != nil {
		switch *s.XListType {
		case "set":
			nonUnique, err := validateListSet(fldPath, obj)
			if err != nil {
				errs = append(errs, err)
			} else {
				for _, i := range nonUnique {
					errs = append(errs, field.Duplicate(fldPath.Index(i), obj[i]))
				}
			}
		case "map":
			errs = append(errs, validateListMap(fldPath, s, obj)...)
		}
		// if a case is ever added here then one should also be added to pkg/apiserver/schema/cel/values.go
	}

	if s.Items != nil {
		for i := range obj {
			errs = append(errs, validationListSetAndMaps(fldPath.Index(i), s.Items, obj[i])...)
		}
	}

	return errs
}


validateListSet

// validateListSet validated uniqueness of unstructured objects (scalar and compound) and
// returns the first non-unique appearance of items.
//
// As a special case to distinguish undefined key and null values, we allow unspecifiedKeyValue and nullObjectValue
// which are both handled like scalars with correct comparison by Golang.
func validateListSet(fldPath *field.Path, obj []interface{}) ([]int, *field.Error) {
	if len(obj) <= 1 {
		return nil, nil
	}

	seenScalars := make(map[interface{}]int, len(obj))
	seenCompounds := make(map[string]int, len(obj))
	var nonUniqueIndices []int
	for i, x := range obj {
		switch x.(type) {
		case map[string]interface{}, []interface{}:
			bs, err := json.Marshal(x)
			if err != nil {
				return nil, field.Invalid(fldPath.Index(i), x, "internal error")
			}
			s := string(bs)
			if times, seen := seenCompounds[s]; !seen {
				seenCompounds[s] = 1
			} else {
				seenCompounds[s]++
				if times == 1 {
					nonUniqueIndices = append(nonUniqueIndices, i)
				}
			}
		default:
			if times, seen := seenScalars[x]; !seen {
				seenScalars[x] = 1
			} else {
				seenScalars[x]++
				if times == 1 {
					nonUniqueIndices = append(nonUniqueIndices, i)
				}
			}
		}
	}

	return nonUniqueIndices, nil
}


validateListMap

func validateListMap(fldPath *field.Path, s *schema.Structural, obj []interface{}) field.ErrorList {
	// only allow nil and objects
	for i, x := range obj {
		if _, ok := x.(map[string]interface{}); x != nil && !ok {
			return field.ErrorList{field.Invalid(fldPath.Index(i), x, "must be an object for an array of list-type map")}
		}
	}

	if len(obj) <= 1 {
		return nil
	}

	// optimize simple case of one key
	if len(s.XListMapKeys) == 1 {
		type unspecifiedKeyValue struct{}

		keyField := s.XListMapKeys[0]
		keys := make([]interface{}, 0, len(obj))
		for _, x := range obj {
			if x == nil {
				keys = append(keys, unspecifiedKeyValue{}) // nil object means unspecified key
				continue
			}

			x := x.(map[string]interface{})

			// undefined key?
			key, ok := x[keyField]
			if !ok {
				keys = append(keys, unspecifiedKeyValue{})
				continue
			}

			keys = append(keys, key)
		}

		nonUnique, err := validateListSet(fldPath, keys)
		if err != nil {
			return field.ErrorList{err}
		}

		var errs field.ErrorList
		for _, i := range nonUnique {
			switch keys[i] {
			case unspecifiedKeyValue{}:
				errs = append(errs, field.Duplicate(fldPath.Index(i), map[string]interface{}{}))
			default:
				errs = append(errs, field.Duplicate(fldPath.Index(i), map[string]interface{}{keyField: keys[i]}))
			}
		}

		return errs
	}

	// multiple key fields
	keys := make([]interface{}, 0, len(obj))
	for _, x := range obj {
		key := map[string]interface{}{}
		if x == nil {
			keys = append(keys, key)
			continue
		}

		x := x.(map[string]interface{})

		for _, keyField := range s.XListMapKeys {
			if k, ok := x[keyField]; ok {
				key[keyField] = k
			}
		}

		keys = append(keys, key)
	}

	nonUnique, err := validateListSet(fldPath, keys)
	if err != nil {
		return field.ErrorList{err}
	}

	var errs field.ErrorList
	for _, i := range nonUnique {
		errs = append(errs, field.Duplicate(fldPath.Index(i), keys[i]))
	}

	return errs
}


CoerceWithOptions

// Coerce checks types of embedded ObjectMeta and TypeMeta and prunes unknown fields inside the former.
// It does coerce ObjectMeta and TypeMeta at the root if isResourceRoot is true.
// If opts.ReturnUnknownFieldPaths is true, it will return the paths of any fields that are not a part of the
// schema that are dropped when unmarshaling.
// If opts.DropInvalidFields is true, fields of wrong type will be dropped.
func CoerceWithOptions(pth *field.Path, obj interface{}, s *structuralschema.Structural, isResourceRoot bool, opts CoerceOptions) (*field.Error, []string) {
	if isResourceRoot {
		if s == nil {
			s = &structuralschema.Structural{}
		}
		if !s.XEmbeddedResource {
			clone := *s
			clone.XEmbeddedResource = true
			s = &clone
		}
	}
	c := coercer{DropInvalidFields: opts.DropInvalidFields, ReturnUnknownFieldPaths: opts.ReturnUnknownFieldPaths}
	schemaOpts := &structuralschema.UnknownFieldPathOptions{
		TrackUnknownFieldPaths: opts.ReturnUnknownFieldPaths,
	}
	fieldErr := c.coerce(pth, obj, s, schemaOpts)
	sort.Strings(schemaOpts.UnknownFieldPaths)
	return fieldErr, schemaOpts.UnknownFieldPaths
}


Coerce

// Coerce calls CoerceWithOptions without returning unknown field paths.
func Coerce(pth *field.Path, obj interface{}, s *structuralschema.Structural, isResourceRoot, dropInvalidFields bool) *field.Error {
	fieldErr, _ := CoerceWithOptions(pth, obj, s, isResourceRoot, CoerceOptions{DropInvalidFields: dropInvalidFields})
	return fieldErr
}


coerce

func (c *coercer) coerce(pth *field.Path, x interface{}, s *structuralschema.Structural, opts *structuralschema.UnknownFieldPathOptions) *field.Error {
	if s == nil {
		return nil
	}
	origPathLen := len(opts.ParentPath)
	defer func() {
		opts.ParentPath = opts.ParentPath[:origPathLen]
	}()
	switch x := x.(type) {
	case map[string]interface{}:
		for k, v := range x {
			if s.XEmbeddedResource {
				switch k {
				case "apiVersion", "kind":
					if _, ok := v.(string); !ok && c.DropInvalidFields {
						delete(x, k)
					} else if !ok {
						return field.Invalid(pth.Child(k), v, "must be a string")
					}
				case "metadata":
					meta, found, unknownFields, err := GetObjectMetaWithOptions(x, ObjectMetaOptions{
						DropMalformedFields:     c.DropInvalidFields,
						ReturnUnknownFieldPaths: c.ReturnUnknownFieldPaths,
						ParentPath:              pth,
					})
					opts.UnknownFieldPaths = append(opts.UnknownFieldPaths, unknownFields...)
					if err != nil {
						if !c.DropInvalidFields {
							return field.Invalid(pth.Child("metadata"), v, err.Error())
						}
						// pass through on error if DropInvalidFields is true
					} else if found {
						if err := SetObjectMeta(x, meta); err != nil {
							return field.Invalid(pth.Child("metadata"), v, err.Error())
						}
						if meta.CreationTimestamp.IsZero() {
							unstructured.RemoveNestedField(x, "metadata", "creationTimestamp")
						}
					}
				}
			}
			prop, ok := s.Properties[k]
			if ok {
				opts.AppendKey(k)
				if err := c.coerce(pth.Child(k), v, &prop, opts); err != nil {
					return err
				}
				opts.ParentPath = opts.ParentPath[:origPathLen]
			} else if s.AdditionalProperties != nil {
				opts.AppendKey(k)
				if err := c.coerce(pth.Key(k), v, s.AdditionalProperties.Structural, opts); err != nil {
					return err
				}
				opts.ParentPath = opts.ParentPath[:origPathLen]
			}
		}
	case []interface{}:
		for i, v := range x {
			opts.AppendIndex(i)
			if err := c.coerce(pth.Index(i), v, s.Items, opts); err != nil {
				return err
			}
			opts.ParentPath = opts.ParentPath[:origPathLen]
		}
	default:
		// scalars, do nothing
	}

	return nil
}


GetObjectMeta

// GetObjectMeta calls GetObjectMetaWithOptions without returning unknown field paths.
func GetObjectMeta(obj map[string]interface{}, dropMalformedFields bool) (*metav1.ObjectMeta, bool, error) {
	meta, found, _, err := GetObjectMetaWithOptions(obj, ObjectMetaOptions{
		DropMalformedFields: dropMalformedFields,
	})
	return meta, found, err
}


GetObjectMetaWithOptions

// GetObjectMetaWithOptions  does conversion of JSON to ObjectMeta.
// It first tries json.Unmarshal into a metav1.ObjectMeta
// type. If that does not work and opts.DropMalformedFields is true, it does field-by-field best-effort conversion
// throwing away fields which lead to errors.
// If opts.ReturnedUnknownFields is true, it will UnmarshalStrict instead, returning the paths of any unknown fields
// it encounters (i.e. paths returned as strict errs from UnmarshalStrict)
func GetObjectMetaWithOptions(obj map[string]interface{}, opts ObjectMetaOptions) (*metav1.ObjectMeta, bool, []string, error) {
	metadata, found := obj["metadata"]
	if !found {
		return nil, false, nil, nil
	}

	// round-trip through JSON first, hoping that unmarshalling just works
	objectMeta := &metav1.ObjectMeta{}
	metadataBytes, err := utiljson.Marshal(metadata)
	if err != nil {
		return nil, false, nil, err
	}
	var unmarshalErr error
	if opts.ReturnUnknownFieldPaths {
		var strictErrs []error
		strictErrs, unmarshalErr = kjson.UnmarshalStrict(metadataBytes, objectMeta)
		if unmarshalErr == nil {
			if len(strictErrs) > 0 {
				unknownPaths := []string{}
				prefix := opts.ParentPath.Child("metadata").String()
				for _, err := range strictErrs {
					if fieldPathErr, ok := err.(kjson.FieldError); ok {
						unknownPaths = append(unknownPaths, prefix+"."+fieldPathErr.FieldPath())
					}
				}
				return objectMeta, true, unknownPaths, nil
			}
			return objectMeta, true, nil, nil
		}
	} else {
		if unmarshalErr = utiljson.Unmarshal(metadataBytes, objectMeta); unmarshalErr == nil {
			// if successful, return
			return objectMeta, true, nil, nil
		}
	}
	if !opts.DropMalformedFields {
		// if we're not trying to drop malformed fields, return the error
		return nil, true, nil, unmarshalErr
	}

	metadataMap, ok := metadata.(map[string]interface{})
	if !ok {
		return nil, false, nil, fmt.Errorf("invalid metadata: expected object, got %T", metadata)
	}

	// Go field by field accumulating into the metadata object.
	// This takes advantage of the fact that you can repeatedly unmarshal individual fields into a single struct,
	// each iteration preserving the old key-values.
	accumulatedObjectMeta := &metav1.ObjectMeta{}
	testObjectMeta := &metav1.ObjectMeta{}
	var unknownFields []string
	for k, v := range metadataMap {
		// serialize a single field
		if singleFieldBytes, err := utiljson.Marshal(map[string]interface{}{k: v}); err == nil {
			// do a test unmarshal
			if utiljson.Unmarshal(singleFieldBytes, testObjectMeta) == nil {
				// if that succeeds, unmarshal for real
				if opts.ReturnUnknownFieldPaths {
					strictErrs, _ := kjson.UnmarshalStrict(singleFieldBytes, accumulatedObjectMeta)
					if len(strictErrs) > 0 {
						prefix := opts.ParentPath.Child("metadata").String()
						for _, err := range strictErrs {
							if fieldPathErr, ok := err.(kjson.FieldError); ok {
								unknownFields = append(unknownFields, prefix+"."+fieldPathErr.FieldPath())
							}
						}
					}
				} else {
					utiljson.Unmarshal(singleFieldBytes, accumulatedObjectMeta)
				}
			}
		}
	}

	return accumulatedObjectMeta, true, unknownFields, nil
}


SetObjectMeta

// SetObjectMeta writes back ObjectMeta into a JSON data structure.
func SetObjectMeta(obj map[string]interface{}, objectMeta *metav1.ObjectMeta) error {
	if objectMeta == nil {
		unstructured.RemoveNestedField(obj, "metadata")
		return nil
	}

	metadata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(objectMeta)
	if err != nil {
		return err
	}

	obj["metadata"] = metadata
	return nil
}


Validate

// Validate validates embedded ObjectMeta and TypeMeta.
// It also validate those at the root if isResourceRoot is true.
func Validate(pth *field.Path, obj interface{}, s *structuralschema.Structural, isResourceRoot bool) field.ErrorList {
	if isResourceRoot {
		if s == nil {
			s = &structuralschema.Structural{}
		}
		if !s.XEmbeddedResource {
			clone := *s
			clone.XEmbeddedResource = true
			s = &clone
		}
	}
	return validate(pth, obj, s)
}


validate

func validate(pth *field.Path, x interface{}, s *structuralschema.Structural) field.ErrorList {
	if s == nil {
		return nil
	}

	var allErrs field.ErrorList

	switch x := x.(type) {
	case map[string]interface{}:
		if s.XEmbeddedResource {
			allErrs = append(allErrs, validateEmbeddedResource(pth, x, s)...)
		}

		for k, v := range x {
			prop, ok := s.Properties[k]
			if ok {
				allErrs = append(allErrs, validate(pth.Child(k), v, &prop)...)
			} else if s.AdditionalProperties != nil {
				allErrs = append(allErrs, validate(pth.Key(k), v, s.AdditionalProperties.Structural)...)
			}
		}
	case []interface{}:
		for i, v := range x {
			allErrs = append(allErrs, validate(pth.Index(i), v, s.Items)...)
		}
	default:
		// scalars, do nothing
	}

	return allErrs
}


validateEmbeddedResource

func validateEmbeddedResource(pth *field.Path, x map[string]interface{}, s *structuralschema.Structural) field.ErrorList {
	var allErrs field.ErrorList

	// require apiVersion and kind, but not metadata
	if _, found := x["apiVersion"]; !found {
		allErrs = append(allErrs, field.Required(pth.Child("apiVersion"), "must not be empty"))
	}
	if _, found := x["kind"]; !found {
		allErrs = append(allErrs, field.Required(pth.Child("kind"), "must not be empty"))
	}

	for k, v := range x {
		switch k {
		case "apiVersion":
			if apiVersion, ok := v.(string); !ok {
				allErrs = append(allErrs, field.Invalid(pth.Child("apiVersion"), v, "must be a string"))
			} else if len(apiVersion) == 0 {
				allErrs = append(allErrs, field.Invalid(pth.Child("apiVersion"), apiVersion, "must not be empty"))
			} else if _, err := schema.ParseGroupVersion(apiVersion); err != nil {
				allErrs = append(allErrs, field.Invalid(pth.Child("apiVersion"), apiVersion, err.Error()))
			}
		case "kind":
			if kind, ok := v.(string); !ok {
				allErrs = append(allErrs, field.Invalid(pth.Child("kind"), v, "must be a string"))
			} else if len(kind) == 0 {
				allErrs = append(allErrs, field.Invalid(pth.Child("kind"), kind, "must not be empty"))
			} else if errs := utilvalidation.IsDNS1035Label(strings.ToLower(kind)); len(errs) > 0 {
				allErrs = append(allErrs, field.Invalid(pth.Child("kind"), kind, "may have mixed case, but should otherwise match: "+strings.Join(errs, ",")))
			}
		case "metadata":
			meta, _, err := GetObjectMeta(x, false)
			if err != nil {
				allErrs = append(allErrs, field.Invalid(pth.Child("metadata"), v, err.Error()))
			} else {
				if len(meta.Name) == 0 {
					meta.Name = "fakename" // we have to set something to avoid an error
				}
				allErrs = append(allErrs, metavalidation.ValidateObjectMeta(meta, len(meta.Namespace) > 0, path.ValidatePathSegmentName, pth.Child("metadata"))...)
			}
		}
	}

	return allErrs
}


RecordUnknownField

// RecordUnknownFields adds a path to an unknown field to the
// record of UnknownFieldPaths, if TrackUnknownFieldPaths is true
func (o *UnknownFieldPathOptions) RecordUnknownField(field string) {
	if !o.TrackUnknownFieldPaths {
		return
	}
	l := len(o.ParentPath)
	o.AppendKey(field)
	o.UnknownFieldPaths = append(o.UnknownFieldPaths, strings.Join(o.ParentPath, ""))
	o.ParentPath = o.ParentPath[:l]
}


AppendKey

// AppendKey adds a key (i.e. field) to the current parent
// path, if TrackUnknownFieldPaths is true.
func (o *UnknownFieldPathOptions) AppendKey(key string) {
	if !o.TrackUnknownFieldPaths {
		return
	}
	if len(o.ParentPath) > 0 {
		o.ParentPath = append(o.ParentPath, ".")
	}
	o.ParentPath = append(o.ParentPath, key)
}


AppendIndex

// AppendIndex adds an index to the most recent field of
// the current parent path, if TrackUnknownFieldPaths is true.
func (o *UnknownFieldPathOptions) AppendIndex(index int) {
	if !o.TrackUnknownFieldPaths {
		return
	}
	o.ParentPath = append(o.ParentPath, "[", strconv.Itoa(index), "]")
}


PruneWithOptions

// PruneWithOptions removes object fields in obj which are not specified in s. It skips TypeMeta
// and ObjectMeta fields if XEmbeddedResource is set to true, or for the root if isResourceRoot=true,
// i.e. it does not prune unknown metadata fields.
// It returns the set of fields that it prunes if opts.TrackUnknownFieldPaths is true
func PruneWithOptions(obj interface{}, s *structuralschema.Structural, isResourceRoot bool, opts structuralschema.UnknownFieldPathOptions) []string {
	if isResourceRoot {
		if s == nil {
			s = &structuralschema.Structural{}
		}
		if !s.XEmbeddedResource {
			clone := *s
			clone.XEmbeddedResource = true
			s = &clone
		}
	}
	prune(obj, s, &opts)
	sort.Strings(opts.UnknownFieldPaths)
	return opts.UnknownFieldPaths
}


Prune

// Prune is equivalent to
// PruneWithOptions(obj, s, isResourceRoot, structuralschema.UnknownFieldPathOptions{})
func Prune(obj interface{}, s *structuralschema.Structural, isResourceRoot bool) {
	PruneWithOptions(obj, s, isResourceRoot, structuralschema.UnknownFieldPathOptions{})
}


prune

func prune(x interface{}, s *structuralschema.Structural, opts *structuralschema.UnknownFieldPathOptions) {
	if s != nil && s.XPreserveUnknownFields {
		skipPrune(x, s, opts)
		return
	}

	origPathLen := len(opts.ParentPath)
	defer func() {
		opts.ParentPath = opts.ParentPath[:origPathLen]
	}()
	switch x := x.(type) {
	case map[string]interface{}:
		if s == nil {
			for k := range x {
				opts.RecordUnknownField(k)
				delete(x, k)
			}
			return
		}
		for k, v := range x {
			if s.XEmbeddedResource && metaFields[k] {
				continue
			}
			prop, ok := s.Properties[k]
			if ok {
				opts.AppendKey(k)
				prune(v, &prop, opts)
				opts.ParentPath = opts.ParentPath[:origPathLen]
			} else if s.AdditionalProperties != nil {
				opts.AppendKey(k)
				prune(v, s.AdditionalProperties.Structural, opts)
				opts.ParentPath = opts.ParentPath[:origPathLen]
			} else {
				if !metaFields[k] || len(opts.ParentPath) > 0 {
					opts.RecordUnknownField(k)
				}
				delete(x, k)
			}
		}
	case []interface{}:
		if s == nil {
			for i, v := range x {
				opts.AppendIndex(i)
				prune(v, nil, opts)
				opts.ParentPath = opts.ParentPath[:origPathLen]
			}
			return
		}
		for i, v := range x {
			opts.AppendIndex(i)
			prune(v, s.Items, opts)
			opts.ParentPath = opts.ParentPath[:origPathLen]
		}
	default:
		// scalars, do nothing
	}
}


skipPrune

func skipPrune(x interface{}, s *structuralschema.Structural, opts *structuralschema.UnknownFieldPathOptions) {
	if s == nil {
		return
	}
	origPathLen := len(opts.ParentPath)
	defer func() {
		opts.ParentPath = opts.ParentPath[:origPathLen]
	}()

	switch x := x.(type) {
	case map[string]interface{}:
		for k, v := range x {
			if s.XEmbeddedResource && metaFields[k] {
				continue
			}
			if prop, ok := s.Properties[k]; ok {
				opts.AppendKey(k)
				prune(v, &prop, opts)
				opts.ParentPath = opts.ParentPath[:origPathLen]
			} else if s.AdditionalProperties != nil {
				opts.AppendKey(k)
				prune(v, s.AdditionalProperties.Structural, opts)
				opts.ParentPath = opts.ParentPath[:origPathLen]
			}
		}
	case []interface{}:
		for i, v := range x {
			opts.AppendIndex(i)
			skipPrune(v, s.Items, opts)
			opts.ParentPath = opts.ParentPath[:origPathLen]
		}
	default:
		// scalars, do nothing
	}
}


StripValueValidations

// StripValueValidations returns a copy without value validations.
func (s *Structural) StripValueValidations() *Structural {
	s = s.DeepCopy()
	v := Visitor{
		Structural: func(s *Structural) bool {
			changed := false
			if s.ValueValidation != nil {
				s.ValueValidation = nil
				changed = true
			}
			return changed
		},
	}
	v.Visit(s)
	return s
}


StripNullable

// StripNullable returns a copy without nullable.
func (s *Structural) StripNullable() *Structural {
	s = s.DeepCopy()
	v := Visitor{
		Structural: func(s *Structural) bool {
			changed := s.Nullable
			s.Nullable = false
			return changed
		},
	}
	v.Visit(s)
	return s
}


DeepCopy

// DeepCopy creates a deep copy of the wrapped JSON value.
func (j JSON) DeepCopy() JSON {
	return JSON{runtime.DeepCopyJSONValue(j.Object)}
}


DeepCopyInto

// DeepCopyInto creates a deep copy of the wrapped JSON value and stores it in into.
func (j JSON) DeepCopyInto(into *JSON) {
	into.Object = runtime.DeepCopyJSONValue(j.Object)
}


Unfold

// Unfold expands vendor extensions of a structural schema.
// It mutates the receiver.
func (s *Structural) Unfold() *Structural {
	if s == nil {
		return nil
	}

	mapper := Visitor{
		Structural: func(s *Structural) bool {
			if !s.XIntOrString {
				return false
			}

			skipAnyOf := isIntOrStringAnyOfPattern(s)
			skipFirstAllOfAnyOf := isIntOrStringAllOfPattern(s)
			if skipAnyOf || skipFirstAllOfAnyOf {
				return false
			}

			if s.ValueValidation == nil {
				s.ValueValidation = &ValueValidation{}
			}
			if s.ValueValidation.AnyOf == nil {
				s.ValueValidation.AnyOf = []NestedValueValidation{
					{ForbiddenGenerics: Generic{Type: "integer"}},
					{ForbiddenGenerics: Generic{Type: "string"}},
				}
			} else {
				s.ValueValidation.AllOf = append([]NestedValueValidation{
					{
						ValueValidation: ValueValidation{
							AnyOf: []NestedValueValidation{
								{ForbiddenGenerics: Generic{Type: "integer"}},
								{ForbiddenGenerics: Generic{Type: "string"}},
							},
						},
					},
				}, s.ValueValidation.AllOf...)
			}

			return true
		},
		NestedValueValidation: nil, // x-kubernetes-int-or-string cannot be set in nested value validation
	}
	mapper.Visit(s)

	return s
}


ValidateStructural

// ValidateStructural checks that s is a structural schema with the invariants:
//
// * structurality: both `ForbiddenGenerics` and `ForbiddenExtensions` only have zero values, with the two exceptions for IntOrString.
// * RawExtension: for every schema with `x-kubernetes-embedded-resource: true`, `x-kubernetes-preserve-unknown-fields: true` and `type: object` are set
// * IntOrString: for `x-kubernetes-int-or-string: true` either `type` is empty under `anyOf` and `allOf` or the schema structure is one of these:
//
//  1. anyOf:
//     - type: integer
//     - type: string
//  2. allOf:
//     - anyOf:
//     - type: integer
//     - type: string
//     - ... zero or more
//
// * every specified field or array in s is also specified outside of value validation.
// * metadata at the root can only restrict the name and generateName, and not be specified at all in nested contexts.
// * additionalProperties at the root is not allowed.
func ValidateStructural(fldPath *field.Path, s *Structural) field.ErrorList {
	allErrs := field.ErrorList{}

	allErrs = append(allErrs, validateStructuralInvariants(s, rootLevel, fldPath)...)
	allErrs = append(allErrs, validateStructuralCompleteness(s, fldPath)...)

	// sort error messages. Otherwise, the errors slice will change every time due to
	// maps in the types and randomized iteration.
	sort.Slice(allErrs, func(i, j int) bool {
		return allErrs[i].Error() < allErrs[j].Error()
	})

	return allErrs
}


validateStructuralInvariants

// validateStructuralInvariants checks the invariants of a structural schema.
func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path) field.ErrorList {
	if s == nil {
		return nil
	}

	allErrs := field.ErrorList{}

	if s.Type == "array" && s.Items == nil {
		allErrs = append(allErrs, field.Required(fldPath.Child("items"), "must be specified"))
	}
	allErrs = append(allErrs, validateStructuralInvariants(s.Items, itemLevel, fldPath.Child("items"))...)

	for k, v := range s.Properties {
		allErrs = append(allErrs, validateStructuralInvariants(&v, fieldLevel, fldPath.Child("properties").Key(k))...)
	}
	allErrs = append(allErrs, validateGeneric(&s.Generic, lvl, fldPath)...)
	allErrs = append(allErrs, validateExtensions(&s.Extensions, fldPath)...)

	// detect the two IntOrString exceptions:
	// 1) anyOf:
	//    - type: integer
	//    - type: string
	// 2) allOf:
	//    - anyOf:
	//      - type: integer
	//      - type: string
	//    - ... zero or more
	skipAnyOf := isIntOrStringAnyOfPattern(s)
	skipFirstAllOfAnyOf := isIntOrStringAllOfPattern(s)

	allErrs = append(allErrs, validateValueValidation(s.ValueValidation, skipAnyOf, skipFirstAllOfAnyOf, lvl, fldPath)...)

	checkMetadata := (lvl == rootLevel) || s.XEmbeddedResource

	if s.XEmbeddedResource && s.Type != "object" {
		if len(s.Type) == 0 {
			allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must be object if x-kubernetes-embedded-resource is true"))
		} else {
			allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object if x-kubernetes-embedded-resource is true"))
		}
	} else if len(s.Type) == 0 && !s.Extensions.XIntOrString && !s.Extensions.XPreserveUnknownFields {
		switch lvl {
		case rootLevel:
			allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty at the root"))
		case itemLevel:
			allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty for specified array items"))
		case fieldLevel:
			allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty for specified object fields"))
		}
	}
	if s.XEmbeddedResource && s.AdditionalProperties != nil {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used if x-kubernetes-embedded-resource is set"))
	}

	if lvl == rootLevel && len(s.Type) > 0 && s.Type != "object" {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object at the root"))
	}

	// restrict metadata schemas to name and generateName only
	if kind, found := s.Properties["kind"]; found && checkMetadata {
		if kind.Type != "string" {
			allErrs = append(allErrs, field.Invalid(fldPath.Child("properties").Key("kind").Child("type"), kind.Type, "must be string"))
		}
	}
	if apiVersion, found := s.Properties["apiVersion"]; found && checkMetadata {
		if apiVersion.Type != "string" {
			allErrs = append(allErrs, field.Invalid(fldPath.Child("properties").Key("apiVersion").Child("type"), apiVersion.Type, "must be string"))
		}
	}

	if metadata, found := s.Properties["metadata"]; found {
		allErrs = append(allErrs, validateStructuralMetadataInvariants(&metadata, checkMetadata, lvl, fldPath.Child("properties").Key("metadata"))...)
	}

	if s.XEmbeddedResource && !s.XPreserveUnknownFields && len(s.Properties) == 0 {
		allErrs = append(allErrs, field.Required(fldPath.Child("properties"), "must not be empty if x-kubernetes-embedded-resource is true without x-kubernetes-preserve-unknown-fields"))
	}

	return allErrs
}


validateStructuralMetadataInvariants

func validateStructuralMetadataInvariants(s *Structural, checkMetadata bool, lvl level, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}

	if checkMetadata && s.Type != "object" {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object"))
	}

	if lvl == rootLevel {
		// metadata is a shallow copy. We can mutate it.
		_, foundName := s.Properties["name"]
		_, foundGenerateName := s.Properties["generateName"]
		if foundName && foundGenerateName && len(s.Properties) == 2 {
			s.Properties = nil
		} else if (foundName || foundGenerateName) && len(s.Properties) == 1 {
			s.Properties = nil
		}
		s.Type = ""
		s.Default.Object = nil // this is checked in API validation (and also tested)
		if s.ValueValidation == nil {
			s.ValueValidation = &ValueValidation{}
		}
		if !reflect.DeepEqual(*s, Structural{ValueValidation: &ValueValidation{}}) {
			// TODO: this is actually a field.Invalid error, but we cannot do JSON serialization of metadata here to get a proper message
			allErrs = append(allErrs, field.Forbidden(fldPath, "must not specify anything other than name and generateName, but metadata is implicitly specified"))
		}
	}

	return allErrs
}


isIntOrStringAnyOfPattern

func isIntOrStringAnyOfPattern(s *Structural) bool {
	if s == nil || s.ValueValidation == nil {
		return false
	}
	return len(s.ValueValidation.AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AnyOf, intOrStringAnyOf)
}


isIntOrStringAllOfPattern

func isIntOrStringAllOfPattern(s *Structural) bool {
	if s == nil || s.ValueValidation == nil {
		return false
	}
	return len(s.ValueValidation.AllOf) >= 1 && len(s.ValueValidation.AllOf[0].AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AllOf[0].AnyOf, intOrStringAnyOf)
}


validateGeneric

// validateGeneric checks the generic fields of a structural schema.
func validateGeneric(g *Generic, lvl level, fldPath *field.Path) field.ErrorList {
	if g == nil {
		return nil
	}

	allErrs := field.ErrorList{}

	if g.AdditionalProperties != nil {
		if lvl == rootLevel {
			allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used at the root"))
		}
		if g.AdditionalProperties.Structural != nil {
			allErrs = append(allErrs, validateStructuralInvariants(g.AdditionalProperties.Structural, fieldLevel, fldPath.Child("additionalProperties"))...)
		}
	}

	return allErrs
}


validateExtensions

// validateExtensions checks Kubernetes vendor extensions of a structural schema.
func validateExtensions(x *Extensions, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}

	if x.XIntOrString && x.XPreserveUnknownFields {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-preserve-unknown-fields"), x.XPreserveUnknownFields, "must be false if x-kubernetes-int-or-string is true"))
	}
	if x.XIntOrString && x.XEmbeddedResource {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-embedded-resource"), x.XEmbeddedResource, "must be false if x-kubernetes-int-or-string is true"))
	}

	return allErrs
}


validateValueValidation

// validateValueValidation checks the value validation in a structural schema.
func validateValueValidation(v *ValueValidation, skipAnyOf, skipFirstAllOfAnyOf bool, lvl level, fldPath *field.Path) field.ErrorList {
	if v == nil {
		return nil
	}

	allErrs := field.ErrorList{}

	if !skipAnyOf {
		for i := range v.AnyOf {
			allErrs = append(allErrs, validateNestedValueValidation(&v.AnyOf[i], false, false, lvl, fldPath.Child("anyOf").Index(i))...)
		}
	}

	for i := range v.AllOf {
		skipAnyOf := false
		if skipFirstAllOfAnyOf && i == 0 {
			skipAnyOf = true
		}
		allErrs = append(allErrs, validateNestedValueValidation(&v.AllOf[i], skipAnyOf, false, lvl, fldPath.Child("allOf").Index(i))...)
	}

	for i := range v.OneOf {
		allErrs = append(allErrs, validateNestedValueValidation(&v.OneOf[i], false, false, lvl, fldPath.Child("oneOf").Index(i))...)
	}

	allErrs = append(allErrs, validateNestedValueValidation(v.Not, false, false, lvl, fldPath.Child("not"))...)

	if len(v.Pattern) > 0 {
		if _, err := regexp.Compile(v.Pattern); err != nil {
			allErrs = append(allErrs, field.Invalid(fldPath.Child("pattern"), v.Pattern, fmt.Sprintf("must be a valid regular expression, but isn't: %v", err)))
		}
	}

	return allErrs
}


validateNestedValueValidation

// validateNestedValueValidation checks the nested value validation under a logic junctor in a structural schema.
func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllOfAnyOf bool, lvl level, fldPath *field.Path) field.ErrorList {
	if v == nil {
		return nil
	}

	allErrs := field.ErrorList{}

	allErrs = append(allErrs, validateValueValidation(&v.ValueValidation, skipAnyOf, skipAllOfAnyOf, lvl, fldPath)...)
	allErrs = append(allErrs, validateNestedValueValidation(v.Items, false, false, lvl, fldPath.Child("items"))...)

	for k, fld := range v.Properties {
		allErrs = append(allErrs, validateNestedValueValidation(&fld, false, false, fieldLevel, fldPath.Child("properties").Key(k))...)
	}

	if len(v.ForbiddenGenerics.Type) > 0 {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("type"), "must be empty to be structural"))
	}
	if v.ForbiddenGenerics.AdditionalProperties != nil {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must be undefined to be structural"))
	}
	if v.ForbiddenGenerics.Default.Object != nil {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "must be undefined to be structural"))
	}
	if len(v.ForbiddenGenerics.Title) > 0 {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("title"), "must be empty to be structural"))
	}
	if len(v.ForbiddenGenerics.Description) > 0 {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("description"), "must be empty to be structural"))
	}
	if v.ForbiddenGenerics.Nullable {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("nullable"), "must be false to be structural"))
	}

	if v.ForbiddenExtensions.XPreserveUnknownFields {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-preserve-unknown-fields"), "must be false to be structural"))
	}
	if v.ForbiddenExtensions.XEmbeddedResource {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-embedded-resource"), "must be false to be structural"))
	}
	if v.ForbiddenExtensions.XIntOrString {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-int-or-string"), "must be false to be structural"))
	}
	if len(v.ForbiddenExtensions.XListMapKeys) > 0 {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-list-map-keys"), "must be empty to be structural"))
	}
	if v.ForbiddenExtensions.XListType != nil {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-list-type"), "must be undefined to be structural"))
	}
	if v.ForbiddenExtensions.XMapType != nil {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-map-type"), "must be undefined to be structural"))
	}
	if len(v.ForbiddenExtensions.XValidations) > 0 {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-validations"), "must be empty to be structural"))
	}

	// forbid reasoning about metadata because it can lead to metadata restriction we don't want
	if _, found := v.Properties["metadata"]; found {
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("properties").Key("metadata"), "must not be specified in a nested context"))
	}

	return allErrs
}


Visit

// Visit recursively walks through the structural schema and calls the given callbacks
// at each node of those types.
func (m *Visitor) Visit(s *Structural) {
	m.visitStructural(s)
}


visitStructural

func (m *Visitor) visitStructural(s *Structural) bool {
	ret := false
	if m.Structural != nil {
		ret = m.Structural(s)
	}

	if s.Items != nil {
		m.visitStructural(s.Items)
	}
	for k, v := range s.Properties {
		if changed := m.visitStructural(&v); changed {
			ret = true
			s.Properties[k] = v
		}
	}
	if s.Generic.AdditionalProperties != nil && s.Generic.AdditionalProperties.Structural != nil {
		m.visitStructural(s.Generic.AdditionalProperties.Structural)
	}
	if s.ValueValidation != nil {
		for i := range s.ValueValidation.AllOf {
			m.visitNestedValueValidation(&s.ValueValidation.AllOf[i])
		}
		for i := range s.ValueValidation.AnyOf {
			m.visitNestedValueValidation(&s.ValueValidation.AnyOf[i])
		}
		for i := range s.ValueValidation.OneOf {
			m.visitNestedValueValidation(&s.ValueValidation.OneOf[i])
		}
		if s.ValueValidation.Not != nil {
			m.visitNestedValueValidation(s.ValueValidation.Not)
		}
	}

	return ret
}


visitNestedValueValidation

func (m *Visitor) visitNestedValueValidation(vv *NestedValueValidation) bool {
	ret := false
	if m.NestedValueValidation != nil {
		ret = m.NestedValueValidation(vv)
	}

	if vv.Items != nil {
		m.visitNestedValueValidation(vv.Items)
	}
	for k, v := range vv.Properties {
		if changed := m.visitNestedValueValidation(&v); changed {
			ret = true
			vv.Properties[k] = v
		}
	}
	if vv.ForbiddenGenerics.AdditionalProperties != nil && vv.ForbiddenGenerics.AdditionalProperties.Structural != nil {
		m.visitStructural(vv.ForbiddenGenerics.AdditionalProperties.Structural)
	}
	for i := range vv.ValueValidation.AllOf {
		m.visitNestedValueValidation(&vv.ValueValidation.AllOf[i])
	}
	for i := range vv.ValueValidation.AnyOf {
		m.visitNestedValueValidation(&vv.ValueValidation.AnyOf[i])
	}
	for i := range vv.ValueValidation.OneOf {
		m.visitNestedValueValidation(&vv.ValueValidation.OneOf[i])
	}
	if vv.ValueValidation.Not != nil {
		m.visitNestedValueValidation(vv.ValueValidation.Not)
	}

	return ret
}


StripUnsupportedFormatsPostProcess

// StripUnsupportedFormatsPostProcess sets unsupported formats to empty string.
func StripUnsupportedFormatsPostProcess(s *spec.Schema) error {
	if len(s.Format) == 0 {
		return nil
	}

	normalized := strings.Replace(s.Format, "-", "", -1) // go-openapi default format name normalization
	if !supportedFormats.Has(normalized) {
		s.Format = ""
	}

	return nil
}


NewSchemaValidator

// NewSchemaValidator creates an openapi schema validator for the given CRD validation.
func NewSchemaValidator(customResourceValidation *apiextensions.CustomResourceValidation) (*validate.SchemaValidator, *spec.Schema, error) {
	// Convert CRD schema to openapi schema
	openapiSchema := &spec.Schema{}
	if customResourceValidation != nil {
		// TODO: replace with NewStructural(...).ToGoOpenAPI
		if err := ConvertJSONSchemaPropsWithPostProcess(customResourceValidation.OpenAPIV3Schema, openapiSchema, StripUnsupportedFormatsPostProcess); err != nil {
			return nil, nil, err
		}
	}
	return validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default), openapiSchema, nil
}


ValidateCustomResource

// ValidateCustomResource validates the Custom Resource against the schema in the CustomResourceDefinition.
// CustomResource is a JSON data structure.
func ValidateCustomResource(fldPath *field.Path, customResource interface{}, validator *validate.SchemaValidator) field.ErrorList {
	if validator == nil {
		return nil
	}

	result := validator.Validate(customResource)
	if result.IsValid() {
		return nil
	}
	var allErrs field.ErrorList
	for _, err := range result.Errors {
		switch err := err.(type) {

		case *openapierrors.Validation:
			errPath := fldPath
			if len(err.Name) > 0 && err.Name != "." {
				errPath = errPath.Child(strings.TrimPrefix(err.Name, "."))
			}

			switch err.Code() {
			case openapierrors.RequiredFailCode:
				allErrs = append(allErrs, field.Required(errPath, ""))

			case openapierrors.EnumFailCode:
				values := []string{}
				for _, allowedValue := range err.Values {
					if s, ok := allowedValue.(string); ok {
						values = append(values, s)
					} else {
						allowedJSON, _ := json.Marshal(allowedValue)
						values = append(values, string(allowedJSON))
					}
				}
				allErrs = append(allErrs, field.NotSupported(errPath, err.Value, values))

			case openapierrors.TooLongFailCode:
				value := interface{}("")
				if err.Value != nil {
					value = err.Value
				}
				max := int64(-1)
				if i, ok := err.Valid.(int64); ok {
					max = i
				}
				allErrs = append(allErrs, field.TooLongMaxLength(errPath, value, int(max)))

			case openapierrors.MaxItemsFailCode:
				actual := int64(-1)
				if i, ok := err.Value.(int64); ok {
					actual = i
				}
				max := int64(-1)
				if i, ok := err.Valid.(int64); ok {
					max = i
				}
				allErrs = append(allErrs, field.TooMany(errPath, int(actual), int(max)))

			case openapierrors.TooManyPropertiesCode:
				actual := int64(-1)
				if i, ok := err.Value.(int64); ok {
					actual = i
				}
				max := int64(-1)
				if i, ok := err.Valid.(int64); ok {
					max = i
				}
				allErrs = append(allErrs, field.TooMany(errPath, int(actual), int(max)))

			case openapierrors.InvalidTypeCode:
				value := interface{}("")
				if err.Value != nil {
					value = err.Value
				}
				allErrs = append(allErrs, field.TypeInvalid(errPath, value, err.Error()))

			default:
				value := interface{}("")
				if err.Value != nil {
					value = err.Value
				}
				allErrs = append(allErrs, field.Invalid(errPath, value, err.Error()))
			}

		default:
			allErrs = append(allErrs, field.Invalid(fldPath, "", err.Error()))
		}
	}
	return allErrs
}


ConvertJSONSchemaProps

// ConvertJSONSchemaProps converts the schema from apiextensions.JSONSchemaPropos to go-openapi/spec.Schema.
func ConvertJSONSchemaProps(in *apiextensions.JSONSchemaProps, out *spec.Schema) error {
	return ConvertJSONSchemaPropsWithPostProcess(in, out, nil)
}