diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go new file mode 100644 index 00000000000..a99589c59c0 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go @@ -0,0 +1,47 @@ +/* +Copyright 2025 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=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +package embedded + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + TypeMeta int + + // NOTE: It's weird to have IntField in both, but Go allows it. + T2 `json:",inline"` + *T3 `json:",inline"` +} + +type T2 struct { + // +k8s:validateFalse="T2.IntField" + IntField int `json:"intField"` +} + +type T3 struct { + // +k8s:validateFalse="T3.StringField" + StringField string `json:"stringField"` + + // +k8s:validateFalse="T3.IntField" + IntField int `json:"intField"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/testdata/validate-false.json b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/testdata/validate-false.json new file mode 100644 index 00000000000..fc1a293eac6 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/testdata/validate-false.json @@ -0,0 +1,11 @@ +{ + "*embedded.T1": { + "intField": [ + "T2.IntField", + "T3.IntField" + ], + "stringField": [ + "T3.StringField" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations.go new file mode 100644 index 00000000000..bd568e65500 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations.go @@ -0,0 +1,101 @@ +//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 embedded + +import ( + context "context" + + 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 { + scheme.AddValidationFunc((*T1)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + return Validate_T1(ctx, op, nil /* fldPath */, obj.(*T1), safe.Cast[*T1](oldObj)) + }) + return nil +} + +func Validate_T1(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *T1) (errs field.ErrorList) { + // field T1.TypeMeta has no validation + + // field T1.T2 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *T2) (errs field.ErrorList) { + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath, &obj.T2, safe.Field(oldObj, func(oldObj *T1) *T2 { return &oldObj.T2 }))...) + + // field T1.T3 + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *T3) (errs field.ErrorList) { + errs = append(errs, Validate_T3(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath, obj.T3, safe.Field(oldObj, func(oldObj *T1) *T3 { return oldObj.T3 }))...) + + return errs +} + +func Validate_T2(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *T2) (errs field.ErrorList) { + // field T2.IntField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int) (errs field.ErrorList) { + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil // no changes + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T2.IntField")...) + return + }(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *T2) *int { return &oldObj.IntField }))...) + + return errs +} + +func Validate_T3(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *T3) (errs field.ErrorList) { + // field T3.StringField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil // no changes + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T3.StringField")...) + return + }(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *T3) *string { return &oldObj.StringField }))...) + + // field T3.IntField + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int) (errs field.ErrorList) { + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil // no changes + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T3.IntField")...) + return + }(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *T3) *int { return &oldObj.IntField }))...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations_test.go new file mode 100644 index 00000000000..a71c048db4a --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//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 embedded + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} 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 76828ecaa21..9b57a72c0eb 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 @@ -536,13 +536,7 @@ func (td *typeDiscoverer) discoverStruct(thisNode *typeNode, fldPath *field.Path // Discover into each field of this struct. for _, memb := range thisNode.valueType.Members { name := memb.Name - if len(name) == 0 { // embedded fields - if memb.Type.Kind == types.Pointer { - name = memb.Type.Elem.Name.Name - } else { - name = memb.Type.Name.Name - } - } + // Only do exported fields. if unicode.IsLower([]rune(name)[0]) { continue @@ -1041,10 +1035,6 @@ func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild } if buf.Len() > 0 { - if len(fld.jsonName) == 0 { - continue // TODO: Embedded (inline) types are expected to be unnamed. - } - leafType, typePfx, exprPfx := getLeafTypeAndPrefixes(fld.childType) targs := targs.WithArgs(generator.Args{ "fieldName": fld.name, @@ -1064,7 +1054,12 @@ func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild panic(fmt.Sprintf("failed to merge buffer: %v", err)) } sw.Do(" return\n", targs) - sw.Do(" }(fldPath.Child(\"$.fieldJSON$\"), ", targs) + sw.Do(" }(", targs) + if len(fld.jsonName) == 0 { // embedded (inline) field + sw.Do("fldPath, ", targs) + } else { + sw.Do("fldPath.Child(\"$.fieldJSON$\"), ", targs) + } sw.Do(" $.fieldExprPfx$obj.$.fieldName$, ", targs) sw.Do(" $.safe.Field|raw$(oldObj, ", targs) sw.Do(" func(oldObj *$.inType|raw$) $.fieldTypePfx$$.fieldType|raw$ {", targs)