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 3d5bd3c1e70..c815d6d9137 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/each.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/each.go @@ -169,3 +169,18 @@ func SemanticDeepEqual[T any](a, b T) bool { func DirectEqual[T comparable](a, b T) bool { return a == b } + +// DirectEqualPtr 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. +// It can be used by any other function that needs to compare two pointees +// directly. +func DirectEqualPtr[T comparable](a, b *T) bool { + if a == b { + return true + } + if a == nil || b == nil { + return false + } + return *a == *b +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/item.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/item.go index 9cc61dd3de2..aba417fa1d4 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/item.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/item.go @@ -26,16 +26,21 @@ import ( // MatchItemFn takes a pointer to an item and returns true if it matches the criteria. type MatchItemFn[T any] func(*T) bool -// SliceItem finds the first item in newList that satisfies the 'matches' predicate, -// and if found, also looks for a matching item in oldList. It then invokes -// 'itemValidator' on these items. -// The fldPath passed to itemValidator is indexed to the matched item's position in newList. -// This function processes only the *first* matching item found in newList. -// It assumes that the 'matches' predicate targets a unique identifier (primary key) and -// will match at most one element per list. -// If this assumption is violated, changes in list order can lead this function -// to have inconsistent behavior. -// This function does not validate items that were removed (present in oldList but not in newList). +// SliceItem finds the first item in newList that satisfies the match function, +// and if found, also looks for a matching item in oldList. If the value of the +// item is the same as the previous value, as per the equiv function, then no +// validation is performed. Otherwise, it invokes 'itemValidator' on these items. +// +// This function processes only the *first* matching item found in newList. It +// assumes that the match functions targets a unique identifier (primary key) +// and will match at most one element per list. If this assumption is violated, +// changes in list order can lead this function to have inconsistent behavior. +// +// The fldPath passed to itemValidator is indexed to the matched item's +// position in newList. +// +// This function does not validate items that were removed (present in oldList +// but not in newList). func SliceItem[TList ~[]TItem, TItem any]( ctx context.Context, op operation.Operation, fldPath *field.Path, newList, oldList TList, diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/subfield.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/subfield.go index 844a28901bc..896f3c3f369 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/subfield.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/subfield.go @@ -27,18 +27,25 @@ import ( // nilable value. type GetFieldFunc[Tstruct any, Tfield any] func(*Tstruct) Tfield -// Subfield validates a subfield of a struct against a validator function. -func Subfield[Tstruct any, Tfield any](ctx context.Context, op operation.Operation, fldPath *field.Path, newStruct, oldStruct *Tstruct, - fldName string, getField GetFieldFunc[Tstruct, Tfield], validator ValidateFunc[Tfield]) field.ErrorList { +// Subfield validates a subfield of a struct against a validator function. If +// the value of the subfield is the same as the previous value, as per the +// equiv function, then no validation is performed. +// +// The fldPath passed to the validator includes the subfield name. +func Subfield[Tstruct any, Tfield any]( + ctx context.Context, op operation.Operation, fldPath *field.Path, + newStruct, oldStruct *Tstruct, + fldName string, getField GetFieldFunc[Tstruct, Tfield], + equiv MatchFunc[Tfield], + validator ValidateFunc[Tfield], +) field.ErrorList { var errs field.ErrorList newVal := getField(newStruct) var oldVal Tfield if oldStruct != nil { oldVal = getField(oldStruct) } - // TODO: passing an equiv function to Subfield for direct comparison instead of - // SemanticDeepEqual if fields can be compared directly, to improve performance. - if op.Type == operation.Update && SemanticDeepEqual(newVal, oldVal) { + if op.Type == operation.Update && oldStruct != nil && equiv(newVal, oldVal) { return nil } errs = append(errs, validator(ctx, op, fldPath.Child(fldName), newVal, oldVal)...) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/zz_generated.validations.go index fa9b456d552..d919c2ba4cb 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/zz_generated.validations.go @@ -70,10 +70,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return nil } // call field-attached validations - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "intField", func(o *SubStruct) *int { return &o.IntField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "intField", func(o *SubStruct) *int { return &o.IntField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field IntField") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "intPtrField", func(o *SubStruct) *int { return o.IntPtrField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "intPtrField", func(o *SubStruct) *int { return o.IntPtrField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field IntPtrField") })...) return @@ -85,10 +85,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field // Validate_StructWithSubfield validates an instance of StructWithSubfield according // to declarative validation rules in the API schema. func Validate_StructWithSubfield(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StructWithSubfield) (errs field.ErrorList) { - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "intField", func(o *StructWithSubfield) *int { return &o.IntField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "intField", func(o *StructWithSubfield) *int { return &o.IntField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field IntField") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "intPtrField", func(o *StructWithSubfield) *int { return o.IntPtrField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "intPtrField", func(o *StructWithSubfield) *int { return o.IntPtrField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field IntPtrField") })...) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/zz_generated.validations.go index 29e17165826..e72db972b44 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/zz_generated.validations.go @@ -64,7 +64,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field // call field-attached validations errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key1 == "a" }, validate.DirectEqual, validate.ImmutableByCompare)...) errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key1 == "b" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *Item) *string { return &o.StringField }, validate.ImmutableByCompare) + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *Item) *string { return &o.StringField }, validate.DirectEqualPtr, validate.ImmutableByCompare) })...) return }(fldPath.Child("listField"), obj.ListField, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.ListField }))...) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/zz_generated.validations.go index d31379112b5..2327682dcd4 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/zz_generated.validations.go @@ -63,7 +63,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field } // call field-attached validations errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key == "target" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *Item) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *Item) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item Items[key=target].stringField") }) })...) @@ -79,7 +79,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field } // call field-attached validations errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *RatchetItem) bool { return item.Key == "ratchet" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *RatchetItem) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "status", func(o *RatchetItem) *string { return &o.Status }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "status", func(o *RatchetItem) *string { return &o.Status }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.NEQ(ctx, op, fldPath, obj, oldObj, "forbidden") }) })...) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/zz_generated.validations.go index e1b57fb9c29..5518cd5cd5f 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/zz_generated.validations.go @@ -62,7 +62,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return nil } // call field-attached validations - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *InnerStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *InnerStruct) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-subfield") })...) return @@ -79,7 +79,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { return // do not proceed } - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *InnerStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *InnerStruct) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-subfield-ptr") })...) return @@ -157,7 +157,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field // Validate_ValidatedInnerStruct validates an instance of ValidatedInnerStruct according // to declarative validation rules in the API schema. func Validate_ValidatedInnerStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ValidatedInnerStruct) (errs field.ErrorList) { - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *ValidatedInnerStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *ValidatedInnerStruct) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-typedef-struct") })...) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/zz_generated.validations.go index 7cd84e6dff8..1da94eda381 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/zz_generated.validations.go @@ -62,7 +62,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field } // call field-attached validations errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ObjectMeta) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "xEnabledField", func(o *ObjectMeta) *string { return &o.XEnabledField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "xEnabledField", func(o *ObjectMeta) *string { return &o.XEnabledField }, validate.DirectEqualPtr, 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.ObjectMeta.XEnabledField") }) })...) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/zz_generated.validations.go index 9c2815b2d39..65c28885215 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/zz_generated.validations.go @@ -62,21 +62,21 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return nil } // call field-attached validations - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", func(o *OtherStruct) *SmallStruct { return &o.StructField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", func(o *OtherStruct) *SmallStruct { return &o.StructField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "StructField.StructField") }) })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", func(o *OtherStruct) []SmallStruct { return o.SliceField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []SmallStruct) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", func(o *OtherStruct) []SmallStruct { return o.SliceField }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []SmallStruct) field.ErrorList { return validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "StructField.SliceField") }) }) })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", func(o *OtherStruct) map[string]SmallStruct { return o.MapField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]SmallStruct) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", func(o *OtherStruct) map[string]SmallStruct { return o.MapField }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]SmallStruct) field.ErrorList { return validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "StructField.MapField") }) }) @@ -92,21 +92,21 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return nil } // call field-attached validations - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", func(o *OtherStruct) *SmallStruct { return &o.StructField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", func(o *OtherStruct) *SmallStruct { return &o.StructField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "StructPtrField.StructField") }) })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", func(o *OtherStruct) []SmallStruct { return o.SliceField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []SmallStruct) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", func(o *OtherStruct) []SmallStruct { return o.SliceField }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []SmallStruct) field.ErrorList { return validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "StructPtrField.SliceField") }) }) })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", func(o *OtherStruct) map[string]SmallStruct { return o.MapField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]SmallStruct) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", func(o *OtherStruct) map[string]SmallStruct { return o.MapField }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]SmallStruct) field.ErrorList { return validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { - return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *SmallStruct) *string { return &o.StringField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "StructPtrField.MapField") }) }) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/zz_generated.validations.go index 077b0344af6..9b3f3b4e010 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/zz_generated.validations.go @@ -60,7 +60,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return nil } // call field-attached validations - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *other.StructType) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *other.StructType) *string { return &o.StringField }, validate.DirectEqualPtr, 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.(other.StructType).StringField") })...) return 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 445597f622e..10ea325eece 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 @@ -62,19 +62,19 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return nil } // call field-attached validations - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *OtherStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *OtherStruct) *string { return &o.StringField }, validate.DirectEqualPtr, 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.StringField") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "pointerField", func(o *OtherStruct) *string { return o.PointerField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "pointerField", func(o *OtherStruct) *string { return o.PointerField }, validate.DirectEqualPtr, 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.PointerField") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", func(o *OtherStruct) *SmallStruct { return &o.StructField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", func(o *OtherStruct) *SmallStruct { return &o.StructField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructField.StructField") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", func(o *OtherStruct) []string { return o.SliceField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", func(o *OtherStruct) []string { return o.SliceField }, validate.SemanticDeepEqual, 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") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", func(o *OtherStruct) map[string]string { return o.MapField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", func(o *OtherStruct) map[string]string { return o.MapField }, validate.SemanticDeepEqual, 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") })...) return @@ -88,19 +88,19 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field return nil } // call field-attached validations - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *OtherStruct) *string { return &o.StringField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *OtherStruct) *string { return &o.StringField }, validate.DirectEqualPtr, 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.StringField") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "pointerField", func(o *OtherStruct) *string { return o.PointerField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "pointerField", func(o *OtherStruct) *string { return o.PointerField }, validate.DirectEqualPtr, 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.PointerField") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", func(o *OtherStruct) *SmallStruct { return &o.StructField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", func(o *OtherStruct) *SmallStruct { return &o.StructField }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructPtrField.StructField") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", func(o *OtherStruct) []string { return o.SliceField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", func(o *OtherStruct) []string { return o.SliceField }, validate.SemanticDeepEqual, 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") })...) - errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", func(o *OtherStruct) map[string]string { return o.MapField }, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", func(o *OtherStruct) map[string]string { return o.MapField }, validate.SemanticDeepEqual, 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") })...) return 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 21a72691d33..5ebb59dca7f 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 @@ -379,6 +379,7 @@ var ( validateEachMapVal = types.Name{Package: libValidationPkg, Name: "EachMapVal"} validateSemanticDeepEqual = types.Name{Package: libValidationPkg, Name: "SemanticDeepEqual"} validateDirectEqual = types.Name{Package: libValidationPkg, Name: "DirectEqual"} + validateDirectEqualPtr = types.Name{Package: libValidationPkg, Name: "DirectEqualPtr"} ) func (evtv eachValTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) { diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/subfield.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/subfield.go index cf9a130cd51..30b6929df96 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/subfield.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/subfield.go @@ -84,26 +84,47 @@ func (stv subfieldTagValidator) GetValidations(context Context, tag codetags.Tag return Validations{}, fmt.Errorf("variable generation is not supported") } - for _, vfn := range validations.Functions { - nilableStructType := context.Type - if !util.IsNilableType(nilableStructType) { - nilableStructType = types.PointerTo(nilableStructType) - } - nilableFieldType := submemb.Type - fieldExprPrefix := "" - if !util.IsNilableType(nilableFieldType) { - nilableFieldType = types.PointerTo(nilableFieldType) - fieldExprPrefix = "&" - } + result.Variables = append(result.Variables, validations.Variables...) - getFn := FunctionLiteral{ - Parameters: []ParamResult{{"o", nilableStructType}}, - Results: []ParamResult{{"", nilableFieldType}}, - } - getFn.Body = fmt.Sprintf("return %so.%s", fieldExprPrefix, submemb.Name) - f := Function(subfieldTagName, vfn.Flags, validateSubfield, subname, getFn, WrapperFunction{vfn, submemb.Type}) - result.Functions = append(result.Functions, f) - result.Variables = append(result.Variables, validations.Variables...) + nilableStructType := context.Type + if !util.IsNilableType(nilableStructType) { + nilableStructType = types.PointerTo(nilableStructType) + } + + nilableFieldType := submemb.Type + fieldExprPrefix := "" + if !util.IsNilableType(nilableFieldType) { + nilableFieldType = types.PointerTo(nilableFieldType) + fieldExprPrefix = "&" + } + + // getFn is the function that retrieves the subfield value from the + // struct. + getFn := FunctionLiteral{ + Parameters: []ParamResult{{"o", nilableStructType}}, + Results: []ParamResult{{"", nilableFieldType}}, + } + getFn.Body = fmt.Sprintf("return %so.%s", fieldExprPrefix, submemb.Name) + + // equivArg is the function that is used to compare the correlated + // elements in the old and new lists, for ratcheting. + var equivArg any + + // directComparable is used to determine whether we can use the direct + // comparison operator "==" or need to use the semantic DeepEqual when + // looking up and comparing correlated list elements for validation ratcheting. + directComparable := util.IsDirectComparable(util.NonPointer(util.NativeType(submemb.Type))) + if directComparable { + // It must be a pointer, since other nilable types are not directly + // comparable. + equivArg = Identifier(validateDirectEqualPtr) + } else { + equivArg = Identifier(validateSemanticDeepEqual) + } + + for _, vfn := range validations.Functions { + f := Function(subfieldTagName, vfn.Flags, validateSubfield, subname, getFn, equivArg, WrapperFunction{vfn, submemb.Type}) + result.AddFunction(f) } } return result, nil