From 6bf5c1bf6c5c4db9b4dbdd986a43259bf0022918 Mon Sep 17 00:00:00 2001 From: Yongrui Lin Date: Thu, 21 May 2026 18:50:56 +0000 Subject: [PATCH] validation-gen: add tests for nilable optional fields with defaults Cover *Struct, []string, and map[string]string with non-zero defaults in the nonzero_defaults package. Each previously failed code generation and now emits the expected RequiredPointer/RequiredSlice/RequiredMap validator. --- .../tags/optional/nonzero_defaults/doc.go | 16 ++++ .../optional/nonzero_defaults/doc_test.go | 6 ++ .../zz_generated.validations.go | 91 +++++++++++++++++++ 3 files changed, 113 insertions(+) diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go index 27824cb9d57..99cfc5967f3 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go @@ -51,4 +51,20 @@ type Struct struct { // +k8s:optional // +default=true BoolPtrField *bool `json:"boolPtrField"` + + // +k8s:optional + // +default={"name": "x"} + StructPtrField *Submarker `json:"structPtrField"` + + // +k8s:optional + // +default=["foo"] + SliceField []string `json:"sliceField"` + + // +k8s:optional + // +default={"k": "v"} + MapField map[string]string `json:"mapField"` +} + +type Submarker struct { + Name string `json:"name"` } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc_test.go index 798a24c3bf8..7de239fa3ac 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc_test.go @@ -35,6 +35,9 @@ func Test(t *testing.T) { field.Required(field.NewPath("intPtrField"), ""), field.Required(field.NewPath("boolField"), ""), field.Required(field.NewPath("boolPtrField"), ""), + field.Required(field.NewPath("structPtrField"), ""), + field.Required(field.NewPath("sliceField"), ""), + field.Required(field.NewPath("mapField"), ""), }) st.Value(&Struct{ @@ -44,5 +47,8 @@ func Test(t *testing.T) { IntPtrField: ptr.To(0), BoolField: true, BoolPtrField: ptr.To(false), + StructPtrField: &Submarker{Name: "x"}, + SliceField: []string{"foo"}, + MapField: map[string]string{"k": "v"}, }).ExpectValid() } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/zz_generated.validations.go index a0376953087..6527dd65cf8 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/zz_generated.validations.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/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" @@ -243,5 +244,95 @@ func Validate_Struct( errs = append(errs, fn(fldPath.Child("boolPtrField"), obj.BoolPtrField, oldVal, oldObj != nil)...) } + { // field Struct.StructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Submarker, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Submarker { + return oldObj.StructPtrField + }) + errs = append(errs, fn(fldPath.Child("structPtrField"), obj.StructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceField + fn := func( + fldPath *field.Path, + obj, oldObj []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 + // optional fields with default values are effectively required + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.SliceField + }) + errs = append(errs, fn(fldPath.Child("sliceField"), obj.SliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapField + 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 + // optional fields with default values are effectively required + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + return errs }