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.
This commit is contained in:
Yongrui Lin 2026-05-21 18:50:56 +00:00
parent dd66e21dad
commit 6bf5c1bf6c
3 changed files with 113 additions and 0 deletions

View file

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

View file

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

View file

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