func (r *crdHandler) destroy()

// Destroy shuts down storage layer for all registered CRDs.
// It should be called as a last step of the shutdown sequence.
func (r *crdHandler) destroy() {
	r.customStorageLock.Lock()
	defer r.customStorageLock.Unlock()

	storageMap := r.customStorage.Load().(crdStorageMap)
	for _, crdInfo := range storageMap {
		for _, storage := range crdInfo.storages {
			// DestroyFunc have to be implemented in idempotent way,
			// so the potential race with r.tearDown() (being called
			// from a goroutine) is safe.
			storage.CustomResource.DestroyFunc()
		}
	}
}


// GetCustomResourceListerCollectionDeleter returns the ListerCollectionDeleter of
// the given crd.
func (r *crdHandler) GetCustomResourceListerCollectionDeleter(crd *apiextensionsv1.CustomResourceDefinition) (finalizer.ListerCollectionDeleter, error) {
	info, err := r.getOrCreateServingInfoFor(crd.UID, crd.Name)
	if err != nil {
		return nil, err
	}
	return info.storages[info.storageVersion].CustomResource, nil
}


// getOrCreateServingInfoFor gets the CRD serving info for the given CRD UID if the key exists in the storage map.
// Otherwise the function fetches the up-to-date CRD using the given CRD name and creates CRD serving info.
func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crdInfo, error) {
	storageMap := r.customStorage.Load().(crdStorageMap)
	if ret, ok := storageMap[uid]; ok {
		return ret, nil
	}

	r.customStorageLock.Lock()
	defer r.customStorageLock.Unlock()

	// Get the up-to-date CRD when we have the lock, to avoid racing with updateCustomResourceDefinition.
	// If updateCustomResourceDefinition sees an update and happens later, the storage will be deleted and
	// we will re-create the updated storage on demand. If updateCustomResourceDefinition happens before,
	// we make sure that we observe the same up-to-date CRD.
	crd, err := r.crdLister.Get(name)
	if err != nil {
		return nil, err
	}
	storageMap = r.customStorage.Load().(crdStorageMap)
	if ret, ok := storageMap[crd.UID]; ok {
		return ret, nil
	}

	storageVersion, err := apiextensionshelpers.GetCRDStorageVersion(crd)
	if err != nil {
		return nil, err
	}

	// Scope/Storages per version.
	requestScopes := map[string]*handlers.RequestScope{}
	storages := map[string]customresource.CustomResourceStorage{}
	statusScopes := map[string]*handlers.RequestScope{}
	scaleScopes := map[string]*handlers.RequestScope{}
	deprecated := map[string]bool{}
	warnings := map[string][]string{}

	equivalentResourceRegistry := runtime.NewEquivalentResourceRegistry()

	structuralSchemas := map[string]*structuralschema.Structural{}
	for _, v := range crd.Spec.Versions {
		val, err := apiextensionshelpers.GetSchemaForVersion(crd, v.Name)
		if err != nil {
			utilruntime.HandleError(err)
			return nil, fmt.Errorf("the server could not properly serve the CR schema")
		}
		if val == nil {
			continue
		}
		internalValidation := &apiextensionsinternal.CustomResourceValidation{}
		if err := apiextensionsv1.Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(val, internalValidation, nil); err != nil {
			return nil, fmt.Errorf("failed converting CRD validation to internal version: %v", err)
		}
		s, err := structuralschema.NewStructural(internalValidation.OpenAPIV3Schema)
		if crd.Spec.PreserveUnknownFields == false && err != nil {
			// This should never happen. If it does, it is a programming error.
			utilruntime.HandleError(fmt.Errorf("failed to convert schema to structural: %v", err))
			return nil, fmt.Errorf("the server could not properly serve the CR schema") // validation should avoid this
		}

		if crd.Spec.PreserveUnknownFields == false {
			// we don't own s completely, e.g. defaults are not deep-copied. So better make a copy here.
			s = s.DeepCopy()

			if err := structuraldefaulting.PruneDefaults(s); err != nil {
				// This should never happen. If it does, it is a programming error.
				utilruntime.HandleError(fmt.Errorf("failed to prune defaults: %v", err))
				return nil, fmt.Errorf("the server could not properly serve the CR schema") // validation should avoid this
			}
		}
		structuralSchemas[v.Name] = s
	}

	openAPIModels, err := buildOpenAPIModelsForApply(r.staticOpenAPISpec, crd)
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("error building openapi models for %s: %v", crd.Name, err))
		openAPIModels = nil
	}

	var typeConverter managedfields.TypeConverter = managedfields.NewDeducedTypeConverter()
	if len(openAPIModels) > 0 {
		typeConverter, err = managedfields.NewTypeConverter(openAPIModels, crd.Spec.PreserveUnknownFields)
		if err != nil {
			return nil, err
		}
	}

	safeConverter, unsafeConverter, err := r.converterFactory.NewConverter(crd)
	if err != nil {
		return nil, err
	}

	// Create replicasPathInCustomResource
	replicasPathInCustomResource := managedfields.ResourcePathMappings{}
	for _, v := range crd.Spec.Versions {
		subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, v.Name)
		if err != nil {
			utilruntime.HandleError(err)
			return nil, fmt.Errorf("the server could not properly serve the CR subresources")
		}
		if subresources == nil || subresources.Scale == nil {
			replicasPathInCustomResource[schema.GroupVersion{Group: crd.Spec.Group, Version: v.Name}.String()] = nil
			continue
		}
		path := fieldpath.Path{}
		splitReplicasPath := strings.Split(strings.TrimPrefix(subresources.Scale.SpecReplicasPath, "."), ".")
		for _, element := range splitReplicasPath {
			s := element
			path = append(path, fieldpath.PathElement{FieldName: &s})
		}
		replicasPathInCustomResource[schema.GroupVersion{Group: crd.Spec.Group, Version: v.Name}.String()] = path
	}

	for _, v := range crd.Spec.Versions {
		// In addition to Unstructured objects (Custom Resources), we also may sometimes need to
		// decode unversioned Options objects, so we delegate to parameterScheme for such types.
		parameterScheme := runtime.NewScheme()
		parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: crd.Spec.Group, Version: v.Name},
			&metav1.ListOptions{},
			&metav1.GetOptions{},
			&metav1.DeleteOptions{},
		)
		parameterCodec := runtime.NewParameterCodec(parameterScheme)

		resource := schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Plural}
		singularResource := schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Singular}
		kind := schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.Kind}
		equivalentResourceRegistry.RegisterKindFor(resource, "", kind)

		typer := newUnstructuredObjectTyper(parameterScheme)
		creator := unstructuredCreator{}

		validationSchema, err := apiextensionshelpers.GetSchemaForVersion(crd, v.Name)
		if err != nil {
			utilruntime.HandleError(err)
			return nil, fmt.Errorf("the server could not properly serve the CR schema")
		}
		var internalValidationSchema *apiextensionsinternal.CustomResourceValidation
		if validationSchema != nil {
			internalValidationSchema = &apiextensionsinternal.CustomResourceValidation{}
			if err := apiextensionsv1.Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(validationSchema, internalValidationSchema, nil); err != nil {
				return nil, fmt.Errorf("failed to convert CRD validation to internal version: %v", err)
			}
		}
		validator, _, err := apiservervalidation.NewSchemaValidator(internalValidationSchema)
		if err != nil {
			return nil, err
		}

		var statusSpec *apiextensionsinternal.CustomResourceSubresourceStatus
		var statusValidator *validate.SchemaValidator
		subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, v.Name)
		if err != nil {
			utilruntime.HandleError(err)
			return nil, fmt.Errorf("the server could not properly serve the CR subresources")
		}
		if subresources != nil && subresources.Status != nil {
			equivalentResourceRegistry.RegisterKindFor(resource, "status", kind)
			statusSpec = &apiextensionsinternal.CustomResourceSubresourceStatus{}
			if err := apiextensionsv1.Convert_v1_CustomResourceSubresourceStatus_To_apiextensions_CustomResourceSubresourceStatus(subresources.Status, statusSpec, nil); err != nil {
				return nil, fmt.Errorf("failed converting CRD status subresource to internal version: %v", err)
			}
			// for the status subresource, validate only against the status schema
			if internalValidationSchema != nil && internalValidationSchema.OpenAPIV3Schema != nil && internalValidationSchema.OpenAPIV3Schema.Properties != nil {
				if statusSchema, ok := internalValidationSchema.OpenAPIV3Schema.Properties["status"]; ok {
					openapiSchema := &spec.Schema{}
					if err := apiservervalidation.ConvertJSONSchemaPropsWithPostProcess(&statusSchema, openapiSchema, apiservervalidation.StripUnsupportedFormatsPostProcess); err != nil {
						return nil, err
					}
					statusValidator = validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default)
				}
			}
		}

		var scaleSpec *apiextensionsinternal.CustomResourceSubresourceScale
		if subresources != nil && subresources.Scale != nil {
			equivalentResourceRegistry.RegisterKindFor(resource, "scale", autoscalingv1.SchemeGroupVersion.WithKind("Scale"))
			scaleSpec = &apiextensionsinternal.CustomResourceSubresourceScale{}
			if err := apiextensionsv1.Convert_v1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale(subresources.Scale, scaleSpec, nil); err != nil {
				return nil, fmt.Errorf("failed converting CRD status subresource to internal version: %v", err)
			}
		}

		columns, err := getColumnsForVersion(crd, v.Name)
		if err != nil {
			utilruntime.HandleError(err)
			return nil, fmt.Errorf("the server could not properly serve the CR columns")
		}
		table, err := tableconvertor.New(columns)
		if err != nil {
			klog.V(2).Infof("The CRD for %v has an invalid printer specification, falling back to default printing: %v", kind, err)
		}

		storages[v.Name] = customresource.NewStorage(
			resource.GroupResource(),
			singularResource.GroupResource(),
			kind,
			schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.ListKind},
			customresource.NewStrategy(
				typer,
				crd.Spec.Scope == apiextensionsv1.NamespaceScoped,
				kind,
				validator,
				statusValidator,
				structuralSchemas,
				statusSpec,
				scaleSpec,
			),
			crdConversionRESTOptionsGetter{
				RESTOptionsGetter:     r.restOptionsGetter,
				converter:             safeConverter,
				decoderVersion:        schema.GroupVersion{Group: crd.Spec.Group, Version: v.Name},
				encoderVersion:        schema.GroupVersion{Group: crd.Spec.Group, Version: storageVersion},
				structuralSchemas:     structuralSchemas,
				structuralSchemaGK:    kind.GroupKind(),
				preserveUnknownFields: crd.Spec.PreserveUnknownFields,
			},
			crd.Status.AcceptedNames.Categories,
			table,
			replicasPathInCustomResource,
		)

		clusterScoped := crd.Spec.Scope == apiextensionsv1.ClusterScoped

		// CRDs explicitly do not support protobuf, but some objects returned by the API server do
		negotiatedSerializer := unstructuredNegotiatedSerializer{
			typer:                 typer,
			creator:               creator,
			converter:             safeConverter,
			structuralSchemas:     structuralSchemas,
			structuralSchemaGK:    kind.GroupKind(),
			preserveUnknownFields: crd.Spec.PreserveUnknownFields,
		}
		var standardSerializers []runtime.SerializerInfo
		for _, s := range negotiatedSerializer.SupportedMediaTypes() {
			if s.MediaType == runtime.ContentTypeProtobuf {
				continue
			}
			standardSerializers = append(standardSerializers, s)
		}

		reqScope := handlers.RequestScope{
			Namer: handlers.ContextBasedNaming{
				Namer:         meta.NewAccessor(),
				ClusterScoped: clusterScoped,
			},
			Serializer:          negotiatedSerializer,
			ParameterCodec:      parameterCodec,
			StandardSerializers: standardSerializers,

			Creater:         creator,
			Convertor:       safeConverter,
			Defaulter:       unstructuredDefaulter{parameterScheme, structuralSchemas, kind.GroupKind()},
			Typer:           typer,
			UnsafeConvertor: unsafeConverter,

			EquivalentResourceMapper: equivalentResourceRegistry,

			Resource: schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Plural},
			Kind:     kind,

			// a handler for a specific group-version of a custom resource uses that version as the in-memory representation
			HubGroupVersion: kind.GroupVersion(),

			MetaGroupVersion: metav1.SchemeGroupVersion,

			TableConvertor: storages[v.Name].CustomResource,

			Authorizer: r.authorizer,

			MaxRequestBodyBytes: r.maxRequestBodyBytes,
		}

		resetFields := storages[v.Name].CustomResource.GetResetFields()
		reqScope, err = scopeWithFieldManager(
			typeConverter,
			reqScope,
			resetFields,
			"",
		)
		if err != nil {
			return nil, err
		}
		requestScopes[v.Name] = &reqScope

		scaleColumns, err := getScaleColumnsForVersion(crd, v.Name)
		if err != nil {
			return nil, fmt.Errorf("the server could not properly serve the CR scale subresource columns %w", err)
		}
		scaleTable, _ := tableconvertor.New(scaleColumns)

		// override scale subresource values
		// shallow copy
		scaleScope := *requestScopes[v.Name]
		scaleConverter := scale.NewScaleConverter()
		scaleScope.Subresource = "scale"
		scaleScope.Serializer = serializer.NewCodecFactory(scaleConverter.Scheme())
		scaleScope.Kind = autoscalingv1.SchemeGroupVersion.WithKind("Scale")
		scaleScope.Namer = handlers.ContextBasedNaming{
			Namer:         meta.NewAccessor(),
			ClusterScoped: clusterScoped,
		}
		scaleScope.TableConvertor = scaleTable

		if subresources != nil && subresources.Scale != nil {
			scaleScope, err = scopeWithFieldManager(
				typeConverter,
				scaleScope,
				nil,
				"scale",
			)
			if err != nil {
				return nil, err
			}
		}

		scaleScopes[v.Name] = &scaleScope

		// override status subresource values
		// shallow copy
		statusScope := *requestScopes[v.Name]
		statusScope.Subresource = "status"
		statusScope.Namer = handlers.ContextBasedNaming{
			Namer:         meta.NewAccessor(),
			ClusterScoped: clusterScoped,
		}

		if subresources != nil && subresources.Status != nil {
			resetFields := storages[v.Name].Status.GetResetFields()
			statusScope, err = scopeWithFieldManager(
				typeConverter,
				statusScope,
				resetFields,
				"status",
			)
			if err != nil {
				return nil, err
			}
		}

		statusScopes[v.Name] = &statusScope

		if v.Deprecated {
			deprecated[v.Name] = true
			if v.DeprecationWarning != nil {
				warnings[v.Name] = append(warnings[v.Name], *v.DeprecationWarning)
			} else {
				warnings[v.Name] = append(warnings[v.Name], defaultDeprecationWarning(v.Name, crd.Spec))
			}
		}
	}

	ret := &crdInfo{
		spec:                &crd.Spec,
		acceptedNames:       &crd.Status.AcceptedNames,
		storages:            storages,
		requestScopes:       requestScopes,
		scaleRequestScopes:  scaleScopes,
		statusRequestScopes: statusScopes,
		deprecated:          deprecated,
		warnings:            warnings,
		storageVersion:      storageVersion,
		waitGroup:           &utilwaitgroup.SafeWaitGroup{},
	}

	// Copy because we cannot write to storageMap without a race
	// as it is used without locking elsewhere.
	storageMap2 := storageMap.clone()

	storageMap2[crd.UID] = ret
	r.customStorage.Store(storageMap2)

	return ret, nil
}


func scopeWithFieldManager(typeConverter managedfields.TypeConverter, reqScope handlers.RequestScope, resetFields map[fieldpath.APIVersion]*fieldpath.Set, subresource string) (handlers.RequestScope, error) {
	fieldManager, err := managedfields.NewDefaultCRDFieldManager(
		typeConverter,
		reqScope.Convertor,
		reqScope.Defaulter,
		reqScope.Creater,
		reqScope.Kind,
		reqScope.HubGroupVersion,
		subresource,
		resetFields,
	)
	if err != nil {
		return handlers.RequestScope{}, err
	}
	reqScope.FieldManager = fieldManager
	return reqScope, nil
}


func defaultDeprecationWarning(deprecatedVersion string, crd apiextensionsv1.CustomResourceDefinitionSpec) string {
	msg := fmt.Sprintf("%s/%s %s is deprecated", crd.Group, deprecatedVersion, crd.Names.Kind)

	var servedNonDeprecatedVersions []string
	for _, v := range crd.Versions {
		if v.Served && !v.Deprecated && version.CompareKubeAwareVersionStrings(deprecatedVersion, v.Name) < 0 {
			servedNonDeprecatedVersions = append(servedNonDeprecatedVersions, v.Name)
		}
	}
	if len(servedNonDeprecatedVersions) == 0 {
		return msg
	}
	sort.Slice(servedNonDeprecatedVersions, func(i, j int) bool {
		return version.CompareKubeAwareVersionStrings(servedNonDeprecatedVersions[i], servedNonDeprecatedVersions[j]) > 0
	})
	msg += fmt.Sprintf("; use %s/%s %s", crd.Group, servedNonDeprecatedVersions[0], crd.Names.Kind)
	return msg
}


func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
	return []runtime.SerializerInfo{
		{
			MediaType:        "application/json",
			MediaTypeType:    "application",
			MediaTypeSubType: "json",
			EncodesAsText:    true,
			Serializer:       json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, false),
			PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, true),
			StrictSerializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{
				Strict: true,
			}),
			StreamSerializer: &runtime.StreamSerializerInfo{
				EncodesAsText: true,
				Serializer:    json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, false),
				Framer:        json.Framer,
			},
		},
		{
			MediaType:        "application/yaml",
			MediaTypeType:    "application",
			MediaTypeSubType: "yaml",
			EncodesAsText:    true,
			Serializer:       json.NewYAMLSerializer(json.DefaultMetaFactory, s.creator, s.typer),
			StrictSerializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{
				Yaml:   true,
				Strict: true,
			}),
		},
		{
			MediaType:        "application/vnd.kubernetes.protobuf",
			MediaTypeType:    "application",
			MediaTypeSubType: "vnd.kubernetes.protobuf",
			Serializer:       protobuf.NewSerializer(s.creator, s.typer),
			StreamSerializer: &runtime.StreamSerializerInfo{
				Serializer: protobuf.NewRawSerializer(s.creator, s.typer),
				Framer:     protobuf.LengthDelimitedFramer,
			},
		},
	}
}


func (s unstructuredNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
	return versioning.NewCodec(encoder, nil, s.converter, Scheme, Scheme, Scheme, gv, nil, "crdNegotiatedSerializer")
}


func (s unstructuredNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
	returnUnknownFieldPaths := false
	if serializer, ok := decoder.(*json.Serializer); ok {
		returnUnknownFieldPaths = serializer.IsStrict()
	}
	d := schemaCoercingDecoder{delegate: decoder, validator: unstructuredSchemaCoercer{structuralSchemas: s.structuralSchemas, structuralSchemaGK: s.structuralSchemaGK, preserveUnknownFields: s.preserveUnknownFields, returnUnknownFieldPaths: returnUnknownFieldPaths}}
	return versioning.NewCodec(nil, d, runtime.UnsafeObjectConvertor(Scheme), Scheme, Scheme, unstructuredDefaulter{
		delegate:           Scheme,
		structuralSchemas:  s.structuralSchemas,
		structuralSchemaGK: s.structuralSchemaGK,
	}, nil, gv, "unstructuredNegotiatedSerializer")
}


func newUnstructuredObjectTyper(Delegate runtime.ObjectTyper) UnstructuredObjectTyper {
	return UnstructuredObjectTyper{
		Delegate:          Delegate,
		UnstructuredTyper: crdserverscheme.NewUnstructuredObjectTyper(),
	}
}


func (t UnstructuredObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
	// Delegate for things other than Unstructured.
	if _, ok := obj.(runtime.Unstructured); !ok {
		return t.Delegate.ObjectKinds(obj)
	}
	return t.UnstructuredTyper.ObjectKinds(obj)
}


func (t UnstructuredObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool {
	return t.Delegate.Recognizes(gvk) || t.UnstructuredTyper.Recognizes(gvk)
}


func (c unstructuredCreator) New(kind schema.GroupVersionKind) (runtime.Object, error) {
	ret := &unstructured.Unstructured{}
	ret.SetGroupVersionKind(kind)
	return ret, nil
}


func (d unstructuredDefaulter) Default(in runtime.Object) {
	// Delegate for things other than Unstructured, and other GKs
	u, ok := in.(runtime.Unstructured)
	if !ok || u.GetObjectKind().GroupVersionKind().GroupKind() != d.structuralSchemaGK {
		d.delegate.Default(in)
		return
	}

	structuraldefaulting.Default(u.UnstructuredContent(), d.structuralSchemas[u.GetObjectKind().GroupVersionKind().Version])
}


// clone returns a clone of the provided crdStorageMap.
// The clone is a shallow copy of the map.
func (in crdStorageMap) clone() crdStorageMap {
	if in == nil {
		return nil
	}
	out := make(crdStorageMap, len(in))
	for key, value := range in {
		out[key] = value
	}
	return out
}


func (t crdConversionRESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
	ret, err := t.RESTOptionsGetter.GetRESTOptions(resource)
	if err == nil {
		d := schemaCoercingDecoder{delegate: ret.StorageConfig.Codec, validator: unstructuredSchemaCoercer{
			// drop invalid fields while decoding old CRs (before we haven't had any ObjectMeta validation)
			dropInvalidMetadata:   true,
			repairGeneration:      true,
			structuralSchemas:     t.structuralSchemas,
			structuralSchemaGK:    t.structuralSchemaGK,
			preserveUnknownFields: t.preserveUnknownFields,
		}}
		c := schemaCoercingConverter{delegate: t.converter, validator: unstructuredSchemaCoercer{
			structuralSchemas:     t.structuralSchemas,
			structuralSchemaGK:    t.structuralSchemaGK,
			preserveUnknownFields: t.preserveUnknownFields,
		}}
		ret.StorageConfig.Codec = versioning.NewCodec(
			ret.StorageConfig.Codec,
			d,
			c,
			&unstructuredCreator{},
			crdserverscheme.NewUnstructuredObjectTyper(),
			&unstructuredDefaulter{
				delegate:           Scheme,
				structuralSchemaGK: t.structuralSchemaGK,
				structuralSchemas:  t.structuralSchemas,
			},
			t.encoderVersion,
			t.decoderVersion,
			"crdRESTOptions",
		)
	}
	return ret, err
}


func (d schemaCoercingDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
	var decodingStrictErrs []error
	obj, gvk, err := d.delegate.Decode(data, defaults, into)
	if err != nil {
		decodeStrictErr, ok := runtime.AsStrictDecodingError(err)
		if !ok || obj == nil {
			return nil, gvk, err
		}
		decodingStrictErrs = decodeStrictErr.Errors()
	}
	var unknownFields []string
	if u, ok := obj.(*unstructured.Unstructured); ok {
		unknownFields, err = d.validator.apply(u)
		if err != nil {
			return nil, gvk, err
		}
	}
	if d.validator.returnUnknownFieldPaths && (len(decodingStrictErrs) > 0 || len(unknownFields) > 0) {
		for _, unknownField := range unknownFields {
			decodingStrictErrs = append(decodingStrictErrs, fmt.Errorf(`unknown field "%s"`, unknownField))
		}
		return obj, gvk, runtime.NewStrictDecodingError(decodingStrictErrs)
	}

	return obj, gvk, nil
}


func (v schemaCoercingConverter) Convert(in, out, context interface{}) error {
	if err := v.delegate.Convert(in, out, context); err != nil {
		return err
	}

	if u, ok := out.(*unstructured.Unstructured); ok {
		if _, err := v.validator.apply(u); err != nil {
			return err
		}
	}

	return nil
}


func (v schemaCoercingConverter) ConvertToVersion(in runtime.Object, gv runtime.GroupVersioner) (runtime.Object, error) {
	out, err := v.delegate.ConvertToVersion(in, gv)
	if err != nil {
		return nil, err
	}

	if u, ok := out.(*unstructured.Unstructured); ok {
		if _, err := v.validator.apply(u); err != nil {
			return nil, err
		}
	}

	return out, nil
}


func (v schemaCoercingConverter) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
	return v.delegate.ConvertFieldLabel(gvk, label, value)
}


func (v *unstructuredSchemaCoercer) apply(u *unstructured.Unstructured) (unknownFieldPaths []string, err error) {
	// save implicit meta fields that don't have to be specified in the validation spec
	kind, foundKind, err := unstructured.NestedString(u.UnstructuredContent(), "kind")
	if err != nil {
		return nil, err
	}
	apiVersion, foundApiVersion, err := unstructured.NestedString(u.UnstructuredContent(), "apiVersion")
	if err != nil {
		return nil, err
	}
	objectMeta, foundObjectMeta, metaUnknownFields, err := schemaobjectmeta.GetObjectMetaWithOptions(u.Object, schemaobjectmeta.ObjectMetaOptions{
		DropMalformedFields:     v.dropInvalidMetadata,
		ReturnUnknownFieldPaths: v.returnUnknownFieldPaths,
	})
	if err != nil {
		return nil, err
	}
	unknownFieldPaths = append(unknownFieldPaths, metaUnknownFields...)

	// compare group and kind because also other object like DeleteCollection options pass through here
	gv, err := schema.ParseGroupVersion(apiVersion)
	if err != nil {
		return nil, err
	}

	if gv.Group == v.structuralSchemaGK.Group && kind == v.structuralSchemaGK.Kind {
		if !v.preserveUnknownFields {
			// TODO: switch over pruning and coercing at the root to schemaobjectmeta.Coerce too
			pruneOpts := structuralschema.UnknownFieldPathOptions{}
			if v.returnUnknownFieldPaths {
				pruneOpts.TrackUnknownFieldPaths = true
			}
			unknownFieldPaths = append(unknownFieldPaths, structuralpruning.PruneWithOptions(u.Object, v.structuralSchemas[gv.Version], true, pruneOpts)...)
			structuraldefaulting.PruneNonNullableNullsWithoutDefaults(u.Object, v.structuralSchemas[gv.Version])
		}

		err, paths := schemaobjectmeta.CoerceWithOptions(nil, u.Object, v.structuralSchemas[gv.Version], false, schemaobjectmeta.CoerceOptions{
			DropInvalidFields:       v.dropInvalidMetadata,
			ReturnUnknownFieldPaths: v.returnUnknownFieldPaths,
		})
		if err != nil {
			return nil, err
		}
		unknownFieldPaths = append(unknownFieldPaths, paths...)

		// fixup missing generation in very old CRs
		if v.repairGeneration && objectMeta.Generation == 0 {
			objectMeta.Generation = 1
		}
	}

	// restore meta fields, starting clean
	if foundKind {
		u.SetKind(kind)
	}
	if foundApiVersion {
		u.SetAPIVersion(apiVersion)
	}
	if foundObjectMeta {
		if err := schemaobjectmeta.SetObjectMeta(u.Object, objectMeta); err != nil {
			return nil, err
		}
	}

	return unknownFieldPaths, nil
}


// hasServedCRDVersion returns true if the given version is in the list of CRD's versions and the Served flag is set.
func hasServedCRDVersion(spec *apiextensionsv1.CustomResourceDefinitionSpec, version string) bool {
	for _, v := range spec.Versions {
		if v.Name == version {
			return v.Served
		}
	}
	return false
}


// buildOpenAPIModelsForApply constructs openapi models from any validation schemas specified in the custom resource,
// and merges it with the models defined in the static OpenAPI spec.
// Returns nil models ifthe static spec is nil, or an error is encountered.
func buildOpenAPIModelsForApply(staticOpenAPISpec map[string]*spec.Schema, crd *apiextensionsv1.CustomResourceDefinition) (map[string]*spec.Schema, error) {
	if staticOpenAPISpec == nil {
		return nil, nil
	}

	// Convert static spec to V3 format to be able to merge
	staticSpecV3 := &spec3.OpenAPI{
		Version: "3.0.0",
		Info: &spec.Info{
			InfoProps: spec.InfoProps{
				Title:   "Kubernetes CRD Swagger",
				Version: "v0.1.0",
			},
		},
		Components: &spec3.Components{
			Schemas: staticOpenAPISpec,
		},
	}

	specs := []*spec3.OpenAPI{staticSpecV3}
	for _, v := range crd.Spec.Versions {
		// Defaults are not pruned here, but before being served.
		// See flag description in builder.go for flag usage
		s, err := builder.BuildOpenAPIV3(crd, v.Name, builder.Options{})
		if err != nil {
			return nil, err
		}
		specs = append(specs, s)
	}

	mergedOpenAPI, err := builder.MergeSpecsV3(specs...)
	if err != nil {
		return nil, err
	}
	return mergedOpenAPI.Components.Schemas, nil
}


// getColumnsForVersion returns the columns for given version or nil.
// NOTE: the newly logically-defaulted columns is not pointing to the original CRD object.
// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through
// the original CRD object instead.
func getColumnsForVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) ([]apiextensionsv1.CustomResourceColumnDefinition, error) {
	for _, v := range crd.Spec.Versions {
		if version == v.Name {
			return serveDefaultColumnsIfEmpty(v.AdditionalPrinterColumns), nil
		}
	}
	return nil, fmt.Errorf("version %s not found in apiextensionsv1.CustomResourceDefinition: %v", version, crd.Name)
}


// getScaleColumnsForVersion returns 2 columns for the desired and actual number of replicas.
func getScaleColumnsForVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) ([]apiextensionsv1.CustomResourceColumnDefinition, error) {
	for _, v := range crd.Spec.Versions {
		if version != v.Name {
			continue
		}
		var cols []apiextensionsv1.CustomResourceColumnDefinition
		if v.Subresources != nil && v.Subresources.Scale != nil {
			if v.Subresources.Scale.SpecReplicasPath != "" {
				cols = append(cols, apiextensionsv1.CustomResourceColumnDefinition{
					Name:        "Desired",
					Type:        "integer",
					Description: "Number of desired replicas",
					JSONPath:    ".spec.replicas",
				})
			}
			if v.Subresources.Scale.StatusReplicasPath != "" {
				cols = append(cols, apiextensionsv1.CustomResourceColumnDefinition{
					Name:        "Available",
					Type:        "integer",
					Description: "Number of actual replicas",
					JSONPath:    ".status.replicas",
				})
			}
		}
		cols = append(cols, apiextensionsv1.CustomResourceColumnDefinition{
			Name:        "Age",
			Type:        "date",
			Description: swaggerMetadataDescriptions["creationTimestamp"],
			JSONPath:    ".metadata.creationTimestamp",
		})
		return cols, nil
	}
	return nil, fmt.Errorf("version %s not found in apiextensionsv1.CustomResourceDefinition: %v", version, crd.Name)
}


// serveDefaultColumnsIfEmpty applies logically defaulting to columns, if the input columns is empty.
// NOTE: in this way, the newly logically-defaulted columns is not pointing to the original CRD object.
// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through
// the original CRD object instead.
func serveDefaultColumnsIfEmpty(columns []apiextensionsv1.CustomResourceColumnDefinition) []apiextensionsv1.CustomResourceColumnDefinition {
	if len(columns) > 0 {
		return columns
	}
	return []apiextensionsv1.CustomResourceColumnDefinition{
		{Name: "Age", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"], JSONPath: ".metadata.creationTimestamp"},
	}
}


func getBaseEnv() (*cel.Env, error)

// This func is duplicated in k8s.io/apiserver/pkg/admission/plugin/cel/validator.go
// If any changes are made here, consider to make the same changes there as well.
func getBaseEnv() (*cel.Env, error) {
	initEnvOnce.Do(func() {
		var opts []cel.EnvOption
		opts = append(opts, cel.HomogeneousAggregateLiterals())
		// Validate function declarations once during base env initialization,
		// so they don't need to be evaluated each time a CEL rule is compiled.
		// This is a relatively expensive operation.
		opts = append(opts, cel.EagerlyValidateDeclarations(true), cel.DefaultUTCTimeZone(true))
		opts = append(opts, library.ExtensionLibs...)

		initEnv, initEnvErr = cel.NewEnv(opts...)
	})
	return initEnv, initEnvErr
}


// Compile compiles all the XValidations rules (without recursing into the schema) and returns a slice containing a
// CompilationResult for each ValidationRule, or an error. declType is expected to be a CEL DeclType corresponding
// to the structural schema.
// Each CompilationResult may contain:
//   - non-nil Program, nil Error: The program was compiled successfully
//   - nil Program, non-nil Error: Compilation resulted in an error
//   - nil Program, nil Error: The provided rule was empty so compilation was not attempted
//
// 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 Compile(s *schema.Structural, declType *apiservercel.DeclType, perCallLimit uint64) ([]CompilationResult, error) {
	t := time.Now()
	defer func() {
		metrics.Metrics.ObserveCompilation(time.Since(t))
	}()

	if len(s.Extensions.XValidations) == 0 {
		return nil, nil
	}
	celRules := s.Extensions.XValidations

	var propDecls []cel.EnvOption
	var root *apiservercel.DeclType
	var ok bool
	baseEnv, err := getBaseEnv()
	if err != nil {
		return nil, err
	}
	reg := apiservercel.NewRegistry(baseEnv)
	scopedTypeName := generateUniqueSelfTypeName()
	rt, err := apiservercel.NewRuleTypes(scopedTypeName, declType, reg)
	if err != nil {
		return nil, err
	}
	if rt == nil {
		return nil, nil
	}
	opts, err := rt.EnvOptions(baseEnv.TypeProvider())
	if err != nil {
		return nil, err
	}
	root, ok = rt.FindDeclType(scopedTypeName)
	if !ok {
		if declType == nil {
			return nil, fmt.Errorf("rule declared on schema that does not support validation rules type: '%s' x-kubernetes-preserve-unknown-fields: '%t'", s.Type, s.XPreserveUnknownFields)
		}
		root = declType.MaybeAssignTypeName(scopedTypeName)
	}
	propDecls = append(propDecls, cel.Variable(ScopedVarName, root.CelType()))
	propDecls = append(propDecls, cel.Variable(OldScopedVarName, root.CelType()))
	opts = append(opts, propDecls...)
	env, err := baseEnv.Extend(opts...)
	if err != nil {
		return nil, err
	}
	estimator := newCostEstimator(root)
	// compResults is the return value which saves a list of compilation results in the same order as x-kubernetes-validations rules.
	compResults := make([]CompilationResult, len(celRules))
	maxCardinality := maxCardinality(root.MinSerializedSize)
	for i, rule := range celRules {
		compResults[i] = compileRule(rule, env, perCallLimit, estimator, maxCardinality)
	}

	return compResults, nil
}


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


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


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


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


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


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


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


func (s *Structural) Type() string

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


func (s *Structural) Format() string

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


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


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
}


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


func (s *Structural) Default() any

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


func (s *Structural) MaxItems() *int64

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


func (s *Structural) MaxLength() *int64

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


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


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


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

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
}


func (s *Structural) Nullable() bool

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


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


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


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


func (s *Structural) XListType() string

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


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


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


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


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


func hasNewlines(s string) bool

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


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


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


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
}


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


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


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


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
}


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


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


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


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
}


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
}


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
}


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


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


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
}


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


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


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


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
}


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


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
}


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


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