From 0310624b1f3f96d4e8b987a814c0b2fa09c3eea2 Mon Sep 17 00:00:00 2001 From: yongruilin Date: Sat, 2 Nov 2024 21:46:56 -0700 Subject: [PATCH] feat: Support `maxItems` validation --- .../apimachinery/pkg/api/validate/schema.go | 8 + .../maxitems/slice_of_primitive/doc.go | 36 +++++ .../maxitems/slice_of_primitive/docs_test.go | 42 +++++ .../zz_generated.validations.go | 72 +++++++++ .../maxitems/slice_of_slice/doc.go | 50 ++++++ .../maxitems/slice_of_slice/docs_test.go | 58 +++++++ .../zz_generated.validations.go | 92 +++++++++++ .../maxitems/slice_of_struct/doc.go | 69 ++++++++ .../maxitems/slice_of_struct/docs_test.go | 89 +++++++++++ .../zz_generated.validations.go | 148 ++++++++++++++++++ .../output_tests/maxitems/typedef/doc.go | 59 +++++++ .../maxitems/typedef/docs_test.go | 73 +++++++++ .../typedef/zz_generated.validations.go | 128 +++++++++++++++ .../cmd/validation-gen/validators/openapi.go | 15 ++ 14 files changed, 939 insertions(+) create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/doc.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/docs_test.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/zz_generated.validations.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/doc.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/docs_test.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/zz_generated.validations.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/doc.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/docs_test.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/zz_generated.validations.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/doc.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/docs_test.go create mode 100644 staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/zz_generated.validations.go 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..2847091851624 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,11 @@ func Optional[T comparable](_ operation.Context, fldPath *field.Path, value, _ * } return field.ErrorList{field.Required(fldPath, "optional value was not specified")} } + +// MaxItems verifies that the specified slice is not longer than max items. +func MaxItems[T any](_ operation.Context, fldPath *field.Path, value, _ []T, max int) field.ErrorList { + 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/maxitems/slice_of_primitive/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/doc.go new file mode 100644 index 0000000000000..19cf41499ece4 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/doc.go @@ -0,0 +1,36 @@ +/* +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 sliceofprimitive + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Maxitems +type M struct { + TypeMeta int + + // slice-of-primitive + // +k8s:validation:maxItems=1 + M0 []int `json:"m0"` + + // slice-of-pointer-to-primitive + // Validation on field only. + // +k8s:validation:maxItems=1 + M1 []*int `json:"m1"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/docs_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/docs_test.go new file mode 100644 index 0000000000000..deded3e657c9a --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/docs_test.go @@ -0,0 +1,42 @@ +/* +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 sliceofprimitive + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&M{M0: []int{0, 0}}). + ExpectInvalid( + field.TooMany(field.NewPath("m0"), 2, 1), + ) + + st.Value(&M{M0: []int{0}}). + ExpectValid() + + st.Value(&M{M1: []*int{new(int), new(int)}}). + ExpectInvalid( + field.TooMany(field.NewPath("m1"), 2, 1), + ) + + st.Value(&M{M1: []*int{new(int)}}). + ExpectValid() +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/zz_generated.validations.go new file mode 100644 index 0000000000000..d3b82762ef3c9 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_primitive/zz_generated.validations.go @@ -0,0 +1,72 @@ +//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 sliceofprimitive + +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.M0 + errs = append(errs, + func(obj, oldObj []int, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M0, safe.Field(oldObj, func(oldObj *M) []int { return oldObj.M0 }), fldPath.Child("m0"))...) + + // field M.M1 + errs = append(errs, + func(obj, oldObj []*int, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M1, safe.Field(oldObj, func(oldObj *M) []*int { return oldObj.M1 }), fldPath.Child("m1"))...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/doc.go new file mode 100644 index 0000000000000..708b8db8393fe --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/doc.go @@ -0,0 +1,50 @@ +/* +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 sliceofslice + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Maxitems +type M struct { + TypeMeta int + + // slice-of-slice-of-value + // +k8s:validation:maxItems=1 + M0 [][]int `json:"m0"` + + // slice-of-typedef-of-slice-of-value + // +k8s:validation:maxItems=1 + M1 []IntSlice `json:"m1"` + + // slice-of-slice-of-pointer + // +k8s:validation:maxItems=1 + M2 [][]*int `json:"m2"` + + // slice-of-typedef-of-slice-of-pointer + // Validation on field only. + // +k8s:validation:maxItems=1 + M3 []IntPtrSlice `json:"m3"` +} + +// Note: no limit here +type IntSlice []int + +// Note: no limit here +type IntPtrSlice []*int diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/docs_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/docs_test.go new file mode 100644 index 0000000000000..8749b5e77dba5 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/docs_test.go @@ -0,0 +1,58 @@ +/* +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 sliceofslice + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&M{M0: [][]int{{0, 0}, {0, 0}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m0"), 2, 1), + ) + + st.Value(&M{M0: [][]int{{0, 0}}}). + ExpectValid() + + st.Value(&M{M1: []IntSlice{{0}}}). + ExpectValid() + + st.Value(&M{M1: []IntSlice{{}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m1"), 2, 1), + ) + st.Value(&M{M2: [][]*int{}}). + ExpectValid() + + st.Value(&M{M2: [][]*int{{}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m2"), 2, 1), + ) + + st.Value(&M{M3: []IntPtrSlice{}}). + ExpectValid() + + st.Value(&M{M3: []IntPtrSlice{{}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m3"), 2, 1), + ) + +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/zz_generated.validations.go new file mode 100644 index 0000000000000..0b8d06d38f846 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_slice/zz_generated.validations.go @@ -0,0 +1,92 @@ +//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 sliceofslice + +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.M0 + errs = append(errs, + func(obj, oldObj [][]int, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M0, safe.Field(oldObj, func(oldObj *M) [][]int { return oldObj.M0 }), fldPath.Child("m0"))...) + + // field M.M1 + errs = append(errs, + func(obj, oldObj []IntSlice, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M1, safe.Field(oldObj, func(oldObj *M) []IntSlice { return oldObj.M1 }), fldPath.Child("m1"))...) + + // field M.M2 + errs = append(errs, + func(obj, oldObj [][]*int, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M2, safe.Field(oldObj, func(oldObj *M) [][]*int { return oldObj.M2 }), fldPath.Child("m2"))...) + + // field M.M3 + errs = append(errs, + func(obj, oldObj []IntPtrSlice, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M3, safe.Field(oldObj, func(oldObj *M) []IntPtrSlice { return oldObj.M3 }), fldPath.Child("m3"))...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/doc.go new file mode 100644 index 0000000000000..0e045e46aa219 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/doc.go @@ -0,0 +1,69 @@ +/* +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 sliceofstruct + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Maxitems +type M struct { + TypeMeta int + + // slice-of-struct + // +k8s:validation:maxItems=1 + M0 []S `json:"m0"` + + // typedef-of-slice-of-struct + // Validation on type only. + M1 SSliceLimited `json:"m1"` + // Validation on field only. + // +k8s:validation:maxItems=1 + M2 SSlice `json:"m2"` + // Validation on both type and field. + // +k8s:validation:maxItems=1 + M3 SSliceLimited `json:"m3"` + + // slice-of-pointer-to-struct + // +k8s:validation:maxItems=1 + M4 []*S `json:"m4"` + + // typedef-of-slice-of-pointer-to-struct + // Validation on type only. + M5 SPtrSliceLimited `json:"m5"` + // Validation on field only. + // +k8s:validation:maxItems=1 + M6 SPtrSlice `json:"m6"` + // Validation on both type and field. + // +k8s:validation:maxItems=1 + M7 SPtrSliceLimited `json:"m7"` +} + +type S struct{} + +// Note: no limit here +type SSlice []S + +// +k8s:validation:maxItems=2 +type SSliceLimited []S + +// +k8s:validation:maxItems=2 +type SPtrSliceLimited []*S + +// Note: no limit here +type SPtrSlice []*S diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/docs_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/docs_test.go new file mode 100644 index 0000000000000..af3a287ef97b1 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/docs_test.go @@ -0,0 +1,89 @@ +/* +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 sliceofstruct + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&M{M0: []S{{}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m0"), 2, 1), + ) + + st.Value(&M{M0: []S{{}}}). + ExpectValid() + + st.Value(&M{M1: SSliceLimited{{}}}). + ExpectValid() + + st.Value(&M{M1: SSliceLimited{{}, {}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m1"), 3, 2), + ) + st.Value(&M{M2: SSlice{}}). + ExpectValid() + + st.Value(&M{M2: SSlice{{}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m2"), 2, 1), + ) + + st.Value(&M{M3: SSliceLimited{}}). + ExpectValid() + + st.Value(&M{M3: SSliceLimited{{}, {}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m3"), 3, 1), + ) + + st.Value(&M{M4: []*S{}}). + ExpectValid() + + st.Value(&M{M4: []*S{{}, {}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m4"), 3, 1), + ) + + st.Value(&M{M5: SPtrSliceLimited{}}). + ExpectValid() + + st.Value(&M{M5: SPtrSliceLimited{{}, {}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m5"), 3, 2), + ) + + st.Value(&M{M6: SPtrSlice{}}). + ExpectValid() + + st.Value(&M{M6: SPtrSlice{{}, {}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m6"), 3, 1), + ) + + st.Value(&M{M7: SPtrSliceLimited{}}). + ExpectValid() + + st.Value(&M{M7: SPtrSliceLimited{{}, {}, {}}}). + ExpectInvalid( + field.TooMany(field.NewPath("m7"), 3, 1), + ) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/zz_generated.validations.go new file mode 100644 index 0000000000000..f3294879e1cb8 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/slice_of_struct/zz_generated.validations.go @@ -0,0 +1,148 @@ +//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 sliceofstruct + +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.M0 + errs = append(errs, + func(obj, oldObj []S, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M0, safe.Field(oldObj, func(oldObj *M) []S { return oldObj.M0 }), fldPath.Child("m0"))...) + + // field M.M1 + errs = append(errs, + func(obj, oldObj SSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + errs = append(errs, Validate_SSliceLimited(opCtx, obj, oldObj, fldPath)...) + return + }(obj.M1, safe.Field(oldObj, func(oldObj *M) SSliceLimited { return oldObj.M1 }), fldPath.Child("m1"))...) + + // field M.M2 + errs = append(errs, + func(obj, oldObj SSlice, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M2, safe.Field(oldObj, func(oldObj *M) SSlice { return oldObj.M2 }), fldPath.Child("m2"))...) + + // field M.M3 + errs = append(errs, + func(obj, oldObj SSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, Validate_SSliceLimited(opCtx, obj, oldObj, fldPath)...) + return + }(obj.M3, safe.Field(oldObj, func(oldObj *M) SSliceLimited { return oldObj.M3 }), fldPath.Child("m3"))...) + + // field M.M4 + errs = append(errs, + func(obj, oldObj []*S, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M4, safe.Field(oldObj, func(oldObj *M) []*S { return oldObj.M4 }), fldPath.Child("m4"))...) + + // field M.M5 + errs = append(errs, + func(obj, oldObj SPtrSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + errs = append(errs, Validate_SPtrSliceLimited(opCtx, obj, oldObj, fldPath)...) + return + }(obj.M5, safe.Field(oldObj, func(oldObj *M) SPtrSliceLimited { return oldObj.M5 }), fldPath.Child("m5"))...) + + // field M.M6 + errs = append(errs, + func(obj, oldObj SPtrSlice, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M6, safe.Field(oldObj, func(oldObj *M) SPtrSlice { return oldObj.M6 }), fldPath.Child("m6"))...) + + // field M.M7 + errs = append(errs, + func(obj, oldObj SPtrSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, Validate_SPtrSliceLimited(opCtx, obj, oldObj, fldPath)...) + return + }(obj.M7, safe.Field(oldObj, func(oldObj *M) SPtrSliceLimited { return oldObj.M7 }), fldPath.Child("m7"))...) + + return errs +} + +func Validate_SPtrSliceLimited(opCtx operation.Context, obj, oldObj SPtrSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + // type SPtrSliceLimited + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 2); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + + return errs +} + +func Validate_SSliceLimited(opCtx operation.Context, obj, oldObj SSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + // type SSliceLimited + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 2); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/doc.go new file mode 100644 index 0000000000000..2ce77e8b73a13 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/doc.go @@ -0,0 +1,59 @@ +/* +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 typedef + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Maxitems +type M struct { + TypeMeta int + + // typedef-of-slice-of-primitive + // Validation on type only. + M0 IntSliceLimited `json:"m0"` + // Validation on field only. + // +k8s:validation:maxItems=1 + M1 IntSlice `json:"m1"` + // Validation on both type and field. + // +k8s:validation:maxItems=1 + M2 IntSliceLimited `json:"m2"` + + // typedef-of-slice-of-pointer-to-primitive + // Validation on type only. + M3 IntPtrSliceLimited `json:"m3"` + // Validation on field only. + // +k8s:validation:maxItems=1 + M4 IntPtrSlice `json:"m4"` + // Validation on both type and field. + // +k8s:validation:maxItems=1 + M5 IntPtrSliceLimited `json:"m5"` +} + +// Note: no limit here +type IntSlice []int + +// +k8s:validation:maxItems=2 +type IntSliceLimited []int + +// Note: no limit here +type IntPtrSlice []*int + +// +k8s:validation:maxItems=2 +type IntPtrSliceLimited []*int diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/docs_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/docs_test.go new file mode 100644 index 0000000000000..b3938e5b7e576 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/docs_test.go @@ -0,0 +1,73 @@ +/* +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 typedef + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&M{M0: IntSliceLimited{0, 0, 0}}). + ExpectInvalid( + field.TooMany(field.NewPath("m0"), 3, 2), + ) + + st.Value(&M{M0: IntSliceLimited{}}). + ExpectValid() + + st.Value(&M{M1: IntSlice{}}). + ExpectValid() + + st.Value(&M{M1: IntSlice{0, 0}}). + ExpectInvalid( + field.TooMany(field.NewPath("m1"), 2, 1), + ) + st.Value(&M{M2: IntSliceLimited{}}). + ExpectValid() + + st.Value(&M{M2: IntSliceLimited{0, 0}}). + ExpectInvalid( + field.TooMany(field.NewPath("m2"), 2, 1), + ) + + st.Value(&M{M3: IntPtrSliceLimited{}}). + ExpectValid() + + st.Value(&M{M3: IntPtrSliceLimited{new(int), new(int), new(int)}}). + ExpectInvalid( + field.TooMany(field.NewPath("m3"), 3, 2), + ) + + st.Value(&M{M4: IntPtrSlice{}}). + ExpectValid() + + st.Value(&M{M4: IntPtrSlice{new(int), new(int), new(int)}}). + ExpectInvalid( + field.TooMany(field.NewPath("m4"), 3, 1), + ) + + st.Value(&M{M5: IntPtrSliceLimited{}}). + ExpectValid() + + st.Value(&M{M5: IntPtrSliceLimited{new(int), new(int), new(int)}}). + ExpectInvalid( + field.TooMany(field.NewPath("m5"), 3, 1), + ) +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/zz_generated.validations.go new file mode 100644 index 0000000000000..36a3915b7f61e --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/maxitems/typedef/zz_generated.validations.go @@ -0,0 +1,128 @@ +//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 typedef + +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_IntPtrSliceLimited(opCtx operation.Context, obj, oldObj IntPtrSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + // type IntPtrSliceLimited + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 2); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + + return errs +} + +func Validate_IntSliceLimited(opCtx operation.Context, obj, oldObj IntSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + // type IntSliceLimited + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 2); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + + return errs +} + +func Validate_M(opCtx operation.Context, obj, oldObj *M, fldPath *field.Path) (errs field.ErrorList) { + // field M.TypeMeta has no validation + + // field M.M0 + errs = append(errs, + func(obj, oldObj IntSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + errs = append(errs, Validate_IntSliceLimited(opCtx, obj, oldObj, fldPath)...) + return + }(obj.M0, safe.Field(oldObj, func(oldObj *M) IntSliceLimited { return oldObj.M0 }), fldPath.Child("m0"))...) + + // field M.M1 + errs = append(errs, + func(obj, oldObj IntSlice, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M1, safe.Field(oldObj, func(oldObj *M) IntSlice { return oldObj.M1 }), fldPath.Child("m1"))...) + + // field M.M2 + errs = append(errs, + func(obj, oldObj IntSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, Validate_IntSliceLimited(opCtx, obj, oldObj, fldPath)...) + return + }(obj.M2, safe.Field(oldObj, func(oldObj *M) IntSliceLimited { return oldObj.M2 }), fldPath.Child("m2"))...) + + // field M.M3 + errs = append(errs, + func(obj, oldObj IntPtrSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + errs = append(errs, Validate_IntPtrSliceLimited(opCtx, obj, oldObj, fldPath)...) + return + }(obj.M3, safe.Field(oldObj, func(oldObj *M) IntPtrSliceLimited { return oldObj.M3 }), fldPath.Child("m3"))...) + + // field M.M4 + errs = append(errs, + func(obj, oldObj IntPtrSlice, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + return + }(obj.M4, safe.Field(oldObj, func(oldObj *M) IntPtrSlice { return oldObj.M4 }), fldPath.Child("m4"))...) + + // field M.M5 + errs = append(errs, + func(obj, oldObj IntPtrSliceLimited, fldPath *field.Path) (errs field.ErrorList) { + if e := validate.MaxItems(opCtx, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, Validate_IntPtrSliceLimited(opCtx, obj, oldObj, fldPath)...) + return + }(obj.M5, safe.Field(oldObj, func(oldObj *M) IntPtrSliceLimited { return oldObj.M5 }), fldPath.Child("m5"))...) + + 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..b81eca7dfb983 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, ShortCircuit, maxItemsValidator, *schema.MaxItems)) + } if len(schema.Format) > 0 { formatFunction := FormatValidationFunction(schema.Format) if formatFunction != nil { @@ -83,6 +88,16 @@ func (openAPIDeclarativeValidator) Docs() []TagDoc { Description: "", Docs: "This field must be no more than X characters long.", }}, + }, { + Tag: maxItemsTagName, + Description: "Indidates that a slice field has a limit on its size.", + Contexts: []TagContext{TagContextType, TagContextField}, + Payloads: []TagPayloadDoc{ + { + Description: "", + Docs: "This field must be no more than X items long.", + }, + }, }} }