mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Handle optional value-types with defaults
This commit is contained in:
		@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This is a test package.
 | 
				
			||||||
 | 
					package nonzerodefaults
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "k8s.io/code-generator/cmd/validation-gen/testscheme"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var localSchemeBuilder = testscheme.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Struct struct {
 | 
				
			||||||
 | 
						TypeMeta int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default="foobar"
 | 
				
			||||||
 | 
						StringField string `json:"stringField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default="foobar"
 | 
				
			||||||
 | 
						StringPtrField *string `json:"stringPtrField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=123
 | 
				
			||||||
 | 
						IntField int `json:"intField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=123
 | 
				
			||||||
 | 
						IntPtrField *int `json:"intPtrField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=true
 | 
				
			||||||
 | 
						BoolField bool `json:"boolField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=true
 | 
				
			||||||
 | 
						BoolPtrField *bool `json:"boolPtrField"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 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 nonzerodefaults
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/utils/ptr"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test(t *testing.T) {
 | 
				
			||||||
 | 
						st := localSchemeBuilder.Test(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						st.Value(&Struct{
 | 
				
			||||||
 | 
							// All zero-values.
 | 
				
			||||||
 | 
						}).ExpectRegexpsByPath(map[string][]string{
 | 
				
			||||||
 | 
							"stringField":    {"Required value"},
 | 
				
			||||||
 | 
							"stringPtrField": {"Required value"},
 | 
				
			||||||
 | 
							"intField":       {"Required value"},
 | 
				
			||||||
 | 
							"intPtrField":    {"Required value"},
 | 
				
			||||||
 | 
							"boolField":      {"Required value"},
 | 
				
			||||||
 | 
							"boolPtrField":   {"Required value"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						st.Value(&Struct{
 | 
				
			||||||
 | 
							StringField:    "abc",
 | 
				
			||||||
 | 
							StringPtrField: ptr.To(""),
 | 
				
			||||||
 | 
							IntField:       123,
 | 
				
			||||||
 | 
							IntPtrField:    ptr.To(0),
 | 
				
			||||||
 | 
							BoolField:      true,
 | 
				
			||||||
 | 
							BoolPtrField:   ptr.To(false),
 | 
				
			||||||
 | 
						}).ExpectValid()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					//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 nonzerodefaults
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 {
 | 
				
			||||||
 | 
						scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}, subresources ...string) field.ErrorList {
 | 
				
			||||||
 | 
							if len(subresources) == 0 {
 | 
				
			||||||
 | 
								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, subresources: %v", obj, subresources))}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.StringField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional fields with default values are effectively required
 | 
				
			||||||
 | 
								if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
 | 
				
			||||||
 | 
									errs = append(errs, e...)
 | 
				
			||||||
 | 
									return // do not proceed
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.StringPtrField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional fields with default values are effectively required
 | 
				
			||||||
 | 
								if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
 | 
				
			||||||
 | 
									errs = append(errs, e...)
 | 
				
			||||||
 | 
									return // do not proceed
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("stringPtrField"), obj.StringPtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.StringPtrField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.IntField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *int) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional fields with default values are effectively required
 | 
				
			||||||
 | 
								if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
 | 
				
			||||||
 | 
									errs = append(errs, e...)
 | 
				
			||||||
 | 
									return // do not proceed
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.IntPtrField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *int) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional fields with default values are effectively required
 | 
				
			||||||
 | 
								if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
 | 
				
			||||||
 | 
									errs = append(errs, e...)
 | 
				
			||||||
 | 
									return // do not proceed
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("intPtrField"), obj.IntPtrField, safe.Field(oldObj, func(oldObj *Struct) *int { return oldObj.IntPtrField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.BoolField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *bool) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional fields with default values are effectively required
 | 
				
			||||||
 | 
								if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
 | 
				
			||||||
 | 
									errs = append(errs, e...)
 | 
				
			||||||
 | 
									return // do not proceed
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("boolField"), &obj.BoolField, safe.Field(oldObj, func(oldObj *Struct) *bool { return &oldObj.BoolField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.BoolPtrField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *bool) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional fields with default values are effectively required
 | 
				
			||||||
 | 
								if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
 | 
				
			||||||
 | 
									errs = append(errs, e...)
 | 
				
			||||||
 | 
									return // do not proceed
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("boolPtrField"), obj.BoolPtrField, safe.Field(oldObj, func(oldObj *Struct) *bool { return oldObj.BoolPtrField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This is a test package.
 | 
				
			||||||
 | 
					package zerodefaults
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "k8s.io/code-generator/cmd/validation-gen/testscheme"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var localSchemeBuilder = testscheme.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Struct struct {
 | 
				
			||||||
 | 
						TypeMeta int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=""
 | 
				
			||||||
 | 
						StringField string `json:"stringField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=""
 | 
				
			||||||
 | 
						StringPtrField *string `json:"stringPtrField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=0
 | 
				
			||||||
 | 
						IntField int `json:"intField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=0
 | 
				
			||||||
 | 
						IntPtrField *int `json:"intPtrField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=false
 | 
				
			||||||
 | 
						BoolField bool `json:"boolField"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// +k8s:optional
 | 
				
			||||||
 | 
						// +default=false
 | 
				
			||||||
 | 
						BoolPtrField *bool `json:"boolPtrField"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 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 zerodefaults
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/utils/ptr"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test(t *testing.T) {
 | 
				
			||||||
 | 
						st := localSchemeBuilder.Test(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						st.Value(&Struct{
 | 
				
			||||||
 | 
							// All zero-values.
 | 
				
			||||||
 | 
						}).ExpectRegexpsByPath(map[string][]string{
 | 
				
			||||||
 | 
							// "stringField": optional value fields with zero defaults are just docs
 | 
				
			||||||
 | 
							// "intField": optional value fields with zero defaults are just docs
 | 
				
			||||||
 | 
							// "boolField": optional value fields with zero defaults are just docs
 | 
				
			||||||
 | 
							"stringPtrField": {"Required value"},
 | 
				
			||||||
 | 
							"intPtrField":    {"Required value"},
 | 
				
			||||||
 | 
							"boolPtrField":   {"Required value"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						st.Value(&Struct{
 | 
				
			||||||
 | 
							StringField:    "abc",
 | 
				
			||||||
 | 
							StringPtrField: ptr.To(""),
 | 
				
			||||||
 | 
							IntField:       123,
 | 
				
			||||||
 | 
							IntPtrField:    ptr.To(0),
 | 
				
			||||||
 | 
							BoolField:      true,
 | 
				
			||||||
 | 
							BoolPtrField:   ptr.To(false),
 | 
				
			||||||
 | 
						}).ExpectValid()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					//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 zerodefaults
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 {
 | 
				
			||||||
 | 
						scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}, subresources ...string) field.ErrorList {
 | 
				
			||||||
 | 
							if len(subresources) == 0 {
 | 
				
			||||||
 | 
								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, subresources: %v", obj, subresources))}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.StringField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional value-type fields with zero-value defaults are purely documentation
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.StringPtrField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional fields with default values are effectively required
 | 
				
			||||||
 | 
								if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
 | 
				
			||||||
 | 
									errs = append(errs, e...)
 | 
				
			||||||
 | 
									return // do not proceed
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("stringPtrField"), obj.StringPtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.StringPtrField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.IntField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *int) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional value-type fields with zero-value defaults are purely documentation
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.IntPtrField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *int) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional fields with default values are effectively required
 | 
				
			||||||
 | 
								if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
 | 
				
			||||||
 | 
									errs = append(errs, e...)
 | 
				
			||||||
 | 
									return // do not proceed
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("intPtrField"), obj.IntPtrField, safe.Field(oldObj, func(oldObj *Struct) *int { return oldObj.IntPtrField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.BoolField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *bool) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional value-type fields with zero-value defaults are purely documentation
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("boolField"), &obj.BoolField, safe.Field(oldObj, func(oldObj *Struct) *bool { return &oldObj.BoolField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// field Struct.BoolPtrField
 | 
				
			||||||
 | 
						errs = append(errs,
 | 
				
			||||||
 | 
							func(fldPath *field.Path, obj, oldObj *bool) (errs field.ErrorList) {
 | 
				
			||||||
 | 
								// optional fields with default values are effectively required
 | 
				
			||||||
 | 
								if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
 | 
				
			||||||
 | 
									errs = append(errs, e...)
 | 
				
			||||||
 | 
									return // do not proceed
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}(fldPath.Child("boolPtrField"), obj.BoolPtrField, safe.Field(oldObj, func(oldObj *Struct) *bool { return oldObj.BoolPtrField }))...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -17,16 +17,21 @@ limitations under the License.
 | 
				
			|||||||
package validators
 | 
					package validators
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/gengo/v2"
 | 
				
			||||||
 | 
						"k8s.io/gengo/v2/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
						"k8s.io/apimachinery/pkg/util/sets"
 | 
				
			||||||
	"k8s.io/gengo/v2/types"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	requiredTagName  = "k8s:required"
 | 
						requiredTagName  = "k8s:required"
 | 
				
			||||||
	optionalTagName  = "k8s:optional"
 | 
						optionalTagName  = "k8s:optional"
 | 
				
			||||||
	forbiddenTagName = "k8s:forbidden"
 | 
						forbiddenTagName = "k8s:forbidden"
 | 
				
			||||||
 | 
						defaultTagName   = "default" // TODO: this should evenually be +k8s:default
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
@@ -84,7 +89,7 @@ var (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// TODO: It might be valuable to have a string payload for when requiredness is
 | 
					// TODO: It might be valuable to have a string payload for when requiredness is
 | 
				
			||||||
// conditional (e.g. required when <otherfield> is specified).
 | 
					// conditional (e.g. required when <otherfield> is specified).
 | 
				
			||||||
func (requirednessTagValidator) doRequired(context Context) (Validations, error) {
 | 
					func (rtv requirednessTagValidator) doRequired(context Context) (Validations, error) {
 | 
				
			||||||
	// Most validators don't care whether the value they are validating was
 | 
						// Most validators don't care whether the value they are validating was
 | 
				
			||||||
	// originally defined as a value-type or a pointer-type in the API.  This
 | 
						// originally defined as a value-type or a pointer-type in the API.  This
 | 
				
			||||||
	// one does.  Since Go doesn't do partial specialization of templates, we
 | 
						// one does.  Since Go doesn't do partial specialization of templates, we
 | 
				
			||||||
@@ -114,7 +119,48 @@ var (
 | 
				
			|||||||
	optionalMapValidator     = types.Name{Package: libValidationPkg, Name: "OptionalMap"}
 | 
						optionalMapValidator     = types.Name{Package: libValidationPkg, Name: "OptionalMap"}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (requirednessTagValidator) doOptional(context Context) (Validations, error) {
 | 
					func (rtv requirednessTagValidator) doOptional(context Context) (Validations, error) {
 | 
				
			||||||
 | 
						// All of our tags are expressed from the perspective of a client of the
 | 
				
			||||||
 | 
						// API, but the code we generate is for the server. Optional is tricky.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// A field which is marked as optional and does not have a default is
 | 
				
			||||||
 | 
						// strictly optional. A client is allowed to not set it and the server will
 | 
				
			||||||
 | 
						// not give it a default value. Code which consumes it must handle that it
 | 
				
			||||||
 | 
						// might not have any value at all.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// A field which is marked as optional but has a default is optional to
 | 
				
			||||||
 | 
						// clients, but required to the server. A client is allowed to not set it
 | 
				
			||||||
 | 
						// but the server will give it a default value. Code which consumes it can
 | 
				
			||||||
 | 
						// assume that it always has a value.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// One special case must be handled: optional non-pointer fields with
 | 
				
			||||||
 | 
						// default values. If the default is not the zero value for the type, then
 | 
				
			||||||
 | 
						// the zero value is used to decide whether to assign the default value,
 | 
				
			||||||
 | 
						// and so must be out of bounds; we can proceed as above.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// But if the default is the zero value, then the zero value is obviously
 | 
				
			||||||
 | 
						// valid, and the fact that the field is optional is meaningless - there is
 | 
				
			||||||
 | 
						// no way to tell the difference between a client not setting it (yielding
 | 
				
			||||||
 | 
						// the zero value) and a client setting it to the zero value.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// TODO: handle default=ref(...)
 | 
				
			||||||
 | 
						// TODO: handle manual defaulting
 | 
				
			||||||
 | 
						if hasDefault, zeroDefault, err := rtv.hasZeroDefault(context); err != nil {
 | 
				
			||||||
 | 
							return Validations{}, err
 | 
				
			||||||
 | 
						} else if hasDefault {
 | 
				
			||||||
 | 
							if !isNilableType(context.Type) && zeroDefault {
 | 
				
			||||||
 | 
								return Validations{Comments: []string{"optional value-type fields with zero-value defaults are purely documentation"}}, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							validations, err := rtv.doRequired(context)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return Validations{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for i, fn := range validations.Functions {
 | 
				
			||||||
 | 
								validations.Functions[i] = WithComment(fn, "optional fields with default values are effectively required")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return validations, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Most validators don't care whether the value they are validating was
 | 
						// Most validators don't care whether the value they are validating was
 | 
				
			||||||
	// originally defined as a value-type or a pointer-type in the API.  This
 | 
						// originally defined as a value-type or a pointer-type in the API.  This
 | 
				
			||||||
	// one does.  Since Go doesn't do partial specialization of templates, we
 | 
						// one does.  Since Go doesn't do partial specialization of templates, we
 | 
				
			||||||
@@ -137,6 +183,71 @@ func (requirednessTagValidator) doOptional(context Context) (Validations, error)
 | 
				
			|||||||
	return Validations{Functions: []FunctionGen{Function(optionalTagName, ShortCircuit|NonError, optionalValueValidator)}}, nil
 | 
						return Validations{Functions: []FunctionGen{Function(optionalTagName, ShortCircuit|NonError, optionalValueValidator)}}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// hasZeroDefault returns whether the field has a default value and whether
 | 
				
			||||||
 | 
					// that default value is the zero value for the field's type.
 | 
				
			||||||
 | 
					func (rtv requirednessTagValidator) hasZeroDefault(context Context) (bool, bool, error) {
 | 
				
			||||||
 | 
						t := realType(context.Type)
 | 
				
			||||||
 | 
						// This validator only applies to fields, so Member must be valid.
 | 
				
			||||||
 | 
						tagsByName, err := gengo.ExtractFunctionStyleCommentTags("+", []string{defaultTagName}, context.Member.CommentLines)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, false, fmt.Errorf("failed to read tags: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags, hasDefault := tagsByName[defaultTagName]
 | 
				
			||||||
 | 
						if !hasDefault {
 | 
				
			||||||
 | 
							return false, false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(tags) == 0 {
 | 
				
			||||||
 | 
							return false, false, fmt.Errorf("+default tag with no value")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(tags) > 1 {
 | 
				
			||||||
 | 
							return false, false, fmt.Errorf("+default tag with multiple values: %q", tags)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						payload := tags[0].Value
 | 
				
			||||||
 | 
						var defaultValue any
 | 
				
			||||||
 | 
						if err := json.Unmarshal([]byte(payload), &defaultValue); err != nil {
 | 
				
			||||||
 | 
							return false, false, fmt.Errorf("failed to parse default value %q: %w", payload, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if defaultValue == nil {
 | 
				
			||||||
 | 
							return false, false, fmt.Errorf("failed to parse default value %q: unmarshalled to nil", payload)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						zero, found := typeZeroValue[t.String()]
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							return false, false, fmt.Errorf("unknown zero-value for type %s", t.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true, reflect.DeepEqual(defaultValue, zero), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This is copied from defaulter-gen.
 | 
				
			||||||
 | 
					// TODO: move this back to gengo as Type.ZeroValue()?
 | 
				
			||||||
 | 
					var typeZeroValue = map[string]any{
 | 
				
			||||||
 | 
						"uint":        0.,
 | 
				
			||||||
 | 
						"uint8":       0.,
 | 
				
			||||||
 | 
						"uint16":      0.,
 | 
				
			||||||
 | 
						"uint32":      0.,
 | 
				
			||||||
 | 
						"uint64":      0.,
 | 
				
			||||||
 | 
						"int":         0.,
 | 
				
			||||||
 | 
						"int8":        0.,
 | 
				
			||||||
 | 
						"int16":       0.,
 | 
				
			||||||
 | 
						"int32":       0.,
 | 
				
			||||||
 | 
						"int64":       0.,
 | 
				
			||||||
 | 
						"byte":        0.,
 | 
				
			||||||
 | 
						"float64":     0.,
 | 
				
			||||||
 | 
						"float32":     0.,
 | 
				
			||||||
 | 
						"bool":        false,
 | 
				
			||||||
 | 
						"time.Time":   "",
 | 
				
			||||||
 | 
						"string":      "",
 | 
				
			||||||
 | 
						"integer":     0.,
 | 
				
			||||||
 | 
						"number":      0.,
 | 
				
			||||||
 | 
						"boolean":     false,
 | 
				
			||||||
 | 
						"[]byte":      "", // base64 encoded characters
 | 
				
			||||||
 | 
						"interface{}": interface{}(nil),
 | 
				
			||||||
 | 
						"any":         interface{}(nil),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	forbiddenValueValidator   = types.Name{Package: libValidationPkg, Name: "ForbiddenValue"}
 | 
						forbiddenValueValidator   = types.Name{Package: libValidationPkg, Name: "ForbiddenValue"}
 | 
				
			||||||
	forbiddenPointerValidator = types.Name{Package: libValidationPkg, Name: "ForbiddenPointer"}
 | 
						forbiddenPointerValidator = types.Name{Package: libValidationPkg, Name: "ForbiddenPointer"}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,10 +17,11 @@ limitations under the License.
 | 
				
			|||||||
package validators
 | 
					package validators
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
					 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
					 | 
				
			||||||
	"k8s.io/gengo/v2/generator"
 | 
						"k8s.io/gengo/v2/generator"
 | 
				
			||||||
	"k8s.io/gengo/v2/types"
 | 
						"k8s.io/gengo/v2/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/sets"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TagValidator describes a single validation tag and how to use it.
 | 
					// TagValidator describes a single validation tag and how to use it.
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user