diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/schema.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/schema.go index 7ae0428631891..7abcfed5afd95 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/schema.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/schema.go @@ -72,3 +72,13 @@ func Optional[T comparable](_ operation.Context, fldPath *field.Path, value, _ * } return field.ErrorList{field.Required(fldPath, "optional value was not specified")} } + +func MaxItems[T any](_ operation.Context, fldPath *field.Path, value, _ []T, max int) field.ErrorList { + if value == nil { + return nil + } + if len(value) > max { + return field.ErrorList{field.TooMany(fldPath, len(value), max)} + } + return nil +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/maxitems/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/maxitems/doc.go new file mode 100644 index 0000000000000..590c7678993ce --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/maxitems/doc.go @@ -0,0 +1,35 @@ +/* +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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +package maxitems + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Maxitems +type M struct { + TypeMeta int + + // +k8s:validation:maxItems=3 + M1 []T `json:"m1"` +} + +type T struct{} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/maxitems/doc_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/maxitems/doc_test.go new file mode 100644 index 0000000000000..43ece1b0596c8 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/maxitems/doc_test.go @@ -0,0 +1,33 @@ +/* +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 maxitems + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&M{M1: []T{{}, {}}}).ExpectValid() + + st.Value(&M{M1: []T{{}, {}, {}, {}}}).ExpectInvalid( + field.TooMany(field.NewPath("m1"), 4, 3), + ) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/maxitems/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/maxitems/zz_generated.validations.go new file mode 100644 index 0000000000000..ec813b9b307b3 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/maxitems/zz_generated.validations.go @@ -0,0 +1,59 @@ +//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 maxitems + +import ( + 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((*M)(nil), func(opCtx operation.Context, obj, oldObj interface{}, subresources ...string) field.ErrorList { + if len(subresources) == 0 { + return Validate_M(opCtx, obj.(*M), safe.Cast[*M](oldObj), nil) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresources: %v", obj, subresources))} + }) + return nil +} + +func Validate_M(opCtx operation.Context, obj, oldObj *M, fldPath *field.Path) (errs field.ErrorList) { + // field M.TypeMeta has no validation + + // field M.M1 + errs = append(errs, + func(obj, oldObj []T, fldPath *field.Path) (errs field.ErrorList) { + errs = append(errs, validate.MaxItems(opCtx, fldPath, obj, oldObj, 3)...) + return + }(obj.M1, safe.Field(oldObj, func(oldObj *M) []T { return oldObj.M1 }), fldPath.Child("m1"))...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/openapi.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/openapi.go index d7e5b16fbf08b..4b3627ed2bdc1 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/openapi.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/openapi.go @@ -35,12 +35,14 @@ const ( markerPrefix = "k8s:validation:" formatTagName = markerPrefix + ":format" maxLengthTagName = markerPrefix + ":maxLength" + maxItemsTagName = markerPrefix + ":maxItems" ) var ( ipValidator = types.Name{Package: libValidationPkg, Name: "IP"} dnsLabelValidator = types.Name{Package: libValidationPkg, Name: "DNSLabel"} maxLengthValidator = types.Name{Package: libValidationPkg, Name: "MaxLength"} + maxItemsValidator = types.Name{Package: libValidationPkg, Name: "MaxItems"} ) func (openAPIDeclarativeValidator) ExtractValidations(t *types.Type, comments []string) (Validations, error) { @@ -53,6 +55,9 @@ func (openAPIDeclarativeValidator) ExtractValidations(t *types.Type, comments [] if schema.MaxLength != nil { result.AddFunction(Function(maxLengthTagName, DefaultFlags, maxLengthValidator, *schema.MaxLength)) } + if schema.MaxItems != nil { + result.AddFunction(Function(maxItemsTagName, DefaultFlags, maxItemsValidator, *schema.MaxItems)) + } if len(schema.Format) > 0 { formatFunction := FormatValidationFunction(schema.Format) if formatFunction != nil {