Files
substrate/hack/tools/code-generator/third_party/k_k_141395.diff
T
Tim Hockin a8d83ee1b5 Use Kubernetes declarative validation (#1215)
This mostly eliminates the need to hand-write validation code.

This PR is a long series of commits which add DV for most of Actor and
all of Atespace.

Here is a map to the commits:
* The first few take a dep on a new Kubernetes tag, import the code into
third_party, and apply a single patch. Because we have different Go
modules for tools, I had to do it twice. When that patch lands, we can
revert these commits, but that won't be until the 1.38 cycle in a few
months.
* The next commits slowly add DV support, so a human can review each of
them in a reasonable amount of time. The emphasis is on great test
cases.
* I made a bad choice early on as to where to generate the code into, so
I moved it. Rebasing on that was exceedingly hard, so I left it as a
move.
* This required changing update/go-generate -> update/codegen -- we need
to get the ordering of tools right, which `go generate` does not
guarantee.
* Then I added a "middle" layer called "ServiceImpl" between the RPC and
storage layers. This allows things like workflow to call the same
business logic as the RPC layer, including validation. Lots of test
fixes.
* Then I finished the Create() and Update() paths for Actor. Those
represent the "right" (or closest to) way to implement resources, and
tests for validation.

I strongly encourage reviewers to read it commit-by-commit. Rebasing
this is VERY tedious, so the sooner it lands or dies completely, the
better. Then we can start converting the rest.

`./hack/run-tool.sh validation-gen --docs` will produce some docs on the
tool and the available tags.

@laoj2 @juli4n @EItanya @HavenXia
@lalitc375 @yongruilin @jpbetz  FYI
2026-08-25 20:24:59 -07:00

4578 lines
212 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
diff --git a/pkg/apis/admissionregistration/v1/zz_generated.validations.go b/pkg/apis/admissionregistration/v1/zz_generated.validations.go
index c2562dc0936b0..b4b638ed6458a 100644
--- a/pkg/apis/admissionregistration/v1/zz_generated.validations.go
+++ b/pkg/apis/admissionregistration/v1/zz_generated.validations.go
@@ -453,7 +453,7 @@ func Validate_ValidatingAdmissionPolicyStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -501,3 +501,8 @@ func Validate_ValidatingWebhookConfiguration(
// field admissionregistrationv1.ValidatingWebhookConfiguration.Webhooks has no validation
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go b/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go
index 93f66e798e3ab..a08534553e18a 100644
--- a/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go
+++ b/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go
@@ -389,7 +389,7 @@ func Validate_ValidatingAdmissionPolicyStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -403,3 +403,8 @@ func Validate_ValidatingAdmissionPolicyStatus(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go b/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go
index 3c1b50fa66d94..6d6d394f124b4 100644
--- a/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go
+++ b/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go
@@ -453,7 +453,7 @@ func Validate_ValidatingAdmissionPolicyStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -501,3 +501,8 @@ func Validate_ValidatingWebhookConfiguration(
// field admissionregistrationv1beta1.ValidatingWebhookConfiguration.Webhooks has no validation
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/batch/v1/zz_generated.validations.go b/pkg/apis/batch/v1/zz_generated.validations.go
index 0a830fdeb62b3..8f0b35bb016f5 100644
--- a/pkg/apis/batch/v1/zz_generated.validations.go
+++ b/pkg/apis/batch/v1/zz_generated.validations.go
@@ -403,7 +403,7 @@ func Validate_JobSchedulingConfiguration(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1alpha3.WorkloadPodGroupResourceClaim, b *schedulingv1alpha3.WorkloadPodGroupResourceClaim) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, v1alpha3.Validate_WorkloadPodGroupResourceClaim); len(e) != 0 {
+ }, deepEqualImpl_, v1alpha3.Validate_WorkloadPodGroupResourceClaim); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -583,3 +583,8 @@ func Validate_JobTemplateSpec(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/certificates/v1/zz_generated.validations.go b/pkg/apis/certificates/v1/zz_generated.validations.go
index 35af1f3579770..bb98173b9a2c2 100644
--- a/pkg/apis/certificates/v1/zz_generated.validations.go
+++ b/pkg/apis/certificates/v1/zz_generated.validations.go
@@ -392,7 +392,7 @@ func Validate_PodCertificateRequestStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -410,3 +410,8 @@ func Validate_PodCertificateRequestStatus(
// field certificatesv1.PodCertificateRequestStatus.NotAfter has no validation
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/certificates/v1beta1/zz_generated.validations.go b/pkg/apis/certificates/v1beta1/zz_generated.validations.go
index 9af5455cf75c3..d64104e9a2e97 100644
--- a/pkg/apis/certificates/v1beta1/zz_generated.validations.go
+++ b/pkg/apis/certificates/v1beta1/zz_generated.validations.go
@@ -392,7 +392,7 @@ func Validate_PodCertificateRequestStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -410,3 +410,8 @@ func Validate_PodCertificateRequestStatus(
// field certificatesv1beta1.PodCertificateRequestStatus.NotAfter has no validation
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/core/v1/zz_generated.validations.go b/pkg/apis/core/v1/zz_generated.validations.go
index cd8ad94525e30..8e0d53348368e 100644
--- a/pkg/apis/core/v1/zz_generated.validations.go
+++ b/pkg/apis/core/v1/zz_generated.validations.go
@@ -855,7 +855,7 @@ func Validate_NodeAllocatableResourceClaimStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *corev1.NodeAllocatableMappedResources, b *corev1.NodeAllocatableMappedResources) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_NodeAllocatableMappedResources); len(e) != 0 {
+ }, deepEqualImpl_, Validate_NodeAllocatableMappedResources); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -897,7 +897,7 @@ func Validate_NodeAllocatableResourceClaimStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *corev1.NodeAllocatableOverheadResources, b *corev1.NodeAllocatableOverheadResources) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_NodeAllocatableOverheadResources); len(e) != 0 {
+ }, deepEqualImpl_, Validate_NodeAllocatableOverheadResources); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -985,7 +985,8 @@ func Validate_NodeSpec(
if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoUnset, validate.NoModify).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoUnset, validate.NoModify).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -1390,7 +1391,7 @@ func Validate_PodSpec(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *corev1.EvictionResponder, b *corev1.EvictionResponder) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_EvictionResponder); len(e) != 0 {
+ func(a *corev1.EvictionResponder, b *corev1.EvictionResponder) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_EvictionResponder); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1462,7 +1463,7 @@ func Validate_PodStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *corev1.NodeAllocatableResourceClaimStatus, b *corev1.NodeAllocatableResourceClaimStatus) bool {
return a.ResourceClaimName == b.ResourceClaimName
- }, validate.SemanticDeepEqual, Validate_NodeAllocatableResourceClaimStatus); len(e) != 0 {
+ }, deepEqualImpl_, Validate_NodeAllocatableResourceClaimStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1500,7 +1501,7 @@ func Validate_PodStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *corev1.PodVolumeHealth, b *corev1.PodVolumeHealth) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_PodVolumeHealth); len(e) != 0 {
+ func(a *corev1.PodVolumeHealth, b *corev1.PodVolumeHealth) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_PodVolumeHealth); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2250,3 +2251,8 @@ func Validate_VolumeHealthStatusType(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/flowcontrol/v1/zz_generated.validations.go b/pkg/apis/flowcontrol/v1/zz_generated.validations.go
index d8484235332a3..8b590e900f5e2 100644
--- a/pkg/apis/flowcontrol/v1/zz_generated.validations.go
+++ b/pkg/apis/flowcontrol/v1/zz_generated.validations.go
@@ -307,7 +307,7 @@ func Validate_PriorityLevelConfigurationSpec(
},
func(obj *flowcontrolv1.PriorityLevelConfigurationSpec) flowcontrolv1.PriorityLevelEnablement {
return obj.Type
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.ExemptPriorityLevelConfiguration) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...)
@@ -338,7 +338,7 @@ func Validate_PriorityLevelConfigurationSpec(
},
func(obj *flowcontrolv1.PriorityLevelConfigurationSpec) flowcontrolv1.PriorityLevelEnablement {
return obj.Type
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1.LimitedPriorityLevelConfiguration) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...)
@@ -454,3 +454,8 @@ func Validate_PriorityLevelConfigurationSpec(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go b/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go
index b5bf044c847af..1079f7f7b4ae7 100644
--- a/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go
+++ b/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go
@@ -309,7 +309,7 @@ func Validate_PriorityLevelConfigurationSpec(
},
func(obj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) flowcontrolv1beta1.PriorityLevelEnablement {
return obj.Type
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.ExemptPriorityLevelConfiguration) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...)
@@ -340,7 +340,7 @@ func Validate_PriorityLevelConfigurationSpec(
},
func(obj *flowcontrolv1beta1.PriorityLevelConfigurationSpec) flowcontrolv1beta1.PriorityLevelEnablement {
return obj.Type
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta1.LimitedPriorityLevelConfiguration) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...)
@@ -456,3 +456,8 @@ func Validate_PriorityLevelConfigurationSpec(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go b/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go
index c598fd02c48cb..56b84e881aa46 100644
--- a/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go
+++ b/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go
@@ -309,7 +309,7 @@ func Validate_PriorityLevelConfigurationSpec(
},
func(obj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) flowcontrolv1beta2.PriorityLevelEnablement {
return obj.Type
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.ExemptPriorityLevelConfiguration) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...)
@@ -340,7 +340,7 @@ func Validate_PriorityLevelConfigurationSpec(
},
func(obj *flowcontrolv1beta2.PriorityLevelConfigurationSpec) flowcontrolv1beta2.PriorityLevelEnablement {
return obj.Type
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta2.LimitedPriorityLevelConfiguration) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...)
@@ -456,3 +456,8 @@ func Validate_PriorityLevelConfigurationSpec(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go b/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go
index b7dece4e10418..213f621973b55 100644
--- a/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go
+++ b/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go
@@ -309,7 +309,7 @@ func Validate_PriorityLevelConfigurationSpec(
},
func(obj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) flowcontrolv1beta3.PriorityLevelEnablement {
return obj.Type
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.ExemptPriorityLevelConfiguration) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...)
@@ -340,7 +340,7 @@ func Validate_PriorityLevelConfigurationSpec(
},
func(obj *flowcontrolv1beta3.PriorityLevelConfigurationSpec) flowcontrolv1beta3.PriorityLevelEnablement {
return obj.Type
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *flowcontrolv1beta3.LimitedPriorityLevelConfiguration) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...)
@@ -456,3 +456,8 @@ func Validate_PriorityLevelConfigurationSpec(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/lifecycle/v1alpha1/zz_generated.validations.go b/pkg/apis/lifecycle/v1alpha1/zz_generated.validations.go
index dbc18b7e8c986..2335b86d38d80 100644
--- a/pkg/apis/lifecycle/v1alpha1/zz_generated.validations.go
+++ b/pkg/apis/lifecycle/v1alpha1/zz_generated.validations.go
@@ -552,7 +552,7 @@ func Validate_EvictionRequestStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -732,7 +732,7 @@ func Validate_EvictionStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -858,7 +858,7 @@ func Validate_EvictionStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *lifecyclev1alpha1.TargetResponder, b *lifecyclev1alpha1.TargetResponder) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_TargetResponder); len(e) != 0 {
+ }, deepEqualImpl_, Validate_TargetResponder); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -904,7 +904,7 @@ func Validate_EvictionStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *lifecyclev1alpha1.ResponderStatus, b *lifecyclev1alpha1.ResponderStatus) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_ResponderStatus); len(e) != 0 {
+ }, deepEqualImpl_, Validate_ResponderStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1374,3 +1374,8 @@ func Validate_TargetResponder(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/lifecycle/validation/validation.go b/pkg/apis/lifecycle/validation/validation.go
index bf3f2e29df13b..28bb04bfb9709 100644
--- a/pkg/apis/lifecycle/validation/validation.go
+++ b/pkg/apis/lifecycle/validation/validation.go
@@ -23,6 +23,7 @@ import (
"strings"
"time"
+ "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate"
@@ -487,7 +488,7 @@ func ValidateEvictionStatusResponder(status, oldStatus *lifecycle.ResponderStatu
oldDefaultedStatus = *oldStatus // +k8s:verify-mutation:reason=clone
}
- if oldStatus != nil && !validate.SemanticDeepEqual(status, oldStatus) && opts.responderState != lifecycle.ResponderStateActive {
+ if oldStatus != nil && !equality.Semantic.DeepEqual(status, oldStatus) && opts.responderState != lifecycle.ResponderStateActive {
// immutable; changes to the ResponderStatus are only allowed by the active responder or during initialization
return append(allErrs, field.Invalid(fldPath, status, validation.FieldImmutableErrorMsg).WithOrigin("immutable"))
}
diff --git a/pkg/apis/lifecycle/validation/validation_test.go b/pkg/apis/lifecycle/validation/validation_test.go
index 0c92b20367be6..0f962d44a5a54 100644
--- a/pkg/apis/lifecycle/validation/validation_test.go
+++ b/pkg/apis/lifecycle/validation/validation_test.go
@@ -23,7 +23,7 @@ import (
"time"
"github.com/google/go-cmp/cmp"
- "k8s.io/apimachinery/pkg/api/validate"
+ "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -143,7 +143,7 @@ func TestValidateEvictionRequestStatusUpdate(t *testing.T) {
}
for name, tc := range successCases {
t.Run(name, func(t *testing.T) {
- if validate.SemanticDeepEqual(tc.oldInput, tc.input) {
+ if equality.Semantic.DeepEqual(tc.oldInput, tc.input) {
t.Errorf("Expected oldInput and input to differ")
}
oldEvictionRequest := mkValidEvictionRequest()
@@ -812,7 +812,7 @@ func TestValidateEvictionStatusUpdate(t *testing.T) {
}
for name, tc := range successCases {
t.Run(name, func(t *testing.T) {
- if !tc.explicitEmptyUpdate && validate.SemanticDeepEqual(tc.oldInput, tc.input) {
+ if !tc.explicitEmptyUpdate && equality.Semantic.DeepEqual(tc.oldInput, tc.input) {
t.Errorf("Expected oldInput and input to differ")
}
// Ensure that the validation doesn't depend on the order of list elements.
diff --git a/pkg/apis/policy/v1/zz_generated.validations.go b/pkg/apis/policy/v1/zz_generated.validations.go
index f153234e54e76..df2d2e74fe77f 100644
--- a/pkg/apis/policy/v1/zz_generated.validations.go
+++ b/pkg/apis/policy/v1/zz_generated.validations.go
@@ -155,7 +155,7 @@ func Validate_PodDisruptionBudgetStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -169,3 +169,8 @@ func Validate_PodDisruptionBudgetStatus(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/policy/v1beta1/zz_generated.validations.go b/pkg/apis/policy/v1beta1/zz_generated.validations.go
index def9d2dd2d8a1..369d266ce91c1 100644
--- a/pkg/apis/policy/v1beta1/zz_generated.validations.go
+++ b/pkg/apis/policy/v1beta1/zz_generated.validations.go
@@ -155,7 +155,7 @@ func Validate_PodDisruptionBudgetStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -169,3 +169,8 @@ func Validate_PodDisruptionBudgetStatus(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/resource/v1/zz_generated.validations.go b/pkg/apis/resource/v1/zz_generated.validations.go
index 5353b062bb69e..716aa0f343c96 100644
--- a/pkg/apis/resource/v1/zz_generated.validations.go
+++ b/pkg/apis/resource/v1/zz_generated.validations.go
@@ -190,7 +190,7 @@ func Validate_AllocatedDeviceStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -386,7 +386,7 @@ func Validate_Device(
return // do not proceed
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_DeviceAttribute); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -434,7 +434,7 @@ func Validate_Device(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *resourcev1.DeviceCounterConsumption, b *resourcev1.DeviceCounterConsumption) bool {
return a.CounterSet == b.CounterSet
- }, validate.SemanticDeepEqual, Validate_DeviceCounterConsumption); len(e) != 0 {
+ }, deepEqualImpl_, Validate_DeviceCounterConsumption); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -570,7 +570,7 @@ func Validate_Device(
return // do not proceed
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_NodeAllocatableResource); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_NodeAllocatableResource); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1108,7 +1108,7 @@ func Validate_DeviceClaim(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *resourcev1.DeviceRequest, b *resourcev1.DeviceRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceRequest); len(e) != 0 {
+ func(a *resourcev1.DeviceRequest, b *resourcev1.DeviceRequest) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_DeviceRequest); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1867,7 +1867,7 @@ func Validate_DeviceRequest(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *resourcev1.DeviceSubRequest, b *resourcev1.DeviceSubRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceSubRequest); len(e) != 0 {
+ func(a *resourcev1.DeviceSubRequest, b *resourcev1.DeviceSubRequest) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_DeviceSubRequest); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2506,7 +2506,7 @@ func Validate_DeviceTaintRuleStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3440,7 +3440,7 @@ func Validate_ResourceClaimStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *resourcev1.AllocatedDeviceStatus, b *resourcev1.AllocatedDeviceStatus) bool {
return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID))
- }, validate.SemanticDeepEqual, Validate_AllocatedDeviceStatus); len(e) != 0 {
+ }, deepEqualImpl_, Validate_AllocatedDeviceStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3674,7 +3674,7 @@ func Validate_ResourceSliceSpec(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *resourcev1.CounterSet, b *resourcev1.CounterSet) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_CounterSet); len(e) != 0 {
+ func(a *resourcev1.CounterSet, b *resourcev1.CounterSet) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_CounterSet); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3777,3 +3777,8 @@ func Validate_SkipNodeOperation(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/resource/v1alpha3/zz_generated.validations.go b/pkg/apis/resource/v1alpha3/zz_generated.validations.go
index 26768c1f0834f..ec13b717c6ee4 100644
--- a/pkg/apis/resource/v1alpha3/zz_generated.validations.go
+++ b/pkg/apis/resource/v1alpha3/zz_generated.validations.go
@@ -277,7 +277,7 @@ func Validate_DeviceTaintRuleStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -776,7 +776,7 @@ func Validate_PoolStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *resourcev1alpha3.PartitionTypeStatus, b *resourcev1alpha3.PartitionTypeStatus) bool {
return a.Attribute == b.Attribute && a.Type == b.Type
- }, validate.SemanticDeepEqual, Validate_PartitionTypeStatus); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PartitionTypeStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1165,7 +1165,7 @@ func Validate_ResourcePoolStatusRequestStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1413,3 +1413,8 @@ func Validate_ShareableSummaryStatus(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/resource/v1beta1/zz_generated.validations.go b/pkg/apis/resource/v1beta1/zz_generated.validations.go
index 62ab9a8bfc35c..c78f404aeddd8 100644
--- a/pkg/apis/resource/v1beta1/zz_generated.validations.go
+++ b/pkg/apis/resource/v1beta1/zz_generated.validations.go
@@ -175,7 +175,7 @@ func Validate_AllocatedDeviceStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -296,7 +296,7 @@ func Validate_BasicDevice(
return // do not proceed
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_DeviceAttribute); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -344,7 +344,7 @@ func Validate_BasicDevice(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *resourcev1beta1.DeviceCounterConsumption, b *resourcev1beta1.DeviceCounterConsumption) bool {
return a.CounterSet == b.CounterSet
- }, validate.SemanticDeepEqual, Validate_DeviceCounterConsumption); len(e) != 0 {
+ }, deepEqualImpl_, Validate_DeviceCounterConsumption); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -480,7 +480,7 @@ func Validate_BasicDevice(
return // do not proceed
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_NodeAllocatableResource); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_NodeAllocatableResource); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1132,7 +1132,7 @@ func Validate_DeviceClaim(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *resourcev1beta1.DeviceRequest, b *resourcev1beta1.DeviceRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceRequest); len(e) != 0 {
+ func(a *resourcev1beta1.DeviceRequest, b *resourcev1beta1.DeviceRequest) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_DeviceRequest); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1931,7 +1931,7 @@ func Validate_DeviceRequest(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *resourcev1beta1.DeviceSubRequest, b *resourcev1beta1.DeviceSubRequest) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_DeviceSubRequest); len(e) != 0 {
+ }, deepEqualImpl_, Validate_DeviceSubRequest); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3271,7 +3271,7 @@ func Validate_ResourceClaimStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *resourcev1beta1.AllocatedDeviceStatus, b *resourcev1beta1.AllocatedDeviceStatus) bool {
return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID))
- }, validate.SemanticDeepEqual, Validate_AllocatedDeviceStatus); len(e) != 0 {
+ }, deepEqualImpl_, Validate_AllocatedDeviceStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3505,7 +3505,7 @@ func Validate_ResourceSliceSpec(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *resourcev1beta1.CounterSet, b *resourcev1beta1.CounterSet) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_CounterSet); len(e) != 0 {
+ func(a *resourcev1beta1.CounterSet, b *resourcev1beta1.CounterSet) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_CounterSet); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3608,3 +3608,8 @@ func Validate_SkipNodeOperation(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/resource/v1beta2/zz_generated.validations.go b/pkg/apis/resource/v1beta2/zz_generated.validations.go
index 5858ddd09b4f4..c4a76b3e8f07f 100644
--- a/pkg/apis/resource/v1beta2/zz_generated.validations.go
+++ b/pkg/apis/resource/v1beta2/zz_generated.validations.go
@@ -190,7 +190,7 @@ func Validate_AllocatedDeviceStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -386,7 +386,7 @@ func Validate_Device(
return // do not proceed
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_DeviceAttribute); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -434,7 +434,7 @@ func Validate_Device(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *resourcev1beta2.DeviceCounterConsumption, b *resourcev1beta2.DeviceCounterConsumption) bool {
return a.CounterSet == b.CounterSet
- }, validate.SemanticDeepEqual, Validate_DeviceCounterConsumption); len(e) != 0 {
+ }, deepEqualImpl_, Validate_DeviceCounterConsumption); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -570,7 +570,7 @@ func Validate_Device(
return // do not proceed
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_NodeAllocatableResource); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_NodeAllocatableResource); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1108,7 +1108,7 @@ func Validate_DeviceClaim(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *resourcev1beta2.DeviceRequest, b *resourcev1beta2.DeviceRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceRequest); len(e) != 0 {
+ func(a *resourcev1beta2.DeviceRequest, b *resourcev1beta2.DeviceRequest) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_DeviceRequest); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1871,7 +1871,7 @@ func Validate_DeviceRequest(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *resourcev1beta2.DeviceSubRequest, b *resourcev1beta2.DeviceSubRequest) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_DeviceSubRequest); len(e) != 0 {
+ }, deepEqualImpl_, Validate_DeviceSubRequest); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2510,7 +2510,7 @@ func Validate_DeviceTaintRuleStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3444,7 +3444,7 @@ func Validate_ResourceClaimStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *resourcev1beta2.AllocatedDeviceStatus, b *resourcev1beta2.AllocatedDeviceStatus) bool {
return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID))
- }, validate.SemanticDeepEqual, Validate_AllocatedDeviceStatus); len(e) != 0 {
+ }, deepEqualImpl_, Validate_AllocatedDeviceStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3678,7 +3678,7 @@ func Validate_ResourceSliceSpec(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *resourcev1beta2.CounterSet, b *resourcev1beta2.CounterSet) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_CounterSet); len(e) != 0 {
+ func(a *resourcev1beta2.CounterSet, b *resourcev1beta2.CounterSet) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_CounterSet); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3781,3 +3781,8 @@ func Validate_SkipNodeOperation(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/scheduling/v1alpha3/zz_generated.validations.go b/pkg/apis/scheduling/v1alpha3/zz_generated.validations.go
index 797660524af64..ae573cfc6b99b 100644
--- a/pkg/apis/scheduling/v1alpha3/zz_generated.validations.go
+++ b/pkg/apis/scheduling/v1alpha3/zz_generated.validations.go
@@ -752,7 +752,7 @@ func Validate_CompositePodGroupStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1047,7 +1047,7 @@ func Validate_CompositePodGroupTemplate(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1alpha3.PodGroupTemplate, b *schedulingv1alpha3.PodGroupTemplate) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupTemplate); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1100,7 +1100,7 @@ func Validate_CompositePodGroupTemplate(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1alpha3.CompositePodGroupTemplate, b *schedulingv1alpha3.CompositePodGroupTemplate) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_CompositePodGroupTemplate); len(e) != 0 {
+ }, deepEqualImpl_, Validate_CompositePodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1816,7 +1816,7 @@ func Validate_PodGroupSpec(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1alpha3.PodGroupResourceClaim, b *schedulingv1alpha3.PodGroupResourceClaim) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaim); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupResourceClaim); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2010,7 +2010,7 @@ func Validate_PodGroupStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2056,7 +2056,7 @@ func Validate_PodGroupStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1alpha3.PodGroupResourceClaimStatus, b *schedulingv1alpha3.PodGroupResourceClaimStatus) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaimStatus); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupResourceClaimStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2210,7 +2210,7 @@ func Validate_PodGroupTemplate(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1alpha3.PodGroupResourceClaim, b *schedulingv1alpha3.PodGroupResourceClaim) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaim); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupResourceClaim); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3171,7 +3171,7 @@ func Validate_WorkloadSpec(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1alpha3.PodGroupTemplate, b *schedulingv1alpha3.PodGroupTemplate) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupTemplate); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3231,7 +3231,7 @@ func Validate_WorkloadSpec(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1alpha3.CompositePodGroupTemplate, b *schedulingv1alpha3.CompositePodGroupTemplate) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_CompositePodGroupTemplate); len(e) != 0 {
+ }, deepEqualImpl_, Validate_CompositePodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3245,3 +3245,8 @@ func Validate_WorkloadSpec(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/scheduling/v1beta1/zz_generated.validations.go b/pkg/apis/scheduling/v1beta1/zz_generated.validations.go
index 6914741407314..ccc3d7ff6a927 100644
--- a/pkg/apis/scheduling/v1beta1/zz_generated.validations.go
+++ b/pkg/apis/scheduling/v1beta1/zz_generated.validations.go
@@ -632,7 +632,7 @@ func Validate_CompositePodGroupTemplate(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1beta1.PodGroupTemplate, b *schedulingv1beta1.PodGroupTemplate) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupTemplate); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -685,7 +685,7 @@ func Validate_CompositePodGroupTemplate(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1beta1.CompositePodGroupTemplate, b *schedulingv1beta1.CompositePodGroupTemplate) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_CompositePodGroupTemplate); len(e) != 0 {
+ }, deepEqualImpl_, Validate_CompositePodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1401,7 +1401,7 @@ func Validate_PodGroupSpec(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1beta1.PodGroupResourceClaim, b *schedulingv1beta1.PodGroupResourceClaim) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaim); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupResourceClaim); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1595,7 +1595,7 @@ func Validate_PodGroupStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1641,7 +1641,7 @@ func Validate_PodGroupStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1beta1.PodGroupResourceClaimStatus, b *schedulingv1beta1.PodGroupResourceClaimStatus) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaimStatus); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupResourceClaimStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1795,7 +1795,7 @@ func Validate_PodGroupTemplate(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1beta1.PodGroupResourceClaim, b *schedulingv1beta1.PodGroupResourceClaim) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaim); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupResourceClaim); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2379,7 +2379,7 @@ func Validate_WorkloadSpec(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1beta1.PodGroupTemplate, b *schedulingv1beta1.PodGroupTemplate) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_PodGroupTemplate); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2439,7 +2439,7 @@ func Validate_WorkloadSpec(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *schedulingv1beta1.CompositePodGroupTemplate, b *schedulingv1beta1.CompositePodGroupTemplate) bool {
return a.Name == b.Name
- }, validate.SemanticDeepEqual, Validate_CompositePodGroupTemplate); len(e) != 0 {
+ }, deepEqualImpl_, Validate_CompositePodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2453,3 +2453,8 @@ func Validate_WorkloadSpec(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/storagemigration/v1/zz_generated.validations.go b/pkg/apis/storagemigration/v1/zz_generated.validations.go
index 3462185867b85..480bbb54c4f61 100644
--- a/pkg/apis/storagemigration/v1/zz_generated.validations.go
+++ b/pkg/apis/storagemigration/v1/zz_generated.validations.go
@@ -148,7 +148,7 @@ func Validate_StorageVersionMigrationStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -163,3 +163,8 @@ func Validate_StorageVersionMigrationStatus(
// field storagemigrationv1.StorageVersionMigrationStatus.ResourceVersion has no validation
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/pkg/apis/storagemigration/v1beta1/zz_generated.validations.go b/pkg/apis/storagemigration/v1beta1/zz_generated.validations.go
index 0f4a1e3feedd3..b76128cb3ba9f 100644
--- a/pkg/apis/storagemigration/v1beta1/zz_generated.validations.go
+++ b/pkg/apis/storagemigration/v1beta1/zz_generated.validations.go
@@ -148,7 +148,7 @@ func Validate_StorageVersionMigrationStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -163,3 +163,8 @@ func Validate_StorageVersionMigrationStatus(
// field storagemigrationv1beta1.StorageVersionMigrationStatus.ResourceVersion has no validation
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/api/resource/v1/zz_generated.validations.go b/staging/src/k8s.io/api/resource/v1/zz_generated.validations.go
index b771d5b92a349..2c77f7412ac17 100644
--- a/staging/src/k8s.io/api/resource/v1/zz_generated.validations.go
+++ b/staging/src/k8s.io/api/resource/v1/zz_generated.validations.go
@@ -104,7 +104,7 @@ func Validate_AllocatedDeviceStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -300,7 +300,7 @@ func Validate_Device(
return // do not proceed
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_DeviceAttribute); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -348,7 +348,7 @@ func Validate_Device(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *DeviceCounterConsumption, b *DeviceCounterConsumption) bool {
return a.CounterSet == b.CounterSet
- }, validate.SemanticDeepEqual, Validate_DeviceCounterConsumption); len(e) != 0 {
+ }, deepEqualImpl_, Validate_DeviceCounterConsumption); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -484,7 +484,7 @@ func Validate_Device(
return // do not proceed
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_NodeAllocatableResource); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_NodeAllocatableResource); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1022,7 +1022,7 @@ func Validate_DeviceClaim(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *DeviceRequest, b *DeviceRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceRequest); len(e) != 0 {
+ func(a *DeviceRequest, b *DeviceRequest) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_DeviceRequest); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1711,7 +1711,7 @@ func Validate_DeviceRequest(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *DeviceSubRequest, b *DeviceSubRequest) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_DeviceSubRequest); len(e) != 0 {
+ func(a *DeviceSubRequest, b *DeviceSubRequest) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_DeviceSubRequest); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2273,7 +2273,7 @@ func Validate_DeviceTaintRuleStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *metav1.Condition, b *metav1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3119,7 +3119,7 @@ func Validate_ResourceClaimStatus(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *AllocatedDeviceStatus, b *AllocatedDeviceStatus) bool {
return a.Driver == b.Driver && a.Device == b.Device && a.Pool == b.Pool && ((a.ShareID == nil && b.ShareID == nil) || (a.ShareID != nil && b.ShareID != nil && *a.ShareID == *b.ShareID))
- }, validate.SemanticDeepEqual, Validate_AllocatedDeviceStatus); len(e) != 0 {
+ }, deepEqualImpl_, Validate_AllocatedDeviceStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3243,7 +3243,7 @@ func Validate_ResourceSliceSpec(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *CounterSet, b *CounterSet) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_CounterSet); len(e) != 0 {
+ func(a *CounterSet, b *CounterSet) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_CounterSet); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3346,3 +3346,8 @@ func Validate_SkipNodeOperation(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/api/scheduling/v1alpha3/zz_generated.validations.go b/staging/src/k8s.io/api/scheduling/v1alpha3/zz_generated.validations.go
index 3202dbe2bea07..b7e1aa6954d65 100644
--- a/staging/src/k8s.io/api/scheduling/v1alpha3/zz_generated.validations.go
+++ b/staging/src/k8s.io/api/scheduling/v1alpha3/zz_generated.validations.go
@@ -696,7 +696,7 @@ func Validate_CompositePodGroupStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -985,7 +985,7 @@ func Validate_CompositePodGroupTemplate(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *PodGroupTemplate, b *PodGroupTemplate) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_PodGroupTemplate); len(e) != 0 {
+ func(a *PodGroupTemplate, b *PodGroupTemplate) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_PodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1032,7 +1032,7 @@ func Validate_CompositePodGroupTemplate(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *CompositePodGroupTemplate, b *CompositePodGroupTemplate) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_CompositePodGroupTemplate); len(e) != 0 {
+ func(a *CompositePodGroupTemplate, b *CompositePodGroupTemplate) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_CompositePodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1744,7 +1744,7 @@ func Validate_PodGroupSpec(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *PodGroupResourceClaim, b *PodGroupResourceClaim) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaim); len(e) != 0 {
+ func(a *PodGroupResourceClaim, b *PodGroupResourceClaim) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_PodGroupResourceClaim); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1938,7 +1938,7 @@ func Validate_PodGroupStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, validate.SemanticDeepEqual, validation.Validate_Condition); len(e) != 0 {
+ func(a *v1.Condition, b *v1.Condition) bool { return a.Type == b.Type }, deepEqualImpl_, validation.Validate_Condition); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -1980,7 +1980,7 @@ func Validate_PodGroupStatus(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *PodGroupResourceClaimStatus, b *PodGroupResourceClaimStatus) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaimStatus); len(e) != 0 {
+ func(a *PodGroupResourceClaimStatus, b *PodGroupResourceClaimStatus) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_PodGroupResourceClaimStatus); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -2130,7 +2130,7 @@ func Validate_PodGroupTemplate(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *PodGroupResourceClaim, b *PodGroupResourceClaim) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_PodGroupResourceClaim); len(e) != 0 {
+ func(a *PodGroupResourceClaim, b *PodGroupResourceClaim) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_PodGroupResourceClaim); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3298,7 +3298,7 @@ func Validate_WorkloadSpec(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *PodGroupTemplate, b *PodGroupTemplate) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_PodGroupTemplate); len(e) != 0 {
+ func(a *PodGroupTemplate, b *PodGroupTemplate) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_PodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3352,7 +3352,7 @@ func Validate_WorkloadSpec(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *CompositePodGroupTemplate, b *CompositePodGroupTemplate) bool { return a.Name == b.Name }, validate.SemanticDeepEqual, Validate_CompositePodGroupTemplate); len(e) != 0 {
+ func(a *CompositePodGroupTemplate, b *CompositePodGroupTemplate) bool { return a.Name == b.Name }, deepEqualImpl_, Validate_CompositePodGroupTemplate); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -3366,3 +3366,8 @@ func Validate_WorkloadSpec(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/each.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/each.go
index 2930c21836cc7..25eef87562098 100644
--- a/staging/src/k8s.io/apimachinery/pkg/api/validate/each.go
+++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/each.go
@@ -18,6 +18,7 @@ package validate
import (
"context"
+ "reflect"
"slices"
"sort"
@@ -163,6 +164,36 @@ func EachMapVal[K ~string, V any](ctx context.Context, op operation.Operation, f
return errs
}
+// EachPtrMapVal validates each value in newMap (which is a map of pointers)
+// using the specified validation function, passing the corresponding old value
+// from oldMap if the key exists in oldMap.
+// For update operations, it implements validation ratcheting by skipping validation
+// when the old value exists and the equiv function confirms the values are equivalent.
+//
+// The equiv function will never be called with nil arguments.
+//
+// If equiv is nil, value-based ratcheting is disabled and all values will be validated.
+func EachPtrMapVal[K ~string, V any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]*V,
+ equiv MatchFunc[*V], validator ValidateFunc[*V]) field.ErrorList {
+ var errs field.ErrorList
+ for key, val := range newMap {
+ if val == nil {
+ // Ignore nil items; they are supposed to have been checked by PtrMapNoNils.
+ continue
+ }
+
+ old := oldMap[key]
+
+ // If the operation is an update, for validation ratcheting, skip re-validating if the old
+ // value is found and the equiv function confirms the values are equivalent.
+ if op.Type == operation.Update && old != nil && equiv != nil && equiv(val, old) {
+ continue
+ }
+ errs = append(errs, validator(ctx, op, fldPath.Key(string(key)), val, old)...)
+ }
+ return errs
+}
+
// EachMapKey validates each element of newMap with the specified
// validation function.
func EachMapKey[K ~string, T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]T,
@@ -261,10 +292,22 @@ func PtrSliceUnique[T any](_ context.Context, _ operation.Operation, fldPath *fi
// arguments of type interface{}/any. The wrapper satisfies the type
// constraints of MatchFunc while leveraging the underlying semantic equality
// logic. It can be used by any other function that needs to call DeepEqual.
+//
+// Deprecated: Callers should use equality.Semantic.DeepEqual directly.
func SemanticDeepEqual[T any](a, b T) bool {
return equality.Semantic.DeepEqual(a, b)
}
+// ReflectDeepEqual is a MatchFunc that uses reflect.DeepEqual to compare two
+// values.
+// This wrapper is needed because MatchFunc requires a function that takes two
+// arguments of specific type T, while reflect.DeepEqual takes arguments of
+// type interface{}/any. It can be used by any other function that needs to
+// call DeepEqual.
+func ReflectDeepEqual[T any](a, b T) bool {
+ return reflect.DeepEqual(a, b)
+}
+
// DirectEqual is a MatchFunc that dereferences two pointers and uses the ==
// operator to compare the values. If both pointers are nil, it returns true.
// If one pointer is nil and the other is not, it returns false.
@@ -290,3 +333,14 @@ func PtrSliceNoNils[T any](_ context.Context, _ operation.Operation, fldPath *fi
}
return
}
+
+// PtrMapNoNils returns a Required error for each nil element in a map of
+// pointers.
+func PtrMapNoNils[K ~string, V any](_ context.Context, _ operation.Operation, fldPath *field.Path, newMap, _ map[K]*V) (errs field.ErrorList) {
+ for key, val := range newMap {
+ if val == nil {
+ errs = append(errs, field.Required(fldPath.Key(string(key)), ""))
+ }
+ }
+ return
+}
diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/each_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/each_test.go
index 3b98fcb0460bd..a31e8863cc183 100644
--- a/staging/src/k8s.io/apimachinery/pkg/api/validate/each_test.go
+++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/each_test.go
@@ -137,7 +137,7 @@ func TestEachValSliceValRatcheting(t *testing.T) {
[]TestStruct{
{11, "a"}, {13, "c"}, {12, "b"},
},
- SemanticDeepEqual,
+ ReflectDeepEqual,
nil,
)
testEachValSliceValRatcheting(t, "ComparableStruct less data in new, exist in old",
@@ -181,7 +181,7 @@ func TestEachValSliceValRatcheting(t *testing.T) {
[]NonComparableStruct{
{I: 11, S: []string{"a"}}, {I: 13, S: []string{"c"}}, {I: 12, S: []string{"b"}},
},
- SemanticDeepEqual,
+ ReflectDeepEqual,
nil,
)
testEachValSliceValRatcheting(t, "NonComparableStructWithKey same data different order",
@@ -194,7 +194,7 @@ func TestEachValSliceValRatcheting(t *testing.T) {
MatchFunc[*NonComparableStructWithKey](func(a, b *NonComparableStructWithKey) bool {
return a.Key == b.Key
}),
- SemanticDeepEqual,
+ ReflectDeepEqual,
)
}
@@ -276,7 +276,7 @@ func TestEachMapValRatcheting(t *testing.T) {
"three": {I: 13, S: []string{"c"}},
"two": {I: 12, S: []string{"b"}},
},
- SemanticDeepEqual,
+ ReflectDeepEqual,
0,
)
testEachMapValRatcheting(t, "non comparable value, less data in new, exist in old",
@@ -289,7 +289,7 @@ func TestEachMapValRatcheting(t *testing.T) {
"one": {I: 11, S: []string{"a"}},
"three": {I: 13, S: []string{"c"}},
},
- SemanticDeepEqual,
+ ReflectDeepEqual,
0,
)
testEachMapValRatcheting(t, "non comparable value, new data, not exist in old",
@@ -304,7 +304,7 @@ func TestEachMapValRatcheting(t *testing.T) {
"two": {I: 12, S: []string{"b"}},
"four": {I: 14, S: []string{"d"}},
},
- SemanticDeepEqual,
+ ReflectDeepEqual,
1,
)
testEachMapValRatcheting(t, "struct with pointer field, same value different pointer",
@@ -316,7 +316,7 @@ func TestEachMapValRatcheting(t *testing.T) {
"one": {I: 11, P: new(1)},
"two": {I: 12, P: new(2)},
},
- SemanticDeepEqual,
+ ReflectDeepEqual,
0,
)
testEachMapValRatcheting(t, "nil map to empty map",
@@ -364,6 +364,179 @@ func testEachMapValRatcheting[K ~string, V any](t *testing.T, name string, old,
})
}
+func TestEachPtrMapVal(t *testing.T) {
+ testEachPtrMapVal(t, "valid", map[string]*int{"one": new(11), "two": new(12), "three": new(13)})
+ testEachPtrMapVal(t, "valid", map[string]*string{"A": new("a"), "B": new("b"), "C": new("c")})
+ testEachPtrMapVal(t, "valid", map[string]*TestStruct{"one": {11, "a"}, "two": {12, "b"}, "three": {13, "c"}})
+
+ testEachPtrMapVal(t, "empty", map[string]*int{})
+ testEachPtrMapVal(t, "empty", map[string]*string{})
+ testEachPtrMapVal(t, "empty", map[string]*TestStruct{})
+
+ testEachPtrMapVal[int](t, "nil", nil)
+ testEachPtrMapVal[string](t, "nil", nil)
+ testEachPtrMapVal[TestStruct](t, "nil", nil)
+
+ t.Run("nil element ignored", func(t *testing.T) {
+ calls := 0
+ vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *int) field.ErrorList {
+ calls++
+ return nil
+ }
+ input := map[string]*int{"a": new(1), "b": nil, "c": new(3)}
+ errs := EachPtrMapVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, vfn)
+ if len(errs) != 0 {
+ t.Errorf("expected 0 errors, got %d: %v", len(errs), errs)
+ }
+ if calls != 2 {
+ t.Errorf("expected 2 calls, got %d", calls)
+ }
+ })
+}
+
+func testEachPtrMapVal[T any](t *testing.T, name string, input map[string]*T) {
+ t.Helper()
+ var zero T
+ t.Run(fmt.Sprintf("%s(*%T)", name, zero), func(t *testing.T) {
+ calls := 0
+ vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList {
+ if oldVal != nil {
+ t.Errorf("expected nil oldVal, got %v", *oldVal)
+ }
+ calls++
+ return nil
+ }
+ errs := EachPtrMapVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, vfn)
+ if len(errs) != 0 {
+ t.Errorf("expected 0 errors, got %d: %v", len(errs), errs)
+ }
+ if calls != len(input) {
+ t.Errorf("expected %d calls, got %d", len(input), calls)
+ }
+ })
+}
+
+func TestEachPtrMapValRatcheting(t *testing.T) {
+ testEachPtrMapValRatcheting(t, "primitive same data",
+ map[string]*int{"one": new(11), "two": new(12), "three": new(13)},
+ map[string]*int{"one": new(11), "three": new(13), "two": new(12)},
+ DirectEqual,
+ 0,
+ )
+ testEachPtrMapValRatcheting(t, "primitive changed value, exists in old",
+ map[string]*int{"one": new(11), "two": new(12)}, // old
+ map[string]*int{"one": new(11), "two": new(99)}, // new
+ DirectEqual,
+ 1,
+ )
+ testEachPtrMapValRatcheting(t, "primitive less data in new, exist in old",
+ map[string]*int{"one": new(11), "two": new(12), "three": new(13)},
+ map[string]*int{"one": new(11), "three": new(13)},
+ DirectEqual,
+ 0,
+ )
+ testEachPtrMapValRatcheting(t, "primitive new data, not exist in old",
+ map[string]*int{"one": new(11), "two": new(12)},
+ map[string]*int{"one": new(11), "two": new(12), "three": new(13)},
+ DirectEqual,
+ 1,
+ )
+ testEachPtrMapValRatcheting(t, "non comparable value, same data",
+ map[string]*NonComparableStruct{
+ "one": {I: 11, S: []string{"a"}},
+ "two": {I: 12, S: []string{"b"}},
+ "three": {I: 13, S: []string{"c"}},
+ },
+ map[string]*NonComparableStruct{
+ "one": {I: 11, S: []string{"a"}},
+ "three": {I: 13, S: []string{"c"}},
+ "two": {I: 12, S: []string{"b"}},
+ },
+ ReflectDeepEqual,
+ 0,
+ )
+ testEachPtrMapValRatcheting(t, "non comparable value, less data in new, exist in old",
+ map[string]*NonComparableStruct{
+ "one": {I: 11, S: []string{"a"}},
+ "two": {I: 12, S: []string{"b"}},
+ "three": {I: 13, S: []string{"c"}},
+ },
+ map[string]*NonComparableStruct{
+ "one": {I: 11, S: []string{"a"}},
+ "three": {I: 13, S: []string{"c"}},
+ },
+ ReflectDeepEqual,
+ 0,
+ )
+ testEachPtrMapValRatcheting(t, "non comparable value, new data, not exist in old",
+ map[string]*NonComparableStruct{
+ "one": {I: 11, S: []string{"a"}},
+ "two": {I: 12, S: []string{"b"}},
+ "three": {I: 13, S: []string{"c"}},
+ },
+ map[string]*NonComparableStruct{
+ "one": {I: 11, S: []string{"a"}},
+ "three": {I: 13, S: []string{"c"}},
+ "two": {I: 12, S: []string{"b"}},
+ "four": {I: 14, S: []string{"d"}},
+ },
+ ReflectDeepEqual,
+ 1,
+ )
+ testEachPtrMapValRatcheting(t, "struct with pointer field, same value different pointer",
+ map[string]*NonComparableStructWithPtr{
+ "one": {I: 11, P: new(1)},
+ "two": {I: 12, P: new(2)},
+ },
+ map[string]*NonComparableStructWithPtr{
+ "one": {I: 11, P: new(1)},
+ "two": {I: 12, P: new(2)},
+ },
+ ReflectDeepEqual,
+ 0,
+ )
+ testEachPtrMapValRatcheting(t, "nil map to empty map",
+ nil,
+ map[string]*int{},
+ DirectEqual,
+ 0,
+ )
+ testEachPtrMapValRatcheting(t, "nil map to non-empty map",
+ nil,
+ map[string]*int{"one": new(1)},
+ DirectEqual,
+ 1,
+ )
+ testEachPtrMapValRatcheting(t, "empty map to nil map",
+ map[string]*int{},
+ nil,
+ DirectEqual,
+ 0,
+ )
+ testEachPtrMapValRatcheting(t, "non-empty map to nil map",
+ map[string]*int{"one": new(1)},
+ nil,
+ DirectEqual,
+ 0,
+ )
+}
+
+func testEachPtrMapValRatcheting[K ~string, V any](t *testing.T, name string, old, new map[K]*V, equiv MatchFunc[*V], wantCalls int) {
+ t.Helper()
+ var zero V
+ t.Run(fmt.Sprintf("%s(*%T)", name, zero), func(t *testing.T) {
+ calls := 0
+ vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *V) field.ErrorList {
+ calls++
+ return nil
+ }
+ _ = EachPtrMapVal(context.Background(), operation.Operation{Type: operation.Update}, field.NewPath("test"), new, old, equiv, vfn)
+ if calls != wantCalls {
+ t.Errorf("expected %d calls, got %d", wantCalls, calls)
+ }
+ })
+}
+
type StringType string
func TestEachMapKey(t *testing.T) {
@@ -455,7 +628,7 @@ func testValSliceUnique[T comparable](t *testing.T, name string, input []T, want
}
})
t.Run(fmt.Sprintf("%s(reflect)", name), func(t *testing.T) {
- errs := ValSliceUnique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, SemanticDeepEqual)
+ errs := ValSliceUnique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, ReflectDeepEqual)
if len(errs) != wantErrs {
t.Errorf("expected %d errors, got %d: %s", wantErrs, len(errs), fmtErrs(errs))
}
@@ -483,7 +656,7 @@ func testValSliceUniqueByReflect[T any](t *testing.T, name string, input []T, wa
t.Helper()
var zero T
t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) {
- errs := ValSliceUnique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, SemanticDeepEqual)
+ errs := ValSliceUnique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, ReflectDeepEqual)
if len(errs) != wantErrs {
t.Errorf("expected %d errors, got %d: %s", wantErrs, len(errs), fmtErrs(errs))
}
@@ -573,7 +746,7 @@ func TestEachPtrSliceValRatcheting(t *testing.T) {
testEachPtrSliceValRatcheting(t, "ComparableStruct same data different order",
[]*TestStruct{{11, "a"}, {12, "b"}, {13, "c"}},
[]*TestStruct{{11, "a"}, {13, "c"}, {12, "b"}},
- SemanticDeepEqual,
+ ReflectDeepEqual,
nil,
)
}
@@ -650,3 +823,30 @@ func TestPtrSliceNoNils(t *testing.T) {
})
}
}
+
+func TestPtrMapNoNils(t *testing.T) {
+ tests := []struct {
+ name string
+ input map[string]*int
+ wantErrs int
+ }{
+ {"nil", nil, 0},
+ {"empty", map[string]*int{}, 0},
+ {"no_nil", map[string]*int{"a": new(1), "b": new(2)}, 0},
+ {"has_nil", map[string]*int{"a": new(1), "b": nil, "c": new(3), "d": nil}, 2},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ errs := PtrMapNoNils(context.Background(), operation.Operation{}, field.NewPath("test"), tt.input, nil)
+ if len(errs) != tt.wantErrs {
+ t.Errorf("expected %d errors, got %d: %v", tt.wantErrs, len(errs), errs)
+ }
+ for _, err := range errs {
+ if err.Type != field.ErrorTypeRequired {
+ t.Errorf("expected Required error, got %v", err.Type)
+ }
+ }
+ })
+ }
+}
diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/item_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/item_test.go
index 2efd68c0d2b0a..180b8da62c0cd 100644
--- a/staging/src/k8s.io/apimachinery/pkg/api/validate/item_test.go
+++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/item_test.go
@@ -157,7 +157,7 @@ func TestValSliceItem(t *testing.T) {
op := operation.Operation{Type: operation.Update}
fp := field.NewPath("")
- got := ValSliceItem(ctx, op, fp, tc.new, tc.old, tc.match, SemanticDeepEqual, tc.validator)
+ got := ValSliceItem(ctx, op, fp, tc.new, tc.old, tc.match, ReflectDeepEqual, tc.validator)
if !reflect.DeepEqual(got, tc.expected) {
t.Errorf("got %v want %v", got, tc.expected)
@@ -227,7 +227,7 @@ func TestPtrSliceItem(t *testing.T) {
op := operation.Operation{Type: operation.Update}
fp := field.NewPath("")
- got := PtrSliceItem(ctx, op, fp, tc.new, tc.old, tc.match, SemanticDeepEqual, tc.validator)
+ got := PtrSliceItem(ctx, op, fp, tc.new, tc.old, tc.match, ReflectDeepEqual, tc.validator)
if !reflect.DeepEqual(got, tc.expected) {
t.Errorf("got %v want %v", got, tc.expected)
diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/update.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/update.go
index c258e6d112c65..cf4db5b5294f1 100644
--- a/staging/src/k8s.io/apimachinery/pkg/api/validate/update.go
+++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/update.go
@@ -42,8 +42,8 @@ const (
NoRemoveItem
)
-// UpdateValueByCompare verifies update constraints for comparable value types.
-func UpdateValueByCompare[T comparable](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
+// UpdateValue verifies update constraints for value types.
+func UpdateValue[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, equiv MatchFunc[T], constraints ...UpdateConstraint) field.ErrorList {
// nil oldValue means no prior value to compare against (eg: a new item at +k8s:eachVal scope) -> no transition to check.
if op.Type != operation.Update || oldValue == nil {
return nil
@@ -51,22 +51,24 @@ func UpdateValueByCompare[T comparable](_ context.Context, op operation.Operatio
var errs field.ErrorList
var zero T
+ valueIsZero := equiv(*value, zero)
+ oldValueIsZero := equiv(*oldValue, zero)
for _, constraint := range constraints {
switch constraint {
case NoSet:
- if *oldValue == zero && *value != zero {
+ if oldValueIsZero && !valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
}
case NoUnset:
- if *oldValue != zero && *value == zero {
+ if !oldValueIsZero && valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
}
case NoModify:
// Rely on validation ratcheting to detect that the value has changed.
// This check only verifies that the field was set in both the old and
// new objects, confirming it was a modification, not a set/unset.
- if *oldValue != zero && *value != zero {
+ if !oldValueIsZero && !valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
}
}
@@ -75,6 +77,13 @@ func UpdateValueByCompare[T comparable](_ context.Context, op operation.Operatio
return errs
}
+// UpdateValueByCompare verifies update constraints for comparable value types.
+//
+// Deprecated: Use UpdateValue instead.
+func UpdateValueByCompare[T comparable](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
+ return UpdateValue(ctx, op, fldPath, value, oldValue, func(a, b T) bool { return a == b }, constraints...)
+}
+
// UpdatePointer verifies update constraints for pointer types.
func UpdatePointer[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
if op.Type != operation.Update {
@@ -107,38 +116,10 @@ func UpdatePointer[T any](_ context.Context, op operation.Operation, fldPath *fi
}
// UpdateValueByReflect verifies update constraints for non-comparable value types using reflection.
-func UpdateValueByReflect[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
- // nil oldValue means no prior value to compare against (eg: a new item at +k8s:eachVal scope) -> no transition to check.
- if op.Type != operation.Update || oldValue == nil {
- return nil
- }
-
- var errs field.ErrorList
- var zero T
- valueIsZero := equality.Semantic.DeepEqual(*value, zero)
- oldValueIsZero := equality.Semantic.DeepEqual(*oldValue, zero)
-
- for _, constraint := range constraints {
- switch constraint {
- case NoSet:
- if oldValueIsZero && !valueIsZero {
- errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
- }
- case NoUnset:
- if !oldValueIsZero && valueIsZero {
- errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
- }
- case NoModify:
- // Rely on validation ratcheting to detect that the value has changed.
- // This check only verifies that the field was set in both the old and
- // new objects, confirming it was a modification, not a set/unset.
- if !oldValueIsZero && !valueIsZero {
- errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
- }
- }
- }
-
- return errs
+//
+// Deprecated: Use UpdateValue instead.
+func UpdateValueByReflect[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
+ return UpdateValue(ctx, op, fldPath, value, oldValue, func(a, b T) bool { return equality.Semantic.DeepEqual(a, b) }, constraints...)
}
// UpdateStruct verifies update constraints for non-pointer struct types.
diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/update_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/update_test.go
index f9e13e0e99dd6..1978ced8d72ab 100644
--- a/staging/src/k8s.io/apimachinery/pkg/api/validate/update_test.go
+++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/update_test.go
@@ -138,10 +138,153 @@ func TestUpdateValue(t *testing.T) {
},
}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ op := operation.Operation{Type: tt.op}
+ errs := UpdateValue(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, func(a, b string) bool { return a == b }, tt.constraints...)
+ if len(errs) != tt.wantErrs {
+ t.Errorf("UpdateValue() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs)
+ }
+ for i, msg := range tt.wantMsgs {
+ if i >= len(errs) {
+ t.Errorf("Expected error message %q not found", msg)
+ continue
+ }
+ if errs[i].Detail != msg {
+ t.Errorf("UpdateValue() error message = %q, want %q", errs[i].Detail, msg)
+ }
+ }
+ })
+ }
+}
+
+func TestUpdateValueByCompare(t *testing.T) {
+ tests := []struct {
+ name string
+ op operation.Type
+ value string
+ oldValue string
+ constraints []UpdateConstraint
+ wantErrs int
+ wantMsgs []string
+ }{
+ {
+ name: "NoSet - unset to set transition (forbidden)",
+ op: operation.Update,
+ value: "value",
+ oldValue: "",
+ constraints: []UpdateConstraint{NoSet},
+ wantErrs: 1,
+ wantMsgs: []string{"field cannot be set once created"},
+ },
+ {
+ name: "NoUnset - set to unset transition (forbidden)",
+ op: operation.Update,
+ value: "",
+ oldValue: "value",
+ constraints: []UpdateConstraint{NoUnset},
+ wantErrs: 1,
+ wantMsgs: []string{"field cannot be cleared once set"},
+ },
+ {
+ name: "NoModify - set to different value (forbidden)",
+ op: operation.Update,
+ value: "value2",
+ oldValue: "value1",
+ constraints: []UpdateConstraint{NoModify},
+ wantErrs: 1,
+ wantMsgs: []string{"field cannot be modified once set"},
+ },
+ }
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
op := operation.Operation{Type: tt.op}
errs := UpdateValueByCompare(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, tt.constraints...)
+ if len(errs) != tt.wantErrs {
+ t.Errorf("UpdateValueByCompare() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs)
+ }
+ for i, msg := range tt.wantMsgs {
+ if i >= len(errs) {
+ t.Errorf("Expected error message %q not found", msg)
+ continue
+ }
+ if errs[i].Detail != msg {
+ t.Errorf("UpdateValueByCompare() error message = %q, want %q", errs[i].Detail, msg)
+ }
+ }
+ })
+ }
+}
+
+func TestUpdateValue_CustomMatchFunc(t *testing.T) {
+ type NonComparableStruct struct {
+ Name string
+ Tags []string
+ }
+
+ customEquiv := func(a, b NonComparableStruct) bool {
+ return a.Name == b.Name && (len(a.Tags) == 0 && len(b.Tags) == 0 || (len(a.Tags) == len(b.Tags) && a.Tags[0] == b.Tags[0]))
+ }
+
+ tests := []struct {
+ name string
+ op operation.Type
+ value NonComparableStruct
+ oldValue NonComparableStruct
+ constraints []UpdateConstraint
+ wantErrs int
+ wantMsgs []string
+ }{
+ {
+ name: "NoSet - unset to set (forbidden)",
+ op: operation.Update,
+ value: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
+ oldValue: NonComparableStruct{},
+ constraints: []UpdateConstraint{NoSet},
+ wantErrs: 1,
+ wantMsgs: []string{"field cannot be set once created"},
+ },
+ {
+ name: "NoUnset - set to unset (forbidden)",
+ op: operation.Update,
+ value: NonComparableStruct{},
+ oldValue: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
+ constraints: []UpdateConstraint{NoUnset},
+ wantErrs: 1,
+ wantMsgs: []string{"field cannot be cleared once set"},
+ },
+ {
+ name: "NoModify - zero to non-zero transition (allowed)",
+ op: operation.Update,
+ value: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
+ oldValue: NonComparableStruct{},
+ constraints: []UpdateConstraint{NoModify},
+ wantErrs: 0,
+ },
+ {
+ name: "NoModify - non-zero to zero transition (allowed)",
+ op: operation.Update,
+ value: NonComparableStruct{},
+ oldValue: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
+ constraints: []UpdateConstraint{NoModify},
+ wantErrs: 0,
+ },
+ {
+ name: "NoModify - non-zero to non-zero transition (forbidden)",
+ op: operation.Update,
+ value: NonComparableStruct{Name: "foo", Tags: []string{"b"}},
+ oldValue: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
+ constraints: []UpdateConstraint{NoModify},
+ wantErrs: 1,
+ wantMsgs: []string{"field cannot be modified once set"},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ op := operation.Operation{Type: tt.op}
+ errs := UpdateValue(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, customEquiv, tt.constraints...)
if len(errs) != tt.wantErrs {
t.Errorf("UpdateValue() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs)
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go
index 25f869adadfb6..a8017edc07723 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go
@@ -39,6 +39,16 @@ type Struct struct {
MapTypedefField map[string]StringType `json:"mapTypedefField"`
UnvalidatedMapField map[string]string `json:"UnvalidatedMapField"`
+
+ // +k8s:validateFalse="field Struct.MapPtrField"
+ // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrField[*]"
+ MapPtrField map[string]*string `json:"mapPtrField"`
+
+ // +k8s:validateFalse="field Struct.MapPtrTypedefField"
+ // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrTypedefField[*]"
+ MapPtrTypedefField map[string]*StringType `json:"mapPtrTypedefField"`
+
+ UnvalidatedMapPtrField map[string]*string `json:"UnvalidatedMapPtrField"`
}
// +k8s:validateFalse="type StringType"
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/testdata/validate-false.json b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/testdata/validate-false.json
index 5076a6fcff727..b7758a6448905 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/testdata/validate-false.json
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/testdata/validate-false.json
@@ -12,6 +12,26 @@
"mapField[岯Ȉ\u0026\u003c沲3]": [
"field Struct.MapField[*]"
],
+ "mapPtrField": [
+ "field Struct.MapPtrField"
+ ],
+ "mapPtrField[`ȝ懿沇]": [
+ "field Struct.MapPtrField[*]"
+ ],
+ "mapPtrField[ǝ祇Fæƭō]": [
+ "field Struct.MapPtrField[*]"
+ ],
+ "mapPtrTypedefField": [
+ "field Struct.MapPtrTypedefField"
+ ],
+ "mapPtrTypedefField[ʈ劉j蕓ư銩6ij憏歅%ɜ¤0Ȼ]": [
+ "field Struct.MapPtrTypedefField[*]",
+ "type StringType"
+ ],
+ "mapPtrTypedefField[剛ň=Z?ǿ\u003c]": [
+ "field Struct.MapPtrTypedefField[*]",
+ "type StringType"
+ ],
"mapTypedefField": [
"field Struct.MapTypedefField"
],
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations.go
index e2520a6146e80..a4c8f92c3abda 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations.go
@@ -146,5 +146,87 @@ func Validate_Struct(
}
// field Struct.UnvalidatedMapField has no validation
+
+ { // field Struct.MapPtrField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]*string,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) map[string]*string {
+ return oldObj.MapPtrField
+ })
+ errs = append(errs, fn(fldPath.Child("mapPtrField"), obj.MapPtrField, oldVal, oldObj != nil)...)
+ }
+
+ { // field Struct.MapPtrTypedefField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]*StringType,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, StringType](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ // iterate the map and call the value type's validation function
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_StringType); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) map[string]*StringType {
+ return oldObj.MapPtrTypedefField
+ })
+ errs = append(errs, fn(fldPath.Child("mapPtrTypedefField"), obj.MapPtrTypedefField, oldVal, oldObj != nil)...)
+ }
+
+ // field Struct.UnvalidatedMapPtrField has no validation
return errs
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go
index 4e39a1de8e8ab..2f771fb1deae0 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go
@@ -38,7 +38,17 @@ type Struct struct {
// +k8s:eachVal=+k8s:validateFalse="field Struct.MapTypedefField[*]"
MapTypedefField map[string]OtherTypedefStruct `json:"mapTypedefField"`
- UnvalidatedMapField map[string]string `json:"UnvalidatedMapField"`
+ UnvalidatedMapField map[string]UnvalidatedStruct `json:"UnvalidatedMapField"`
+
+ // +k8s:validateFalse="field Struct.MapPtrField"
+ // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrField[*]"
+ MapPtrField map[string]*OtherStruct `json:"mapPtrField"`
+
+ // +k8s:validateFalse="field Struct.MapPtrTypedefField"
+ // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrTypedefField[*]"
+ MapPtrTypedefField map[string]*OtherTypedefStruct `json:"mapPtrTypedefField"`
+
+ UnvalidatedMapPtrField map[string]*UnvalidatedStruct `json:"UnvalidatedMapPtrField"`
}
// +k8s:validateFalse="type OtherStruct"
@@ -46,3 +56,5 @@ type OtherStruct struct{}
// +k8s:validateFalse="type OtherTypedefStruct"
type OtherTypedefStruct OtherStruct
+
+type UnvalidatedStruct struct{}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/testdata/validate-false.json b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/testdata/validate-false.json
index ec038cbc5e682..2253b92760894 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/testdata/validate-false.json
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/testdata/validate-false.json
@@ -14,6 +14,28 @@
"field Struct.MapField[*]",
"type OtherStruct"
],
+ "mapPtrField": [
+ "field Struct.MapPtrField"
+ ],
+ "mapPtrField[\u003eóDz岋笨Gń條]": [
+ "field Struct.MapPtrField[*]",
+ "type OtherStruct"
+ ],
+ "mapPtrField[]": [
+ "field Struct.MapPtrField[*]",
+ "type OtherStruct"
+ ],
+ "mapPtrTypedefField": [
+ "field Struct.MapPtrTypedefField"
+ ],
+ "mapPtrTypedefField[v$蛷)讻IeYF墄[C_睿ȉǫ蹟t´]": [
+ "field Struct.MapPtrTypedefField[*]",
+ "type OtherTypedefStruct"
+ ],
+ "mapPtrTypedefField[ș$0đ\u003eƯ]ɹȽ東]": [
+ "field Struct.MapPtrTypedefField[*]",
+ "type OtherTypedefStruct"
+ ],
"mapTypedefField": [
"field Struct.MapTypedefField"
],
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations.go
index 0b45bc834b4bd..ebe69b0254344 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations.go
@@ -163,5 +163,91 @@ func Validate_Struct(
}
// field Struct.UnvalidatedMapField has no validation
+
+ { // field Struct.MapPtrField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]*OtherStruct,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, OtherStruct](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ // iterate the map and call the value type's validation function
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) map[string]*OtherStruct {
+ return oldObj.MapPtrField
+ })
+ errs = append(errs, fn(fldPath.Child("mapPtrField"), obj.MapPtrField, oldVal, oldObj != nil)...)
+ }
+
+ { // field Struct.MapPtrTypedefField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]*OtherTypedefStruct,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, OtherTypedefStruct](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherTypedefStruct) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ // iterate the map and call the value type's validation function
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherTypedefStruct); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) map[string]*OtherTypedefStruct {
+ return oldObj.MapPtrTypedefField
+ })
+ errs = append(errs, fn(fldPath.Child("mapPtrTypedefField"), obj.MapPtrTypedefField, oldVal, oldObj != nil)...)
+ }
+
+ // field Struct.UnvalidatedMapPtrField has no validation
return errs
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go
index d21f559c00da1..a4b62cc13e9e0 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go
@@ -29,20 +29,28 @@ var localSchemeBuilder = testscheme.New()
// Note: no validation here
type UnvalidatedType map[string]string
+// Note: no validation here
+type UnvalidatedPtrType map[string]*string
+
// +k8s:validateFalse="type MapType"
// +k8s:eachVal=+k8s:validateFalse="type MapType[*]"
type MapType map[string]string
-// Note: no validation here
-type UnvalidatedPtrType map[string]*string
-
-// +k8s:validateFalse="type StringType"
-type StringType string
+// +k8s:validateFalse="type MapPtrType"
+// +k8s:eachVal=+k8s:validateFalse="type MapPtrType[*]"
+type MapPtrType map[string]*string
// +k8s:validateFalse="type MapTypedefType"
// +k8s:eachVal=+k8s:validateFalse="type MapTypedefType[*]"
type MapTypedefType map[string]StringType
+// +k8s:validateFalse="type MapPtrTypedefType"
+// +k8s:eachVal=+k8s:validateFalse="type MapPtrTypedefType[*]"
+type MapPtrTypedefType map[string]*StringType
+
+// +k8s:validateFalse="type StringType"
+type StringType string
+
// +k8s:validateFalse="type Struct"
type Struct struct {
TypeMeta int
@@ -54,4 +62,16 @@ type Struct struct {
// +k8s:validateFalse="field Struct.MapTypedefField"
// +k8s:eachVal=+k8s:validateFalse="field Struct.MapTypedefField[*]"
MapTypedefField MapTypedefType `json:"mapTypedefField"`
+
+ UnvalidatedMapField UnvalidatedType `json:"UnvalidatedMapField"`
+
+ // +k8s:validateFalse="field Struct.MapPtrField"
+ // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrField[*]"
+ MapPtrField MapPtrType `json:"mapPtrField"`
+
+ // +k8s:validateFalse="field Struct.MapPtrTypedefField"
+ // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrTypedefField[*]"
+ MapPtrTypedefField MapPtrTypedefType `json:"mapPtrTypedefField"`
+
+ UnvalidatedMapPtrField UnvalidatedPtrType `json:"UnvalidatedMapPtrField"`
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/testdata/validate-false.json b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/testdata/validate-false.json
index c4b147c06f9f2..65a321116f417 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/testdata/validate-false.json
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/testdata/validate-false.json
@@ -15,6 +15,32 @@
"field Struct.MapField[*]",
"type MapType[*]"
],
+ "mapPtrField": [
+ "field Struct.MapPtrField",
+ "type MapPtrType"
+ ],
+ "mapPtrField[`ȝ懿沇]": [
+ "field Struct.MapPtrField[*]",
+ "type MapPtrType[*]"
+ ],
+ "mapPtrField[ǝ祇Fæƭō]": [
+ "field Struct.MapPtrField[*]",
+ "type MapPtrType[*]"
+ ],
+ "mapPtrTypedefField": [
+ "field Struct.MapPtrTypedefField",
+ "type MapPtrTypedefType"
+ ],
+ "mapPtrTypedefField[ʈ劉j蕓ư銩6ij憏歅%ɜ¤0Ȼ]": [
+ "field Struct.MapPtrTypedefField[*]",
+ "type MapPtrTypedefType[*]",
+ "type StringType"
+ ],
+ "mapPtrTypedefField[剛ň=Z?ǿ\u003c]": [
+ "field Struct.MapPtrTypedefField[*]",
+ "type MapPtrTypedefType[*]",
+ "type StringType"
+ ],
"mapTypedefField": [
"field Struct.MapTypedefField",
"type MapTypedefType"
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations.go
index a5da56f4c5832..15a29c153d037 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations.go
@@ -56,6 +56,49 @@ func RegisterValidations(scheme *testscheme.Scheme) error {
return nil
}
+// Validate_MapPtrType validates an instance of MapPtrType according
+// to declarative validation rules in the API schema.
+func Validate_MapPtrType(
+ ctx context.Context, op operation.Operation, fldPath *field.Path,
+ obj, oldObj MapPtrType) (errs field.ErrorList) {
+
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapPtrType"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapPtrType[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+
+ return errs
+}
+
+// Validate_MapPtrTypedefType validates an instance of MapPtrTypedefType according
+// to declarative validation rules in the API schema.
+func Validate_MapPtrTypedefType(
+ ctx context.Context, op operation.Operation, fldPath *field.Path,
+ obj, oldObj MapPtrTypedefType) (errs field.ErrorList) {
+
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapPtrTypedefType"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapPtrTypedefType[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+
+ // iterate the map and call the value type's validation function
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_StringType); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+
+ return errs
+}
+
// Validate_MapType validates an instance of MapType according
// to declarative validation rules in the API schema.
func Validate_MapType(
@@ -188,5 +231,88 @@ func Validate_Struct(
errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...)
}
+ // field Struct.UnvalidatedMapField has no validation
+
+ { // field Struct.MapPtrField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj MapPtrType,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ // call the type's validation function
+ errs = append(errs, Validate_MapPtrType(ctx, op, fldPath, obj, oldObj)...)
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) MapPtrType {
+ return oldObj.MapPtrField
+ })
+ errs = append(errs, fn(fldPath.Child("mapPtrField"), obj.MapPtrField, oldVal, oldObj != nil)...)
+ }
+
+ { // field Struct.MapPtrTypedefField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj MapPtrTypedefType,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, StringType](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ // call the type's validation function
+ errs = append(errs, Validate_MapPtrTypedefType(ctx, op, fldPath, obj, oldObj)...)
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) MapPtrTypedefType {
+ return oldObj.MapPtrTypedefField
+ })
+ errs = append(errs, fn(fldPath.Child("mapPtrTypedefField"), obj.MapPtrTypedefField, oldVal, oldObj != nil)...)
+ }
+
+ // field Struct.UnvalidatedMapPtrField has no validation
return errs
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc.go
new file mode 100644
index 0000000000000..74beb7963a20c
--- /dev/null
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc.go
@@ -0,0 +1,54 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// +k8s:validation-gen=TypesWithField=TypeMeta
+// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
+// +k8s:validation-gen-deep-equal-func=k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func.CustomDeepEqual
+
+// This is a test package.
+// +k8s:validation-gen-nolint
+package deepequalfunc
+
+import (
+ "reflect"
+
+ "k8s.io/code-generator/cmd/validation-gen/testscheme"
+)
+
+var localSchemeBuilder = testscheme.New()
+
+var CustomDeepEqualCalls int
+
+func CustomDeepEqual(a, b any) bool {
+ CustomDeepEqualCalls++
+ return reflect.DeepEqual(a, b)
+}
+
+type Struct struct {
+ TypeMeta int
+
+ // +k8s:validateFalse="field Struct.NonComparableField"
+ NonComparableField NonComparableStruct `json:"nonComparableField"`
+
+ // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]"
+ MapField map[string]NonComparableStruct `json:"mapField"`
+}
+
+// +k8s:validateFalse="type NonComparableStruct"
+type NonComparableStruct struct {
+ // Ptr makes it not direct-comparable
+ Ptr *int `json:"ptr"`
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc_test.go
new file mode 100644
index 0000000000000..595e71d26c1bd
--- /dev/null
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc_test.go
@@ -0,0 +1,71 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package deepequalfunc
+
+import (
+ "testing"
+)
+
+func TestDeepEqualFunc(t *testing.T) {
+ st := localSchemeBuilder.Test(t)
+
+ // Create
+ val1 := 10
+ obj := &Struct{
+ NonComparableField: NonComparableStruct{Ptr: &val1},
+ MapField: map[string]NonComparableStruct{
+ "a": {Ptr: &val1},
+ },
+ }
+ st.Value(obj).ExpectValidateFalseByPath(map[string][]string{
+ "nonComparableField": {"field Struct.NonComparableField", "type NonComparableStruct"},
+ "mapField[a]": {"field Struct.MapField[*]", "type NonComparableStruct"},
+ })
+
+ // Update with same values should ratchet (skip validation) using CustomDeepEqual
+ CustomDeepEqualCalls = 0
+ oldObj := &Struct{
+ NonComparableField: NonComparableStruct{Ptr: &val1},
+ MapField: map[string]NonComparableStruct{
+ "a": {Ptr: &val1},
+ },
+ }
+ newObj := &Struct{
+ NonComparableField: NonComparableStruct{Ptr: &val1},
+ MapField: map[string]NonComparableStruct{
+ "a": {Ptr: &val1},
+ },
+ }
+ st.Value(newObj).OldValue(oldObj).ExpectValid()
+ if CustomDeepEqualCalls == 0 {
+ t.Errorf("expected CustomDeepEqual to be called during update ratcheting, got %d calls", CustomDeepEqualCalls)
+ }
+
+ // Update with a new map key added: old key "a" ratchets via deepEqualImpl_ -> CustomDeepEqual,
+ // only new key "b" fails.
+ val2 := 20
+ updateObj := &Struct{
+ NonComparableField: NonComparableStruct{Ptr: &val1},
+ MapField: map[string]NonComparableStruct{
+ "a": {Ptr: &val1},
+ "b": {Ptr: &val2},
+ },
+ }
+ st.Value(updateObj).OldValue(oldObj).ExpectValidateFalseByPath(map[string][]string{
+ "mapField[b]": {"field Struct.MapField[*]", "type NonComparableStruct"},
+ })
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/zz_generated.validations.go
new file mode 100644
index 0000000000000..2ec646bb0e067
--- /dev/null
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/zz_generated.validations.go
@@ -0,0 +1,143 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by validation-gen. DO NOT EDIT.
+
+package deepequalfunc
+
+import (
+ context "context"
+ fmt "fmt"
+
+ operation "k8s.io/apimachinery/pkg/api/operation"
+ safe "k8s.io/apimachinery/pkg/api/safe"
+ validate "k8s.io/apimachinery/pkg/api/validate"
+ field "k8s.io/apimachinery/pkg/util/validation/field"
+ testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme"
+)
+
+func init() { localSchemeBuilder.Register(RegisterValidations) }
+
+// RegisterValidations adds validation functions to the given scheme.
+// Public to allow building arbitrary schemes.
+func RegisterValidations(scheme *testscheme.Scheme) error {
+ // type Struct
+ scheme.AddValidationFunc(
+ (*Struct)(nil),
+ func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
+ switch op.Request.SubresourcePath() {
+ case "/":
+ return Validate_Struct(
+ ctx, op, nil, /* fldPath */
+ obj.(*Struct),
+ safe.Cast[*Struct](oldObj))
+ }
+ return field.ErrorList{
+ field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())),
+ }
+ })
+ return nil
+}
+
+// Validate_NonComparableStruct validates an instance of NonComparableStruct according
+// to declarative validation rules in the API schema.
+func Validate_NonComparableStruct(
+ ctx context.Context, op operation.Operation, fldPath *field.Path,
+ obj, oldObj *NonComparableStruct) (errs field.ErrorList) {
+
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type NonComparableStruct"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+
+ // field NonComparableStruct.Ptr has no validation
+ return errs
+}
+
+// Validate_Struct validates an instance of Struct according
+// to declarative validation rules in the API schema.
+func Validate_Struct(
+ ctx context.Context, op operation.Operation, fldPath *field.Path,
+ obj, oldObj *Struct) (errs field.ErrorList) {
+
+ // field Struct.TypeMeta has no validation
+
+ { // field Struct.NonComparableField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj *NonComparableStruct,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if CustomDeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.NonComparableField"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ // call the type's validation function
+ errs = append(errs, Validate_NonComparableStruct(ctx, op, fldPath, obj, oldObj)...)
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) *NonComparableStruct {
+ return &oldObj.NonComparableField
+ })
+ errs = append(errs, fn(fldPath.Child("nonComparableField"), &obj.NonComparableField, oldVal, oldObj != nil)...)
+ }
+
+ { // field Struct.MapField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]NonComparableStruct,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if CustomDeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ // iterate the map and call the value type's validation function
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_NonComparableStruct); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) map[string]NonComparableStruct {
+ return oldObj.MapField
+ })
+ errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...)
+ }
+
+ return errs
+}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return CustomDeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/zz_generated.validations.go
index eb7d8cd0c62dd..9b6ecf48a5b03 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/zz_generated.validations.go
@@ -133,7 +133,7 @@ func Validate_ItemList(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *Item, b *Item) bool { return a.Key == b.Key }, validate.SemanticDeepEqual, Validate_Item); len(e) != 0 {
+ func(a *Item, b *Item) bool { return a.Key == b.Key }, deepEqualImpl_, Validate_Item); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -371,18 +371,18 @@ func Validate_StructSlice(
}
}
// call field-attached validations
- if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil,
+ if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, nil,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field SetSliceNonComparableField[*]")
}); len(e) != 0 {
errs = append(errs, e...)
}
// lists with set semantics require unique values
- if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual); len(e) != 0 {
+ if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 {
errs = append(errs, e...)
}
// iterate the list and call the type's validation function
- if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, Validate_NonComparableStruct); len(e) != 0 {
+ if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, nil, Validate_NonComparableStruct); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -440,7 +440,7 @@ func Validate_StructSlice(
}
// call field-attached validations
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *NonComparableStructWithKey, b *NonComparableStructWithKey) bool { return a.Key == b.Key }, validate.SemanticDeepEqual,
+ func(a *NonComparableStructWithKey, b *NonComparableStructWithKey) bool { return a.Key == b.Key }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStructWithKey) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapSliceNonComparableField[*]")
}); len(e) != 0 {
@@ -453,7 +453,7 @@ func Validate_StructSlice(
}
// iterate the list and call the type's validation function
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *NonComparableStructWithKey, b *NonComparableStructWithKey) bool { return a.Key == b.Key }, validate.SemanticDeepEqual, Validate_NonComparableStructWithKey); len(e) != 0 {
+ func(a *NonComparableStructWithKey, b *NonComparableStructWithKey) bool { return a.Key == b.Key }, deepEqualImpl_, Validate_NonComparableStructWithKey); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -480,7 +480,7 @@ func Validate_StructSlice(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *PtrKeyStruct, b *PtrKeyStruct) bool {
return ((a.Key == nil && b.Key == nil) || (a.Key != nil && b.Key != nil && *a.Key == *b.Key))
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *PtrKeyStruct) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapSlicePtrKeyField[*]")
}); len(e) != 0 {
@@ -497,7 +497,7 @@ func Validate_StructSlice(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *PtrKeyStruct, b *PtrKeyStruct) bool {
return ((a.Key == nil && b.Key == nil) || (a.Key != nil && b.Key != nil && *a.Key == *b.Key))
- }, validate.SemanticDeepEqual, Validate_PtrKeyStruct); len(e) != 0 {
+ }, deepEqualImpl_, Validate_PtrKeyStruct); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -524,7 +524,7 @@ func Validate_StructSlice(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *MixedKeyStruct, b *MixedKeyStruct) bool {
return ((a.Key1 == nil && b.Key1 == nil) || (a.Key1 != nil && b.Key1 != nil && *a.Key1 == *b.Key1)) && a.Key2 == b.Key2
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedKeyStruct) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapSliceMixedKeyField[*]")
}); len(e) != 0 {
@@ -541,7 +541,7 @@ func Validate_StructSlice(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *MixedKeyStruct, b *MixedKeyStruct) bool {
return ((a.Key1 == nil && b.Key1 == nil) || (a.Key1 != nil && b.Key1 != nil && *a.Key1 == *b.Key1)) && a.Key2 == b.Key2
- }, validate.SemanticDeepEqual, Validate_MixedKeyStruct); len(e) != 0 {
+ }, deepEqualImpl_, Validate_MixedKeyStruct); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -555,3 +555,8 @@ func Validate_StructSlice(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/zz_generated.validations.go
index e9364e922abcb..183797afbb0c9 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/zz_generated.validations.go
@@ -157,7 +157,7 @@ func Validate_StructWithMaps(
}
}
// call field-attached validations
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual,
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapTest.MapNonComparableStructField[*]")
}); len(e) != 0 {
@@ -174,3 +174,8 @@ func Validate_StructWithMaps(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations.go
index 8dea5b21bba98..0c9ae1e747285 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations.go
@@ -193,7 +193,7 @@ func Validate_T1(
}
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_T5); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T5); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -217,7 +217,7 @@ func Validate_T1(
}
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_T6); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T6); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -250,7 +250,7 @@ func Validate_T2(
}
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_T1); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T1); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -318,7 +318,7 @@ func Validate_T4(
}
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_T3); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T3); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -355,7 +355,7 @@ func Validate_T5(
errs = append(errs, e...)
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_T5); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T5); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -412,7 +412,7 @@ func Validate_T6(
}
}
// iterate the map and call the value type's validation function
- if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_T6); len(e) != 0 {
+ if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T6); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -426,3 +426,8 @@ func Validate_T6(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc.go
new file mode 100644
index 0000000000000..dfa903970c25e
--- /dev/null
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc.go
@@ -0,0 +1,41 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// +k8s:validation-gen=TypesWithField=TypeMeta
+// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
+
+// This is a test package.
+// +k8s:validation-gen-nolint
+package mapofpointers
+
+import "k8s.io/code-generator/cmd/validation-gen/testscheme"
+
+var localSchemeBuilder = testscheme.New()
+
+type Struct struct {
+ TypeMeta int
+
+ // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]"
+ MapField map[string]*OtherStruct `json:"mapField"`
+
+ // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPrimitiveField[*]"
+ MapPrimitiveField map[string]*string `json:"mapPrimitiveField"`
+}
+
+// +k8s:validateFalse="type OtherStruct"
+type OtherStruct struct {
+ Value string `json:"value"`
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc_test.go
new file mode 100644
index 0000000000000..b9e93fe5666a8
--- /dev/null
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc_test.go
@@ -0,0 +1,52 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package mapofpointers
+
+import (
+ "testing"
+
+ "k8s.io/apimachinery/pkg/util/validation/field"
+)
+
+func Test(t *testing.T) {
+ st := localSchemeBuilder.Test(t)
+
+ // 1. Zero values should be valid
+ st.Value(&Struct{
+ // All zero values.
+ }).ExpectValid()
+
+ // 2. Non-nil elements trigger validation errors
+ st.Value(&Struct{
+ MapField: map[string]*OtherStruct{"k1": {}, "k2": {}},
+ MapPrimitiveField: map[string]*string{"k1": new("a"), "k2": new("b")},
+ }).ExpectValidateFalseByPath(map[string][]string{
+ "mapField[k1]": {"field Struct.MapField[*]", "type OtherStruct"},
+ "mapField[k2]": {"field Struct.MapField[*]", "type OtherStruct"},
+ "mapPrimitiveField[k1]": {"field Struct.MapPrimitiveField[*]"},
+ "mapPrimitiveField[k2]": {"field Struct.MapPrimitiveField[*]"},
+ })
+
+ // 3. Nil elements trigger Required errors from PtrMapNoNils
+ st.Value(&Struct{
+ MapField: map[string]*OtherStruct{"k1": nil},
+ MapPrimitiveField: map[string]*string{"k1": nil},
+ }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
+ field.Required(field.NewPath("mapField").Key("k1"), ""),
+ field.Required(field.NewPath("mapPrimitiveField").Key("k1"), ""),
+ })
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/zz_generated.validations.go
new file mode 100644
index 0000000000000..641eb0e3fc9b9
--- /dev/null
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/zz_generated.validations.go
@@ -0,0 +1,156 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by validation-gen. DO NOT EDIT.
+
+package mapofpointers
+
+import (
+ context "context"
+ fmt "fmt"
+
+ equality "k8s.io/apimachinery/pkg/api/equality"
+ operation "k8s.io/apimachinery/pkg/api/operation"
+ safe "k8s.io/apimachinery/pkg/api/safe"
+ validate "k8s.io/apimachinery/pkg/api/validate"
+ field "k8s.io/apimachinery/pkg/util/validation/field"
+ testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme"
+)
+
+func init() { localSchemeBuilder.Register(RegisterValidations) }
+
+// RegisterValidations adds validation functions to the given scheme.
+// Public to allow building arbitrary schemes.
+func RegisterValidations(scheme *testscheme.Scheme) error {
+ // type Struct
+ scheme.AddValidationFunc(
+ (*Struct)(nil),
+ func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
+ switch op.Request.SubresourcePath() {
+ case "/":
+ return Validate_Struct(
+ ctx, op, nil, /* fldPath */
+ obj.(*Struct),
+ safe.Cast[*Struct](oldObj))
+ }
+ return field.ErrorList{
+ field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())),
+ }
+ })
+ return nil
+}
+
+// Validate_OtherStruct validates an instance of OtherStruct according
+// to declarative validation rules in the API schema.
+func Validate_OtherStruct(
+ ctx context.Context, op operation.Operation, fldPath *field.Path,
+ obj, oldObj *OtherStruct) (errs field.ErrorList) {
+
+ if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct"); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+
+ // field OtherStruct.Value has no validation
+ return errs
+}
+
+// Validate_Struct validates an instance of Struct according
+// to declarative validation rules in the API schema.
+func Validate_Struct(
+ ctx context.Context, op operation.Operation, fldPath *field.Path,
+ obj, oldObj *Struct) (errs field.ErrorList) {
+
+ // field Struct.TypeMeta has no validation
+
+ { // field Struct.MapField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]*OtherStruct,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, OtherStruct](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ // iterate the map and call the value type's validation function
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) map[string]*OtherStruct {
+ return oldObj.MapField
+ })
+ errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...)
+ }
+
+ { // field Struct.MapPrimitiveField
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]*string,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual,
+ func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
+ return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPrimitiveField[*]")
+ }); len(e) != 0 {
+ errs = append(errs, e...)
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *Struct) map[string]*string {
+ return oldObj.MapPrimitiveField
+ })
+ errs = append(errs, fn(fldPath.Child("mapPrimitiveField"), obj.MapPrimitiveField, oldVal, oldObj != nil)...)
+ }
+
+ return errs
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/zz_generated.validations.go
index 85cc57c4600f8..78c8f3462e128 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/zz_generated.validations.go
@@ -168,7 +168,7 @@ func Validate_Struct(
if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj,
func(item *PtrItem) bool {
return item.StringKey != nil && *item.StringKey == "target-ptr" && item.IntKey == 42 && item.BoolKey == true
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *PtrItem) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item PtrItems[stringKey=target-ptr,intKey=42,boolKey=true]")
}); len(e) != 0 {
@@ -207,7 +207,7 @@ func Validate_Struct(
if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj,
func(item *MixedPtrItem) bool {
return item.StringPtrKey != nil && *item.StringPtrKey == "target-ptr" && item.StringKey == "target"
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedPtrItem) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item MixedPtrItems")
}); len(e) != 0 {
@@ -225,3 +225,8 @@ func Validate_Struct(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/zz_generated.validations.go
index 870b19161765c..9556a0689460c 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/zz_generated.validations.go
@@ -303,7 +303,7 @@ func Validate_Struct(
}
func() { // cohort = "{"key": "target-ptr"}"
if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj,
- func(item *PtrKeyItem) bool { return item.Key != nil && *item.Key == "target-ptr" }, validate.SemanticDeepEqual,
+ func(item *PtrKeyItem) bool { return item.Key != nil && *item.Key == "target-ptr" }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *PtrKeyItem) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item PtrKeyItems[key=target-ptr]")
}); len(e) != 0 {
@@ -410,3 +410,8 @@ func Validate_StructWithNestedTypedef(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go
index 40c4c8a1d6ca0..c176df16c859d 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go
@@ -219,7 +219,8 @@ func Validate_SpecialValidationStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoModify).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -248,7 +249,8 @@ func Validate_SpecialValidationStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkBeta().MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoModify).MarkBeta().MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/zz_generated.validations.go
index 8e9c0e99c504f..bd1654a64c5f1 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/zz_generated.validations.go
@@ -245,7 +245,7 @@ func Validate_Struct(
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
func(a *NonComparableStruct, b *NonComparableStruct) bool {
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListNonComparableField[*]")
}); len(e) != 0 {
@@ -290,7 +290,7 @@ func Validate_Struct(
if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj,
func(item *PtrKeyStruct) bool {
return item.Key1Field != nil && *item.Key1Field == "target-ptr" && item.Key2Field == 42
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *PtrKeyStruct) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ListPtrKeyField[key1Field=target-ptr,key2Field=42]")
}); len(e) != 0 {
@@ -329,7 +329,7 @@ func Validate_Struct(
if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj,
func(item *MixedPtrKeyStruct) bool {
return item.StringPtrKey != nil && *item.StringPtrKey == "target-ptr" && item.StringKey == "target"
- }, validate.SemanticDeepEqual,
+ }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedPtrKeyStruct) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ListMixedPtrKeyField")
}); len(e) != 0 {
@@ -347,3 +347,8 @@ func Validate_Struct(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/zz_generated.validations.go
index 79c4099dfb3e0..fb48c34721b80 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/zz_generated.validations.go
@@ -227,7 +227,7 @@ func Validate_Struct(
}
// call field-attached validations
if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj,
- func(a *NonComparableStruct, b *NonComparableStruct) bool { return a.KeyField == b.KeyField }, validate.SemanticDeepEqual,
+ func(a *NonComparableStruct, b *NonComparableStruct) bool { return a.KeyField == b.KeyField }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListNonComparableField[*]")
}); len(e) != 0 {
@@ -277,3 +277,8 @@ func Validate_Struct(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/zz_generated.validations.go
index 8a8b743054e9d..77b155ffdda58 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/zz_generated.validations.go
@@ -183,7 +183,7 @@ func Validate_ImmutableStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -191,7 +191,7 @@ func Validate_ImmutableStruct(
return // do not proceed
}
// lists with set semantics require unique values
- if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual); len(e) != 0 {
+ if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -278,7 +278,7 @@ func Validate_ImmutableStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -286,7 +286,7 @@ func Validate_ImmutableStruct(
return // do not proceed
}
// lists with set semantics require unique values
- if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual); len(e) != 0 {
+ if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -397,7 +397,7 @@ func Validate_Struct(
}
// call field-attached validations
// lists with set semantics require unique values
- if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual); len(e) != 0 {
+ if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -422,7 +422,7 @@ func Validate_Struct(
}
// call field-attached validations
// lists with set semantics require unique values
- if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual); len(e) != 0 {
+ if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -436,3 +436,8 @@ func Validate_Struct(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/zz_generated.validations.go
index 00cd01fb9ceae..d3968299c2140 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/zz_generated.validations.go
@@ -25,6 +25,7 @@ import (
context "context"
fmt "fmt"
+ equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
@@ -195,7 +196,7 @@ func Validate_Collections(
if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "listField",
func(obj *Collections) []string { return obj.ListField },
- func(obj *Collections) string { return obj.D1 }, validate.SemanticDeepEqual,
+ func(obj *Collections) string { return obj.D1 }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj)...)
@@ -222,7 +223,7 @@ func Validate_Collections(
}
if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "mapField",
func(obj *Collections) map[string]string { return obj.MapField },
- func(obj *Collections) string { return obj.D1 }, validate.SemanticDeepEqual,
+ func(obj *Collections) string { return obj.D1 }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
errs := field.ErrorList{}
errs = append(errs, validate.ForbiddenMap(ctx, op, fldPath, obj, oldObj)...)
@@ -531,3 +532,8 @@ func Validate_StrictUnion(
// field StrictUnion.FieldB has no validation
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/zz_generated.validations.go
index ef8c3bc1ef004..120b2c9072d41 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/zz_generated.validations.go
@@ -258,7 +258,8 @@ func Validate_Struct(
if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoUnset).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a int, b int) bool { return a == b }, validate.NoUnset).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/zz_generated.validations.go
index 2134450e65b06..b96320a866dcd 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/zz_generated.validations.go
@@ -136,7 +136,8 @@ func Validate_Struct(
earlyReturn := false
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
- return validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify)
+ return validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoModify)
}).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
@@ -168,7 +169,8 @@ func Validate_Struct(
earlyReturn := false
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
- return validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify)
+ return validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoModify)
}).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/zz_generated.validations.go
index b9d799f3e64e2..de5a185c66a68 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/zz_generated.validations.go
@@ -79,7 +79,7 @@ func Validate_Struct(
// call field-attached validations
func() { // cohort = "labels"
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "labels",
- func(o *v1.ObjectMeta) map[string]string { return o.Labels }, validate.SemanticDeepEqual,
+ func(o *v1.ObjectMeta) map[string]string { return o.Labels }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
return validate.EachMapKey(ctx, op, fldPath, obj, oldObj,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
@@ -91,7 +91,7 @@ func Validate_Struct(
}()
func() { // cohort = "ownerReferences"
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "ownerReferences",
- func(o *v1.ObjectMeta) []v1.OwnerReference { return o.OwnerReferences }, validate.SemanticDeepEqual,
+ func(o *v1.ObjectMeta) []v1.OwnerReference { return o.OwnerReferences }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []v1.OwnerReference) field.ErrorList {
return validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj,
func(a *v1.OwnerReference, b *v1.OwnerReference) bool { return a.UID == b.UID })
@@ -101,7 +101,7 @@ func Validate_Struct(
}()
func() { // cohort = "finalizers"
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "finalizers",
- func(o *v1.ObjectMeta) []string { return o.Finalizers }, validate.SemanticDeepEqual,
+ func(o *v1.ObjectMeta) []string { return o.Finalizers }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
return validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)
}); len(e) != 0 {
@@ -119,3 +119,8 @@ func Validate_Struct(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/zz_generated.validations.go
index e9c369fdf7722..d9cf158b01398 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/zz_generated.validations.go
@@ -112,7 +112,7 @@ func Validate_Struct(
}()
func() { // cohort = "sliceField"
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField",
- func(o *OtherStruct) []string { return o.SliceField }, validate.SemanticDeepEqual,
+ func(o *OtherStruct) []string { return o.SliceField }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructField.SliceField")
}); len(e) != 0 {
@@ -121,7 +121,7 @@ func Validate_Struct(
}()
func() { // cohort = "mapField"
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField",
- func(o *OtherStruct) map[string]string { return o.MapField }, validate.SemanticDeepEqual,
+ func(o *OtherStruct) map[string]string { return o.MapField }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructField.MapField")
}); len(e) != 0 {
@@ -185,7 +185,7 @@ func Validate_Struct(
}()
func() { // cohort = "sliceField"
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField",
- func(o *OtherStruct) []string { return o.SliceField }, validate.SemanticDeepEqual,
+ func(o *OtherStruct) []string { return o.SliceField }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructPtrField.SliceField")
}); len(e) != 0 {
@@ -194,7 +194,7 @@ func Validate_Struct(
}()
func() { // cohort = "mapField"
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField",
- func(o *OtherStruct) map[string]string { return o.MapField }, validate.SemanticDeepEqual,
+ func(o *OtherStruct) map[string]string { return o.MapField }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructPtrField.MapField")
}); len(e) != 0 {
@@ -212,3 +212,8 @@ func Validate_Struct(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/zz_generated.validations.go
index b21f8fbf63bcf..2c0282191a3d1 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/zz_generated.validations.go
@@ -492,7 +492,7 @@ func Validate_ParentWithMaxItems(
func() { // cohort = "value"
earlyReturn := false
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value",
- func(o *TargetWithMaxItems) []string { return o.Value }, validate.SemanticDeepEqual,
+ func(o *TargetWithMaxItems) []string { return o.Value }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
return validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2)
}).MarkShortCircuit(); len(e) != 0 {
@@ -502,7 +502,7 @@ func Validate_ParentWithMaxItems(
return // do not proceed
}
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value",
- func(o *TargetWithMaxItems) []string { return o.Value }, validate.SemanticDeepEqual,
+ func(o *TargetWithMaxItems) []string { return o.Value }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithMaxItems.Field.Value")
}); len(e) != 0 {
@@ -546,7 +546,7 @@ func Validate_ParentWithMaxProperties(
func() { // cohort = "value"
earlyReturn := false
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value",
- func(o *TargetWithMaxProperties) map[string]string { return o.Value }, validate.SemanticDeepEqual,
+ func(o *TargetWithMaxProperties) map[string]string { return o.Value }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
return validate.MaxProperties(ctx, op, fldPath, obj, oldObj, 2)
}).MarkShortCircuit(); len(e) != 0 {
@@ -556,7 +556,7 @@ func Validate_ParentWithMaxProperties(
return // do not proceed
}
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value",
- func(o *TargetWithMaxProperties) map[string]string { return o.Value }, validate.SemanticDeepEqual,
+ func(o *TargetWithMaxProperties) map[string]string { return o.Value }, deepEqualImpl_,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithMaxProperties.Field.Value")
}); len(e) != 0 {
@@ -1029,7 +1029,8 @@ func Validate_ParentWithUpdate(
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value",
func(o *TargetWithUpdate) *string { return &o.Value }, validate.DirectEqual,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
- return validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify)
+ return validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoModify)
}).MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
@@ -1308,7 +1309,8 @@ func Validate_TargetWithUpdate(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -1326,3 +1328,8 @@ func Validate_TargetWithUpdate(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/zz_generated.validations.go
index 23dfb6e20603c..b0e1a4e3fb7f8 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/zz_generated.validations.go
@@ -426,7 +426,7 @@ func Validate_UpdateListStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, validate.NoAddItem, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, validate.NoAddItem, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -434,7 +434,7 @@ func Validate_UpdateListStruct(
return // do not proceed
}
// lists with set semantics require unique values
- if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual); len(e) != 0 {
+ if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 {
errs = append(errs, e...)
}
return
@@ -627,3 +627,8 @@ func Validate_UpdateListStruct(
return errs
}
+
+// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.
+func deepEqualImpl_[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc.go
index 8737cf5e61f67..465888aea1f33 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc.go
@@ -52,6 +52,18 @@ type UpdateMapStruct struct {
// +k8s:eachVal=+k8s:update=NoModify
EachValNoModifyMap map[string]MapItem `json:"eachValNoModifyMap"`
+
+ // Pointer Map NoSet
+ // +k8s:update=NoSet
+ PointerMapNoSet map[string]*string `json:"pointerMapNoSet"`
+
+ // Pointer Map NoAdd
+ // +k8s:update=NoAddItem
+ PointerMapNoAdd map[string]*string `json:"pointerMapNoAdd"`
+
+ // Pointer Map NoRemove
+ // +k8s:update=NoRemoveItem
+ PointerMapNoRemove map[string]*MapItem `json:"pointerMapNoRemove"`
}
type MapItem struct {
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc_test.go
index bb5e84ded9b00..3917898a1c416 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc_test.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc_test.go
@@ -129,4 +129,60 @@ func TestUpdateMapTags(t *testing.T) {
field.Invalid(field.NewPath("eachValNoModifyMap").Key("a"), nil, "cannot be modified").WithOrigin("update"),
})
}
+
+ // Pointer Map NoSet
+ {
+ old := base
+ old.PointerMapNoSet = nil
+ cur := base
+ cur.PointerMapNoSet = map[string]*string{"a": new("1")}
+
+ st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{
+ field.Invalid(field.NewPath("pointerMapNoSet"), nil, "field cannot be set once created").WithOrigin("update"),
+ })
+
+ // nil -> nil is allowed
+ st.Value(&old).OldValue(&old).ExpectValid()
+ }
+
+ // Pointer Map NoAddItem
+ {
+ old := base
+ old.PointerMapNoAdd = map[string]*string{"a": new("1")}
+ cur := base
+ cur.PointerMapNoAdd = map[string]*string{"a": new("1"), "b": new("2")}
+
+ st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{
+ field.Forbidden(field.NewPath("pointerMapNoAdd").Key("b"), "item may not be added").WithOrigin("update"),
+ })
+ }
+
+ // Pointer Map NoRemoveItem
+ {
+ old := base
+ old.PointerMapNoRemove = map[string]*MapItem{
+ "a": {Name: "a", Value: "1"},
+ "b": {Name: "b", Value: "2"},
+ }
+ cur := base
+ cur.PointerMapNoRemove = map[string]*MapItem{
+ "a": {Name: "a", Value: "1"},
+ }
+
+ st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{
+ field.Forbidden(field.NewPath("pointerMapNoRemove").Key("b"), "item may not be removed").WithOrigin("update"),
+ })
+ }
+
+ // Nil element in pointer map on update should trigger Required error from PtrMapNoNils
+ {
+ old := base
+ old.PointerMapNoAdd = map[string]*string{"a": new("1")}
+ cur := base
+ cur.PointerMapNoAdd = map[string]*string{"a": nil}
+
+ st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{
+ field.Required(field.NewPath("pointerMapNoAdd").Key("a"), ""),
+ })
+ }
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/zz_generated.validations.go
index 38588d3eb9fbc..e471ab147021d 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/zz_generated.validations.go
@@ -270,5 +270,104 @@ func Validate_UpdateMapStruct(
errs = append(errs, fn(fldPath.Child("eachValNoModifyMap"), obj.EachValNoModifyMap, oldVal, oldObj != nil)...)
}
+ { // field UpdateMapStruct.PointerMapNoSet
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]*string,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoSet).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *UpdateMapStruct) map[string]*string {
+ return oldObj.PointerMapNoSet
+ })
+ errs = append(errs, fn(fldPath.Child("pointerMapNoSet"), obj.PointerMapNoSet, oldVal, oldObj != nil)...)
+ }
+
+ { // field UpdateMapStruct.PointerMapNoAdd
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]*string,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoAddItem).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *UpdateMapStruct) map[string]*string {
+ return oldObj.PointerMapNoAdd
+ })
+ errs = append(errs, fn(fldPath.Child("pointerMapNoAdd"), obj.PointerMapNoAdd, oldVal, oldObj != nil)...)
+ }
+
+ { // field UpdateMapStruct.PointerMapNoRemove
+ fn := func(
+ fldPath *field.Path,
+ obj, oldObj map[string]*MapItem,
+ oldValueCorrelated bool) (errs field.ErrorList) {
+ // don't revalidate unchanged data
+ if oldValueCorrelated && op.Type == operation.Update {
+ if equality.Semantic.DeepEqual(obj, oldObj) {
+ return nil
+ }
+ }
+ // call field-attached validations
+ earlyReturn := false
+ if e := validate.PtrMapNoNils[string, MapItem](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 {
+ errs = append(errs, e...)
+ earlyReturn = true
+ }
+ if earlyReturn {
+ return // do not proceed
+ }
+ return
+ }
+ oldVal := safe.Field(oldObj,
+ func(oldObj *UpdateMapStruct) map[string]*MapItem {
+ return oldObj.PointerMapNoRemove
+ })
+ errs = append(errs, fn(fldPath.Child("pointerMapNoRemove"), obj.PointerMapNoRemove, oldVal, oldObj != nil)...)
+ }
+
return errs
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/zz_generated.validations.go
index 888cb51b17a87..61e82e9fdecb6 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/zz_generated.validations.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/zz_generated.validations.go
@@ -77,7 +77,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoSet).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoSet).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -106,7 +107,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoUnset).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoUnset).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -135,7 +137,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -164,7 +167,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoSet, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoSet, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -193,7 +197,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a string, b string) bool { return a == b }, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -222,7 +227,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a int, b int) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -251,7 +257,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a int32, b int32) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -280,7 +287,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a int64, b int64) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -309,7 +317,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a uint, b uint) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -338,7 +347,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a bool, b bool) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -367,7 +377,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a float32, b float32) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -396,7 +407,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a float64, b float64) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -425,7 +437,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a byte, b byte) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -715,7 +728,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a CustomString, b CustomString) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -744,7 +758,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
- if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoSet).MarkShortCircuit(); len(e) != 0 {
+ if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
+ func(a CustomInt, b CustomInt) bool { return a == b }, validate.NoSet).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/targets.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/targets.go
index 29a676b040ddd..90692c7f40266 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/targets.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/targets.go
@@ -54,6 +54,9 @@ const (
// (e.g. in tests), or set to "nil" to disable scheme registration for this
// package.
schemeRegistryTagName = "k8s:validation-gen-scheme-registry"
+ // Defines the deep-equal function used for equivalence and ratcheting checks.
+ // Defaults to "k8s.io/apimachinery/pkg/api/equality.Semantic.DeepEqual".
+ deepEqualFuncTagName = "k8s:validation-gen-deep-equal-func"
// If set, generate go test files for test fixtures. Supported values: "validateFalse".
testFixtureTagName = "k8s:validation-gen-test-fixture"
@@ -69,10 +72,11 @@ const (
)
var (
- runtimePkg = "k8s.io/apimachinery/pkg/runtime"
- schemeType = types.Name{Package: runtimePkg, Name: "Scheme"}
- metav1Pkg = "k8s.io/apimachinery/pkg/apis/meta/v1"
- listMetaType = types.Name{Package: metav1Pkg, Name: "ListMeta"}
+ runtimePkg = "k8s.io/apimachinery/pkg/runtime"
+ schemeType = types.Name{Package: runtimePkg, Name: "Scheme"}
+ defaultDeepEqualFunc = types.Name{Package: "k8s.io/apimachinery/pkg/api/equality", Name: "Semantic.DeepEqual"}
+ metav1Pkg = "k8s.io/apimachinery/pkg/apis/meta/v1"
+ listMetaType = types.Name{Package: metav1Pkg, Name: "ListMeta"}
)
// extractAndParseTag extracts all the values for a given tag, according to the
@@ -157,6 +161,48 @@ func registerScheme(pkg *types.Package) bool {
return ok
}
+func deepEqualFuncTag(pkg *types.Package) types.Name {
+ // TODO: convert to extractAndParseTag() and update all callers to use quoted values
+ tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{deepEqualFuncTagName}, pkg.Comments)
+ if err != nil {
+ klog.Fatalf("Failed to extract deep equal func tags: %v", err)
+ }
+ values, found := tags[deepEqualFuncTagName]
+ if !found || len(values) == 0 {
+ return defaultDeepEqualFunc
+ }
+ if len(values) > 1 {
+ panic(fmt.Sprintf("Package %q contains more than one usage of %q", pkg.Path, deepEqualFuncTagName))
+ }
+ val := values[0].Value
+ if val == "" {
+ return defaultDeepEqualFunc
+ }
+ return parseDeepEqualFunc(val)
+}
+
+func parseDeepEqualFunc(val string) types.Name {
+ lastSlash := strings.LastIndex(val, "/")
+ if lastSlash == -1 {
+ dot := strings.Index(val, ".")
+ if dot == -1 {
+ return types.Name{Name: val}
+ }
+ return types.Name{
+ Package: val[:dot],
+ Name: val[dot+1:],
+ }
+ }
+ dot := lastSlash + strings.Index(val[lastSlash:], ".")
+ if dot == lastSlash {
+ return types.Name{Package: val}
+ }
+ return types.Name{
+ Package: val[:dot],
+ Name: val[dot+1:],
+ }
+}
+
func isSubresourceTag(t *types.Type) (string, bool) {
var comments []string
comments = append(comments, t.SecondClosestCommentLines...)
@@ -358,6 +404,7 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target {
pkg := context.Universe[input]
schemeRegistry, registerThisPkg := schemeRegistryTag(pkg)
+ deepEqualFunc := deepEqualFuncTag(pkg)
criteria, found := validationTypeMatch(pkg, idOpts)
if !found {
@@ -485,7 +532,7 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target {
GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) {
generators = []generator.Generator{
- NewGenValidations(args.OutputFile, pkg.Path, typesPkg.Path, rootTypes, td, inputToCanonicalPkg, schemeRegistry, registerThisPkg),
+ NewGenValidations(args.OutputFile, pkg.Path, typesPkg.Path, rootTypes, td, inputToCanonicalPkg, schemeRegistry, registerThisPkg, deepEqualFunc),
}
testFixtureTags := testFixtureTag(pkg)
if testFixtureTags.Len() > 0 {
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/targets_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/targets_test.go
new file mode 100644
index 0000000000000..ff67234ed3261
--- /dev/null
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/targets_test.go
@@ -0,0 +1,64 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+ "testing"
+
+ "k8s.io/gengo/v2/types"
+)
+
+func TestParseDeepEqualFunc(t *testing.T) {
+ testCases := []struct {
+ name string
+ input string
+ expected types.Name
+ }{{
+ name: "fully qualified with struct field",
+ input: "k8s.io/apimachinery/pkg/api/equality.Semantic.DeepEqual",
+ expected: types.Name{Package: "k8s.io/apimachinery/pkg/api/equality", Name: "Semantic.DeepEqual"},
+ }, {
+ name: "fully qualified package function",
+ input: "k8s.io/code-generator/cmd/validation-gen/testscheme.CustomEqual",
+ expected: types.Name{Package: "k8s.io/code-generator/cmd/validation-gen/testscheme", Name: "CustomEqual"},
+ }, {
+ name: "single level package function",
+ input: "testscheme.CustomEqual",
+ expected: types.Name{Package: "testscheme", Name: "CustomEqual"},
+ }, {
+ name: "single level package struct field",
+ input: "testscheme.Semantic.DeepEqual",
+ expected: types.Name{Package: "testscheme", Name: "Semantic.DeepEqual"},
+ }, {
+ name: "package-local function",
+ input: "CustomDeepEqual",
+ expected: types.Name{Package: "", Name: "CustomDeepEqual"},
+ }, {
+ name: "empty input",
+ input: "",
+ expected: types.Name{Package: "", Name: ""},
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ result := parseDeepEqualFunc(tc.input)
+ if want, got := tc.expected, result; got != want {
+ t.Errorf("expected %v, got %v", want, got)
+ }
+ })
+ }
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go
index 16725be28ccb7..239d03a4a34cf 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go
@@ -56,8 +56,6 @@ var (
operationPkgSymbols = mkPkgNames(operationPkg, "Operation", "MatchesSubresource", "Update")
contextPkg = "context"
contextPkgSymbols = mkPkgNames(contextPkg, "Context")
- equalityPkg = "k8s.io/apimachinery/pkg/api/equality"
- equalityPkgSymbols = mkPkgNames(equalityPkg, "Semantic")
)
// genValidations produces a file with autogenerated validations.
@@ -71,10 +69,12 @@ type genValidations struct {
imports namer.ImportTracker
schemeRegistry types.Name
emitRegisterFunc bool
+ deepEqualFunc types.Name
+ usedDeepEqualImpl bool
}
// NewGenValidations creates a new generator for the specified package.
-func NewGenValidations(outputFilename, outputPackage, inputPackage string, rootTypes []*types.Type, discovered *typeDiscoverer, inputToCanonicalPkg map[string]string, schemeRegistry types.Name, emitRegisterFunc bool) generator.Generator {
+func NewGenValidations(outputFilename, outputPackage, inputPackage string, rootTypes []*types.Type, discovered *typeDiscoverer, inputToCanonicalPkg map[string]string, schemeRegistry types.Name, emitRegisterFunc bool, deepEqualFunc types.Name) generator.Generator {
return &genValidations{
GoGenerator: generator.GoGenerator{
OutputFilename: outputFilename,
@@ -87,6 +87,7 @@ func NewGenValidations(outputFilename, outputPackage, inputPackage string, rootT
imports: generator.NewImportTrackerForPackage(outputPackage),
schemeRegistry: schemeRegistry,
emitRegisterFunc: emitRegisterFunc,
+ deepEqualFunc: deepEqualFunc,
}
}
@@ -169,6 +170,26 @@ func (g *genValidations) GenerateType(c *generator.Context, t *types.Type, w io.
return nil
}
+func (g *genValidations) Finalize(c *generator.Context, w io.Writer) error {
+ if g.usedDeepEqualImpl {
+ sw := generator.NewSnippetWriter(w, c, "$", "$")
+ sw.Do("\n", nil)
+ sw.Do("// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.\n", nil)
+ sw.Do("func deepEqualImpl_[T any](a, b T) bool {\n", nil)
+ if g.deepEqualFunc.Package == "" {
+ sw.Do(" return $.$(a, b)\n", g.deepEqualFunc.Name)
+ } else {
+ targs := generator.Args{
+ "deepEqualFunc": c.Universe.Type(g.deepEqualFunc),
+ }
+ sw.Do(" return $.deepEqualFunc|raw$(a, b)\n", targs)
+ }
+ sw.Do("}\n", nil)
+ return sw.Error()
+ }
+ return nil
+}
+
// typeDiscoverer contains fields necessary to build graphs of types.
type typeDiscoverer struct {
initialized bool
@@ -670,8 +691,6 @@ func (td *typeDiscoverer) verifySupportedType(t *types.Type) error {
}
elem := util.NativeType(t.Elem)
switch elem.Kind {
- case types.Pointer:
- return fmt.Errorf("maps of pointers are not supported")
case types.Map:
return fmt.Errorf("maps of maps are not supported")
}
@@ -1289,23 +1308,30 @@ func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild
// need to be executed, but if no fields changed they can be
// skipped).
//
- // For the same reasons, in the specific case of lists of pointers,
+ // For the same reasons, in the specific case of lists and maps of pointers,
// we do not emit nil-checks inside type-specific validation
// functions. If we solve for the duplicative ratcheting, then we
// should also solve for nil-checks.
- if nt := util.NativeType(fld.childType); nt.Kind == types.Slice && nt.Elem.Kind == types.Pointer {
- hasFieldValidations := len(fld.fieldValidations.Functions) > 0
- hasPropagation := fld.node != nil && g.hasValidations(fld.node)
- hasKeyIterations := len(fld.fieldKeyIterations.Functions) > 0 && hasPropagation
- hasValIterations := len(fld.fieldValIterations.Functions) > 0 && hasPropagation
-
- if hasFieldValidations || hasPropagation || hasKeyIterations || hasValIterations {
- // Prepend the nil-check so it runs before any other
- // validations. This is important because the other
- // validations may assume that the slice has no nil values.
- nilCheck := validators.PtrSliceNoNils(nt.Elem.Elem.Name)
- fld.fieldValidations.Functions = append([]validators.FunctionGen{nilCheck}, fld.fieldValidations.Functions...)
+ if nt := util.NativeType(fld.childType); nt.Kind == types.Slice || nt.Kind == types.Map {
+ if nt.Elem.Kind == types.Pointer {
+ hasFieldValidations := len(fld.fieldValidations.Functions) > 0
+ hasPropagation := fld.node != nil && g.hasValidations(fld.node)
+ hasKeyIterations := len(fld.fieldKeyIterations.Functions) > 0 && hasPropagation
+ hasValIterations := len(fld.fieldValIterations.Functions) > 0 && hasPropagation
+ if hasFieldValidations || hasPropagation || hasKeyIterations || hasValIterations {
+ // Prepend the nil-check so it runs before any other
+ // validations. This is important because the other
+ // validations may assume that the map or slice has no nil
+ // values.
+ var nilCheck validators.FunctionGen
+ if nt.Kind == types.Slice {
+ nilCheck = validators.PtrSliceNoNils(nt.Elem.Elem.Name)
+ } else {
+ nilCheck = validators.PtrMapNoNils(nt.Key.Name, nt.Elem.Elem.Name)
+ }
+ fld.fieldValidations.Functions = append([]validators.FunctionGen{nilCheck}, fld.fieldValidations.Functions...)
+ }
}
}
@@ -1314,7 +1340,7 @@ func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild
if !validations.Empty() {
emitComments(validations.Comments, bufsw)
if len(validations.Functions) > 0 {
- emitRatchetingCheck(c, fld.childType, bufsw)
+ g.emitRatchetingCheck(c, fld.childType, bufsw)
fldRatchetingChecked = true
bufsw.Do("// call field-attached validations\n", nil)
g.emitCallsToValidators(c, validations.Functions, bufsw)
@@ -1332,7 +1358,7 @@ func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild
// (transitively) then we don't need to do anything.
if g.hasValidations(fld.node) {
if !fldRatchetingChecked {
- emitRatchetingCheck(c, fld.childType, bufsw)
+ g.emitRatchetingCheck(c, fld.childType, bufsw)
fldRatchetingChecked = true
}
g.emitCallToOtherTypeFunc(c, fld.node, bufsw)
@@ -1347,7 +1373,7 @@ func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild
emitComments(iterations.Comments, bufsw)
if len(iterations.Functions) > 0 {
if !fldRatchetingChecked {
- emitRatchetingCheck(c, fld.childType, bufsw)
+ g.emitRatchetingCheck(c, fld.childType, bufsw)
fldRatchetingChecked = true
}
g.emitCallsToValidators(c, iterations.Functions, bufsw)
@@ -1448,7 +1474,7 @@ func (g *genValidations) emitCallToOtherTypeFunc(c *generator.Context, node *typ
}
// emitRatchetingCheck emits an equivalence check for default ratcheting.
-func emitRatchetingCheck(c *generator.Context, t *types.Type, sw *generator.SnippetWriter) {
+func (g *genValidations) emitRatchetingCheck(c *generator.Context, t *types.Type, sw *generator.SnippetWriter) {
// Emit equivalence check for default ratcheting.
targs := generator.Args{
"operation": mkSymbolArgs(c, operationPkgSymbols),
@@ -1467,8 +1493,12 @@ func emitRatchetingCheck(c *generator.Context, t *types.Type, sw *generator.Snip
// - *obj == *oldObj : compare values
sw.Do(" if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) {\n", targs)
} else {
- targs["equality"] = mkSymbolArgs(c, equalityPkgSymbols)
- sw.Do(" if $.equality.Semantic|raw$.DeepEqual(obj, oldObj) {\n", targs)
+ if g.deepEqualFunc.Package == "" {
+ sw.Do(" if $.$(obj, oldObj) {\n", g.deepEqualFunc.Name)
+ } else {
+ targs["deepEqualFunc"] = c.Universe.Type(g.deepEqualFunc)
+ sw.Do(" if $.deepEqualFunc|raw$(obj, oldObj) {\n", targs)
+ }
}
sw.Do(" return nil\n", nil)
sw.Do(" }\n", nil)
@@ -1755,9 +1785,23 @@ func (g *genValidations) toGolangSourceDataLiteral(sw *generator.SnippetWriter,
case *types.Member:
sw.Do("obj."+v.Name, nil)
case validators.Identifier:
- sw.Do("$.|raw$", c.Universe.Type(types.Name(v)))
+ if types.Name(v) == validators.DeepEqualImpl {
+ g.usedDeepEqualImpl = true
+ }
+ if v.Package == "" {
+ sw.Do(v.Name, nil)
+ } else {
+ sw.Do("$.|raw$", c.Universe.Type(types.Name(v)))
+ }
case *validators.Identifier:
- sw.Do("$.|raw$", c.Universe.Type(types.Name(*v)))
+ if types.Name(*v) == validators.DeepEqualImpl {
+ g.usedDeepEqualImpl = true
+ }
+ if v.Package == "" {
+ sw.Do(v.Name, nil)
+ } else {
+ sw.Do("$.|raw$", c.Universe.Type(types.Name(*v)))
+ }
case validators.PrivateVar:
sw.Do("$.|private$", c.Universe.Type(types.Name(v)))
case *validators.PrivateVar:
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/each.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/each.go
index e5e5fc30a54b2..75709c87d33fb 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/each.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/each.go
@@ -70,6 +70,7 @@ var (
validateEachValSliceVal = types.Name{Package: libValidationPkg, Name: "EachValSliceVal"}
validateEachPtrSliceVal = types.Name{Package: libValidationPkg, Name: "EachPtrSliceVal"}
validateEachMapVal = types.Name{Package: libValidationPkg, Name: "EachMapVal"}
+ validateEachPtrMapVal = types.Name{Package: libValidationPkg, Name: "EachPtrMapVal"}
)
func (evtv eachValTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) {
@@ -240,10 +241,16 @@ func (evtv eachValTagValidator) getMapValidations(t *types.Type, validations Val
if util.IsDirectComparable(util.NonPointer(util.NativeType(nt.Elem))) {
equivArg = Identifier(validateDirectEqual)
}
+
+ validateFunc := validateEachMapVal
+ if nt.Elem.Kind == types.Pointer {
+ validateFunc = validateEachPtrMapVal
+ }
+
wrapped := WrapFunctions(validations, func(vfn FunctionGen, _ DeferredScope) FunctionGen {
comm := vfn.Comments
vfn.Comments = nil
- return Function(eachValTagName, vfn.Flags, validateEachMapVal, equivArg, WrapperFunction{Function: vfn, ObjType: nt.Elem, PathFragment: "[*]"}).WithComments(comm...)
+ return Function(eachValTagName, vfn.Flags, validateFunc, equivArg, WrapperFunction{Function: vfn, ObjType: nt.Elem, PathFragment: "[*]"}).WithComments(comm...)
})
return Validations{
Functions: wrapped.Functions,
@@ -387,6 +394,7 @@ func (ektv eachKeyTagValidator) Docs() TagDoc {
var (
validatePtrSliceNoNils = types.Name{Package: libValidationPkg, Name: "PtrSliceNoNils"}
+ validatePtrMapNoNils = types.Name{Package: libValidationPkg, Name: "PtrMapNoNils"}
)
// PtrSliceNoNils returns a synthetic validation that rejects nil elements of a
@@ -394,3 +402,9 @@ var (
func PtrSliceNoNils(elemType types.Name) FunctionGen {
return Function("PtrSliceNoNils", ShortCircuit, validatePtrSliceNoNils).WithTypeArgs(elemType)
}
+
+// PtrMapNoNils returns a synthetic validation that rejects nil elements of a
+// pointer map. It is not tag-driven; the generator injects it for map[K]*V fields.
+func PtrMapNoNils(keyType, elemType types.Name) FunctionGen {
+ return Function("PtrMapNoNils", ShortCircuit, validatePtrMapNoNils).WithTypeArgs(keyType, elemType)
+}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/list.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/list.go
index adb25196f9a84..16103556a3560 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/list.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/list.go
@@ -408,7 +408,7 @@ func (cutv customUniqueTagValidator) Docs() TagDoc {
var (
validateValSliceUnique = types.Name{Package: libValidationPkg, Name: "ValSliceUnique"}
validatePtrSliceUnique = types.Name{Package: libValidationPkg, Name: "PtrSliceUnique"}
- validateSemanticDeepEqual = types.Name{Package: libValidationPkg, Name: "SemanticDeepEqual"}
+ validateSemanticDeepEqual = DeepEqualImpl
validateDirectEqual = types.Name{Package: libValidationPkg, Name: "DirectEqual"}
)
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/update.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/update.go
index d883e20c1caaf..f0cfd3264db4d 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/update.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/update.go
@@ -199,13 +199,12 @@ func (utc updateTagCollector) Docs() TagDoc {
}
var (
- updateValueValidator = types.Name{Package: libValidationPkg, Name: "UpdateValueByCompare"}
- updatePointerValidator = types.Name{Package: libValidationPkg, Name: "UpdatePointer"}
- updateValueByReflectValidator = types.Name{Package: libValidationPkg, Name: "UpdateValueByReflect"}
- updateStructValidator = types.Name{Package: libValidationPkg, Name: "UpdateStruct"}
- valSliceUpdateValidator = types.Name{Package: libValidationPkg, Name: "ValSliceUpdate"}
- ptrSliceUpdateValidator = types.Name{Package: libValidationPkg, Name: "PtrSliceUpdate"}
- updateMapValidator = types.Name{Package: libValidationPkg, Name: "UpdateMap"}
+ updateValueValidator = types.Name{Package: libValidationPkg, Name: "UpdateValue"}
+ updatePointerValidator = types.Name{Package: libValidationPkg, Name: "UpdatePointer"}
+ updateStructValidator = types.Name{Package: libValidationPkg, Name: "UpdateStruct"}
+ valSliceUpdateValidator = types.Name{Package: libValidationPkg, Name: "ValSliceUpdate"}
+ ptrSliceUpdateValidator = types.Name{Package: libValidationPkg, Name: "PtrSliceUpdate"}
+ updateMapValidator = types.Name{Package: libValidationPkg, Name: "UpdateMap"}
// Constraint constants that will be used as arguments
noSetConstraint = types.Name{Package: libValidationPkg, Name: "NoSet"}
@@ -314,9 +313,8 @@ func generateMapValidation(constraints []validate.UpdateConstraint) Validations
return Validations{Functions: []FunctionGen{fn}}
}
-// emitScalarUpdate emits a call to
-// UpdateValueByCompare/UpdatePointer/UpdateStruct/UpdateValueByReflect based
-// on the Go kind of context.Type.
+// emitScalarUpdate emits a call to UpdateValue/UpdatePointer/UpdateStruct
+// based on the Go kind of context.Type.
// Used for both field scope (scalar/pointer/struct) and list/map element
// scope via +k8s:eachVal.
func emitScalarUpdate(context Context, constraints []validate.UpdateConstraint) Validations {
@@ -327,21 +325,33 @@ func emitScalarUpdate(context Context, constraints []validate.UpdateConstraint)
isComparable := util.IsDirectComparable(t)
var validatorFunc types.Name
+ var args []any
if isPointer {
validatorFunc = updatePointerValidator
+ args = constraintIdentifierArgs(constraints)
} else if isStruct {
validatorFunc = updateStructValidator
- } else if isComparable {
- validatorFunc = updateValueValidator
+ args = constraintIdentifierArgs(constraints)
} else {
- validatorFunc = updateValueByReflectValidator
+ validatorFunc = updateValueValidator
+ var matchArg any
+ if isComparable {
+ matchArg = FunctionLiteral{
+ Parameters: []ParamResult{{"a", context.Type}, {"b", context.Type}},
+ Results: []ParamResult{{"", types.Bool}},
+ Body: "return a == b",
+ }
+ } else {
+ matchArg = Identifier(validateSemanticDeepEqual)
+ }
+ args = append([]any{matchArg}, constraintIdentifierArgs(constraints)...)
}
// Use ShortCircuit flag so these run in the same group as +k8s:optional.
// Scalar/pointer/struct fields only accept NoSet/NoUnset/NoModify
// (validateConstraintsForType rejects the rest), all of which emit
// field.Invalid at the field path.
- fn := Function(updateTagName, ShortCircuit, validatorFunc, constraintIdentifierArgs(constraints)...).
+ fn := Function(updateTagName, ShortCircuit, validatorFunc, args...).
WithEmits(Emission{field.ErrorTypeInvalid, "update", ""})
return Validations{Functions: []FunctionGen{fn}}
}
diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/validators.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/validators.go
index 196c94bbeb1f1..235897dddc26c 100644
--- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/validators.go
+++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/validators.go
@@ -26,6 +26,10 @@ import (
"k8s.io/gengo/v2/types"
)
+var (
+ DeepEqualImpl = types.Name{Name: "deepEqualImpl_"}
+)
+
// TagValidator describes a single validation tag and how to use it. To be
// findable by validation-gen, a TagValidator must be registered - see
// RegisterTagValidator.
diff --git a/staging/src/k8s.io/code-generator/pkg/apidefinitions/activation_test.go b/staging/src/k8s.io/code-generator/pkg/apidefinitions/activation_test.go
index fa7c8e29f6408..c4d35d8d5f227 100644
--- a/staging/src/k8s.io/code-generator/pkg/apidefinitions/activation_test.go
+++ b/staging/src/k8s.io/code-generator/pkg/apidefinitions/activation_test.go
@@ -178,6 +178,7 @@ func TestIdentify(t *testing.T) {
"+k8s:defaulter-gen-input=k8s.io/api/foo/v1",
"+k8s:validation-gen=TypesWithField=TypeMeta",
"+k8s:validation-gen-input=k8s.io/api/foo/v1",
+ "+k8s:validation-gen-deep-equal-func=foo",
"+k8s:validation-gen-nolint",
"+k8s:validation-gen-scheme-registry=foo",
"+k8s:validation-gen-test-fixture=bar",
diff --git a/staging/src/k8s.io/code-generator/pkg/apidefinitions/lint_test.go b/staging/src/k8s.io/code-generator/pkg/apidefinitions/lint_test.go
index a30549a216844..5c11d765bb261 100644
--- a/staging/src/k8s.io/code-generator/pkg/apidefinitions/lint_test.go
+++ b/staging/src/k8s.io/code-generator/pkg/apidefinitions/lint_test.go
@@ -49,6 +49,7 @@ func TestLintRules(t *testing.T) {
"+k8s:defaulter-gen-input=k8s.io/api/foo/v1",
"+k8s:validation-gen=TypesWithField=TypeMeta",
"+k8s:validation-gen-input=k8s.io/api/foo/v1",
+ "+k8s:validation-gen-deep-equal-func=foo",
"+k8s:validation-gen-nolint",
"+k8s:validation-gen-scheme-registry=foo",
"+k8s:validation-gen-test-fixture=bar",
diff --git a/staging/src/k8s.io/code-generator/pkg/apidefinitions/spec.go b/staging/src/k8s.io/code-generator/pkg/apidefinitions/spec.go
index 58c5da6cb01a1..96289c97bb434 100644
--- a/staging/src/k8s.io/code-generator/pkg/apidefinitions/spec.go
+++ b/staging/src/k8s.io/code-generator/pkg/apidefinitions/spec.go
@@ -96,6 +96,7 @@ var (
InputTag: "k8s:validation-gen-input",
ValueMode: TypeFilterList,
AuxTags: []string{
+ "k8s:validation-gen-deep-equal-func",
"k8s:validation-gen-nolint",
"k8s:validation-gen-scheme-registry",
"k8s:validation-gen-test-fixture",
diff --git a/staging/src/k8s.io/dynamic-resource-allocation/api/metadata/v1alpha1/validation.go b/staging/src/k8s.io/dynamic-resource-allocation/api/metadata/v1alpha1/validation.go
index 6c0dbe3bc8ecb..d2aae78012279 100644
--- a/staging/src/k8s.io/dynamic-resource-allocation/api/metadata/v1alpha1/validation.go
+++ b/staging/src/k8s.io/dynamic-resource-allocation/api/metadata/v1alpha1/validation.go
@@ -20,6 +20,7 @@ import (
"context"
resourceapi "k8s.io/api/resource/v1"
+ "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate"
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -29,7 +30,7 @@ func ValidateCustom_Device_Attributes(
ctx context.Context, op operation.Operation, fldPath *field.Path,
obj, oldObj map[resourceapi.QualifiedName]resourceapi.DeviceAttribute,
) field.ErrorList {
- return validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, resourceapi.Validate_DeviceAttribute)
+ return validate.EachMapVal(ctx, op, fldPath, obj, oldObj, semanticDeepEqual, resourceapi.Validate_DeviceAttribute)
}
func ValidateCustom_Device_NetworkData(
@@ -38,3 +39,12 @@ func ValidateCustom_Device_NetworkData(
) field.ErrorList {
return resourceapi.Validate_NetworkDeviceData(ctx, op, fldPath, obj, oldObj)
}
+
+// semanticDeepEqual is a MatchFunc that uses equality.Semantic.DeepEqual to
+// compare two values.
+// This wrapper is needed because MatchFunc requires a function that takes two
+// arguments of specific type T, while equality.Semantic.DeepEqual takes
+// arguments of type interface{}/any.
+func semanticDeepEqual[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}
diff --git a/staging/src/k8s.io/dynamic-resource-allocation/api/metadata/v1beta1/validation.go b/staging/src/k8s.io/dynamic-resource-allocation/api/metadata/v1beta1/validation.go
index d4df1c48f5e37..00cad29f34893 100644
--- a/staging/src/k8s.io/dynamic-resource-allocation/api/metadata/v1beta1/validation.go
+++ b/staging/src/k8s.io/dynamic-resource-allocation/api/metadata/v1beta1/validation.go
@@ -20,6 +20,7 @@ import (
"context"
resourceapi "k8s.io/api/resource/v1"
+ "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate"
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -29,7 +30,7 @@ func ValidateCustom_Device_Attributes(
ctx context.Context, op operation.Operation, fldPath *field.Path,
obj, oldObj map[resourceapi.QualifiedName]resourceapi.DeviceAttribute,
) field.ErrorList {
- return validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, resourceapi.Validate_DeviceAttribute)
+ return validate.EachMapVal(ctx, op, fldPath, obj, oldObj, semanticDeepEqual, resourceapi.Validate_DeviceAttribute)
}
func ValidateCustom_Device_NetworkData(
@@ -38,3 +39,12 @@ func ValidateCustom_Device_NetworkData(
) field.ErrorList {
return resourceapi.Validate_NetworkDeviceData(ctx, op, fldPath, obj, oldObj)
}
+
+// semanticDeepEqual is a MatchFunc that uses equality.Semantic.DeepEqual to
+// compare two values.
+// This wrapper is needed because MatchFunc requires a function that takes two
+// arguments of specific type T, while equality.Semantic.DeepEqual takes
+// arguments of type interface{}/any.
+func semanticDeepEqual[T any](a, b T) bool {
+ return equality.Semantic.DeepEqual(a, b)
+}