mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 02:08:13 +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 | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
|  | ||||
| 	"k8s.io/gengo/v2" | ||||
| 	"k8s.io/gengo/v2/types" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/gengo/v2/types" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	requiredTagName  = "k8s:required" | ||||
| 	optionalTagName  = "k8s:optional" | ||||
| 	forbiddenTagName = "k8s:forbidden" | ||||
| 	defaultTagName   = "default" // TODO: this should evenually be +k8s:default | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| @@ -84,7 +89,7 @@ var ( | ||||
|  | ||||
| // TODO: It might be valuable to have a string payload for when requiredness is | ||||
| // 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 | ||||
| 	// 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 | ||||
| @@ -114,7 +119,48 @@ var ( | ||||
| 	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 | ||||
| 	// 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 | ||||
| @@ -137,6 +183,71 @@ func (requirednessTagValidator) doOptional(context Context) (Validations, error) | ||||
| 	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 ( | ||||
| 	forbiddenValueValidator   = types.Name{Package: libValidationPkg, Name: "ForbiddenValue"} | ||||
| 	forbiddenPointerValidator = types.Name{Package: libValidationPkg, Name: "ForbiddenPointer"} | ||||
|   | ||||
| @@ -17,10 +17,11 @@ limitations under the License. | ||||
| package validators | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	"k8s.io/gengo/v2/generator" | ||||
| 	"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. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Tim Hockin
					Tim Hockin