mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 22:40:35 +00:00
add +k8s:maxItems tag logic and tests
This commit is contained in:
@@ -22,14 +22,12 @@ import (
|
||||
"testing"
|
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
@@ -66,47 +64,31 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
|
||||
input: mkValidResourceClaim(tweakDevicesConstraints(32)),
|
||||
},
|
||||
"valid config, max allowed": {
|
||||
input: mkValidResourceClaim(tweakDevicesRequests(32)),
|
||||
input: mkValidResourceClaim(tweakDevicesConfigs(32)),
|
||||
},
|
||||
"invalid requests, too many": {
|
||||
input: mkValidResourceClaim(tweakDevicesConfigs(33)),
|
||||
input: mkValidResourceClaim(tweakDevicesRequests(33)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32),
|
||||
field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
"invalid constraints, too many": {
|
||||
input: mkValidResourceClaim(tweakDevicesConstraints(33)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices", "constraints"), 33, 32),
|
||||
field.TooMany(field.NewPath("spec", "devices", "constraints"), 33, 32).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
"invalid config, too many": {
|
||||
input: mkValidResourceClaim(tweakDevicesRequests(33)),
|
||||
input: mkValidResourceClaim(tweakDevicesConfigs(33)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices", "config"), 33, 32),
|
||||
field.TooMany(field.NewPath("spec", "devices", "config"), 33, 32).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
// TODO: Add more test cases
|
||||
}
|
||||
for k, tc := range testCases {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
var declarativeTakeoverErrs field.ErrorList
|
||||
var imperativeErrs field.ErrorList
|
||||
for _, gateVal := range []bool{true, false} {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeclarativeValidation, gateVal)
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeclarativeValidationTakeover, gateVal)
|
||||
|
||||
errs := Strategy.Validate(ctx, &tc.input)
|
||||
if gateVal {
|
||||
declarativeTakeoverErrs = errs
|
||||
} else {
|
||||
imperativeErrs = errs
|
||||
}
|
||||
}
|
||||
equivalenceMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin()
|
||||
equivalenceMatcher.Test(t, imperativeErrs, declarativeTakeoverErrs)
|
||||
|
||||
apitesting.VerifyVersionedValidationEquivalence(t, &tc.input, nil)
|
||||
apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -129,7 +111,8 @@ func tweakDevicesConstraints(items int) func(*resource.ResourceClaim) {
|
||||
|
||||
func tweakDevicesRequests(items int) func(*resource.ResourceClaim) {
|
||||
return func(rc *resource.ResourceClaim) {
|
||||
for i := 0; i < items; i++ {
|
||||
// The first request already exists in the valid template
|
||||
for i := 1; i < items; i++ {
|
||||
rc.Spec.Devices.Requests = append(rc.Spec.Devices.Requests, mkDeviceRequest(fmt.Sprintf("req-%d", i)))
|
||||
}
|
||||
}
|
||||
@@ -138,13 +121,20 @@ func tweakDevicesRequests(items int) func(*resource.ResourceClaim) {
|
||||
func mkDeviceClaimConfiguration() resource.DeviceClaimConfiguration {
|
||||
return resource.DeviceClaimConfiguration{
|
||||
Requests: []string{"req-0"},
|
||||
DeviceConfiguration: resource.DeviceConfiguration{
|
||||
Opaque: &resource.OpaqueDeviceConfiguration{
|
||||
Driver: "dra.example.com",
|
||||
Parameters: runtime.RawExtension{
|
||||
Raw: []byte(`{"kind": "foo", "apiVersion": "dra.example.com/v1"}`),
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mkDeviceConstraint() resource.DeviceConstraint {
|
||||
return resource.DeviceConstraint{
|
||||
Requests: []string{"req-0"},
|
||||
MatchAttribute: pointer.To(resource.FullyQualifiedName("a")),
|
||||
MatchAttribute: pointer.To(resource.FullyQualifiedName("foo/bar")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,23 +179,9 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
|
||||
}
|
||||
for k, tc := range testCases {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
var declarativeTakeoverErrs field.ErrorList
|
||||
var imperativeErrs field.ErrorList
|
||||
for _, gateVal := range []bool{true, false} {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeclarativeValidation, gateVal)
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeclarativeValidationTakeover, gateVal)
|
||||
|
||||
errs := Strategy.ValidateUpdate(ctx, &tc.update, &tc.old)
|
||||
if gateVal {
|
||||
declarativeTakeoverErrs = errs
|
||||
} else {
|
||||
imperativeErrs = errs
|
||||
}
|
||||
}
|
||||
equivalenceMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin()
|
||||
equivalenceMatcher.Test(t, imperativeErrs, declarativeTakeoverErrs)
|
||||
|
||||
apitesting.VerifyVersionedValidationEquivalence(t, &tc.update, &tc.old)
|
||||
tc.old.ResourceVersion = "1"
|
||||
tc.update.ResourceVersion = "2"
|
||||
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, Strategy.ValidateUpdate, tc.expectedErrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -282,51 +258,7 @@ func TestValidateStatusUpdateForDeclarative(t *testing.T) {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
tc.old.ObjectMeta.ResourceVersion = "1"
|
||||
tc.update.ObjectMeta.ResourceVersion = "1"
|
||||
var declarativeTakeoverErrs field.ErrorList
|
||||
var imperativeErrs field.ErrorList
|
||||
for _, gateVal := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("gate=%v", gateVal), func(t *testing.T) {
|
||||
// We only need to test both gate enabled and disabled together, because
|
||||
// 1) the DeclarativeValidationTakeover won't take effect if DeclarativeValidation is disabled.
|
||||
// 2) the validation output, when only DeclarativeValidation is enabled, is the same as when both gates are disabled.
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeclarativeValidation, gateVal)
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeclarativeValidationTakeover, gateVal)
|
||||
errs := strategy.ValidateUpdate(ctx, &tc.update, &tc.old)
|
||||
if gateVal {
|
||||
declarativeTakeoverErrs = errs
|
||||
} else {
|
||||
imperativeErrs = errs
|
||||
}
|
||||
// The errOutputMatcher is used to verify the output matches the expected errors in test cases.
|
||||
errOutputMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin()
|
||||
|
||||
if len(tc.expectedErrs) > 0 {
|
||||
errOutputMatcher.Test(t, tc.expectedErrs, errs)
|
||||
} else if len(errs) != 0 {
|
||||
t.Errorf("expected no errors, but got: %v", errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
// The equivalenceMatcher is used to verify the output errors from hand-written imperative validation
|
||||
// are equivalent to the output errors when DeclarativeValidationTakeover is enabled.
|
||||
equivalenceMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin()
|
||||
// TODO: remove this once ErrorMatcher has been extended to handle this form of deduplication.
|
||||
dedupedImperativeErrs := field.ErrorList{}
|
||||
for _, err := range imperativeErrs {
|
||||
found := false
|
||||
for _, existingErr := range dedupedImperativeErrs {
|
||||
if equivalenceMatcher.Matches(existingErr, err) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
dedupedImperativeErrs = append(dedupedImperativeErrs, err)
|
||||
}
|
||||
}
|
||||
equivalenceMatcher.Test(t, dedupedImperativeErrs, declarativeTakeoverErrs)
|
||||
|
||||
apitesting.VerifyVersionedValidationEquivalence(t, &tc.update, &tc.old)
|
||||
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, strategy.ValidateUpdate, tc.expectedErrs, "status")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,26 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// MaxLength verifies that the specified value is not longer than max
|
||||
// characters.
|
||||
func MaxLength[T ~string](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, max int) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if len(*value) > max {
|
||||
return field.ErrorList{field.TooLong(fldPath, *value, max).WithOrigin("maxLength")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxItems verifies that the specified slice is not longer than max items.
|
||||
func MaxItems[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T, max int) field.ErrorList {
|
||||
if len(value) > max {
|
||||
return field.ErrorList{field.TooMany(fldPath, len(value), max).WithOrigin("maxItems")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Minimum verifies that the specified value is greater than or equal to min.
|
||||
func Minimum[T constraints.Integer](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, min T) field.ErrorList {
|
||||
if value == nil {
|
||||
|
||||
@@ -26,6 +26,115 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func TestMaxLength(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
value string
|
||||
max int
|
||||
wantErrs field.ErrorList // regex
|
||||
}{{
|
||||
name: "empty string",
|
||||
value: "",
|
||||
max: 0,
|
||||
wantErrs: nil,
|
||||
}, {
|
||||
name: "zero length",
|
||||
value: "0",
|
||||
max: 0,
|
||||
wantErrs: field.ErrorList{
|
||||
field.TooLong(field.NewPath("fldpath"), nil, 0).WithOrigin("maxLength"),
|
||||
},
|
||||
}, {
|
||||
name: "one character",
|
||||
value: "0",
|
||||
max: 1,
|
||||
wantErrs: nil,
|
||||
}, {
|
||||
name: "two characters",
|
||||
value: "01",
|
||||
max: 1,
|
||||
wantErrs: field.ErrorList{
|
||||
field.TooLong(field.NewPath("fldpath"), nil, 1).WithOrigin("maxLength"),
|
||||
},
|
||||
}, {
|
||||
value: "",
|
||||
max: -1,
|
||||
wantErrs: field.ErrorList{
|
||||
field.TooLong(field.NewPath("fldpath"), nil, -1).WithOrigin("maxLength"),
|
||||
},
|
||||
}}
|
||||
|
||||
matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType()
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
v := tc.value
|
||||
gotErrs := MaxLength(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.max)
|
||||
matcher.Test(t, tc.wantErrs, gotErrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxItems(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := make([]string, 0)
|
||||
max := 0
|
||||
return MaxItems(context.Background(), op, fp, value, nil, max)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := make([]string, 1)
|
||||
max := 0
|
||||
return MaxItems(context.Background(), op, fp, value, nil, max)
|
||||
},
|
||||
err: "fldpath: Too many.*must have at most",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := make([]int, 1)
|
||||
max := 1
|
||||
return MaxItems(context.Background(), op, fp, value, nil, max)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := make([]int, 2)
|
||||
max := 1
|
||||
return MaxItems(context.Background(), op, fp, value, nil, max)
|
||||
},
|
||||
err: "fldpath: Too many.*must have at most",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := make([]bool, 0)
|
||||
max := -1
|
||||
return MaxItems(context.Background(), op, fp, value, nil, max)
|
||||
},
|
||||
err: "fldpath: Too many.*too many items",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinimum(t *testing.T) {
|
||||
testMinimumPositive[int](t)
|
||||
testMinimumNegative[int](t)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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()
|
||||
|
||||
type Struct struct {
|
||||
TypeMeta int
|
||||
|
||||
// +k8s:maxItems=0
|
||||
Max0Field []int `json:"max0Field"`
|
||||
|
||||
// +k8s:maxItems=10
|
||||
Max10Field []int `json:"max10Field"`
|
||||
|
||||
// +k8s:maxItems=0
|
||||
Max0TypedefField []IntType `json:"max0TypedefField"`
|
||||
|
||||
// +k8s:maxItems=10
|
||||
Max10TypedefField []IntType `json:"max10TypedefField"`
|
||||
}
|
||||
|
||||
type IntType int
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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(&Struct{
|
||||
// All zero values
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
Max0Field: make([]int, 0),
|
||||
Max10Field: make([]int, 0),
|
||||
Max0TypedefField: make([]IntType, 0),
|
||||
Max10TypedefField: make([]IntType, 0),
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
Max10Field: make([]int, 1),
|
||||
Max10TypedefField: make([]IntType, 1),
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
Max10Field: make([]int, 9),
|
||||
Max10TypedefField: make([]IntType, 9),
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
Max10Field: make([]int, 10),
|
||||
Max10TypedefField: make([]IntType, 10),
|
||||
}).ExpectValid()
|
||||
|
||||
testVal := &Struct{
|
||||
Max0Field: make([]int, 1),
|
||||
Max10Field: make([]int, 11),
|
||||
Max0TypedefField: make([]IntType, 1),
|
||||
Max10TypedefField: make([]IntType, 11),
|
||||
}
|
||||
st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.TooMany(field.NewPath("max0Field"), 1, 0),
|
||||
field.TooMany(field.NewPath("max10Field"), 11, 10),
|
||||
field.TooMany(field.NewPath("max0TypedefField"), 1, 0),
|
||||
field.TooMany(field.NewPath("max10TypedefField"), 11, 10),
|
||||
})
|
||||
// Test validation ratcheting
|
||||
st.Value(&Struct{
|
||||
Max0Field: make([]int, 1),
|
||||
Max10Field: make([]int, 11),
|
||||
Max0TypedefField: make([]IntType, 1),
|
||||
Max10TypedefField: make([]IntType, 11),
|
||||
}).OldValue(&Struct{
|
||||
Max0Field: make([]int, 1),
|
||||
Max10Field: make([]int, 11),
|
||||
Max0TypedefField: make([]IntType, 1),
|
||||
Max10TypedefField: make([]IntType, 11),
|
||||
}).ExpectValid()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//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 (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
|
||||
equality "k8s.io/apimachinery/pkg/api/equality"
|
||||
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 {
|
||||
// type Struct
|
||||
scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
|
||||
switch op.Request.SubresourcePath() {
|
||||
case "/":
|
||||
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, subresource: %v", obj, op.Request.SubresourcePath()))}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate_Struct validates an instance of Struct according
|
||||
// to declarative validation rules in the API schema.
|
||||
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.Max0Field
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []int) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("max0Field"), obj.Max0Field, safe.Field(oldObj, func(oldObj *Struct) []int { return oldObj.Max0Field }))...)
|
||||
|
||||
// field Struct.Max10Field
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []int) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("max10Field"), obj.Max10Field, safe.Field(oldObj, func(oldObj *Struct) []int { return oldObj.Max10Field }))...)
|
||||
|
||||
// field Struct.Max0TypedefField
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []IntType) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("max0TypedefField"), obj.Max0TypedefField, safe.Field(oldObj, func(oldObj *Struct) []IntType { return oldObj.Max0TypedefField }))...)
|
||||
|
||||
// field Struct.Max10TypedefField
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []IntType) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("max10TypedefField"), obj.Max10TypedefField, safe.Field(oldObj, func(oldObj *Struct) []IntType { return oldObj.Max10TypedefField }))...)
|
||||
|
||||
return errs
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
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()
|
||||
|
||||
type Struct struct {
|
||||
TypeMeta int
|
||||
|
||||
// +k8s:maxItems=0
|
||||
Max0Field []OtherStruct `json:"max0Field"`
|
||||
|
||||
// +k8s:maxItems=10
|
||||
Max10Field []OtherStruct `json:"max10Field"`
|
||||
|
||||
// +k8s:maxItems=0
|
||||
Max0TypedefField []OtherTypedefStruct `json:"max0TypedefField"`
|
||||
|
||||
// +k8s:maxItems=10
|
||||
Max10TypedefField []OtherTypedefStruct `json:"max10TypedefField"`
|
||||
}
|
||||
|
||||
type OtherStruct struct{}
|
||||
|
||||
type OtherTypedefStruct OtherStruct
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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(&Struct{
|
||||
// All zero values
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
Max0Field: make([]OtherStruct, 0),
|
||||
Max10Field: make([]OtherStruct, 0),
|
||||
Max0TypedefField: make([]OtherTypedefStruct, 0),
|
||||
Max10TypedefField: make([]OtherTypedefStruct, 0),
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
Max10Field: make([]OtherStruct, 1),
|
||||
Max10TypedefField: make([]OtherTypedefStruct, 1),
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
Max10Field: make([]OtherStruct, 9),
|
||||
Max10TypedefField: make([]OtherTypedefStruct, 9),
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
Max10Field: make([]OtherStruct, 10),
|
||||
Max10TypedefField: make([]OtherTypedefStruct, 10),
|
||||
}).ExpectValid()
|
||||
|
||||
testVal := &Struct{
|
||||
Max0Field: make([]OtherStruct, 1),
|
||||
Max10Field: make([]OtherStruct, 11),
|
||||
Max0TypedefField: make([]OtherTypedefStruct, 1),
|
||||
Max10TypedefField: make([]OtherTypedefStruct, 11),
|
||||
}
|
||||
st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.TooMany(field.NewPath("max0Field"), 1, 0),
|
||||
field.TooMany(field.NewPath("max10Field"), 11, 10),
|
||||
field.TooMany(field.NewPath("max0TypedefField"), 1, 0),
|
||||
field.TooMany(field.NewPath("max10TypedefField"), 11, 10),
|
||||
})
|
||||
// Test validation ratcheting
|
||||
st.Value(&Struct{
|
||||
Max0Field: make([]OtherStruct, 1),
|
||||
Max10Field: make([]OtherStruct, 11),
|
||||
Max0TypedefField: make([]OtherTypedefStruct, 1),
|
||||
Max10TypedefField: make([]OtherTypedefStruct, 11),
|
||||
}).OldValue(&Struct{
|
||||
Max0Field: make([]OtherStruct, 1),
|
||||
Max10Field: make([]OtherStruct, 11),
|
||||
Max0TypedefField: make([]OtherTypedefStruct, 1),
|
||||
Max10TypedefField: make([]OtherTypedefStruct, 11),
|
||||
}).ExpectValid()
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//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 (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
|
||||
equality "k8s.io/apimachinery/pkg/api/equality"
|
||||
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 {
|
||||
// type Struct
|
||||
scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
|
||||
switch op.Request.SubresourcePath() {
|
||||
case "/":
|
||||
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, subresource: %v", obj, op.Request.SubresourcePath()))}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate_Struct validates an instance of Struct according
|
||||
// to declarative validation rules in the API schema.
|
||||
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.Max0Field
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []OtherStruct) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("max0Field"), obj.Max0Field, safe.Field(oldObj, func(oldObj *Struct) []OtherStruct { return oldObj.Max0Field }))...)
|
||||
|
||||
// field Struct.Max10Field
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []OtherStruct) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("max10Field"), obj.Max10Field, safe.Field(oldObj, func(oldObj *Struct) []OtherStruct { return oldObj.Max10Field }))...)
|
||||
|
||||
// field Struct.Max0TypedefField
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []OtherTypedefStruct) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("max0TypedefField"), obj.Max0TypedefField, safe.Field(oldObj, func(oldObj *Struct) []OtherTypedefStruct { return oldObj.Max0TypedefField }))...)
|
||||
|
||||
// field Struct.Max10TypedefField
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []OtherTypedefStruct) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("max10TypedefField"), obj.Max10TypedefField, safe.Field(oldObj, func(oldObj *Struct) []OtherTypedefStruct { return oldObj.Max10TypedefField }))...)
|
||||
|
||||
return errs
|
||||
}
|
||||
@@ -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 typedeftoslice
|
||||
|
||||
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
|
||||
|
||||
var localSchemeBuilder = testscheme.New()
|
||||
|
||||
// Note: no validation here
|
||||
type UnvalidatedType []int
|
||||
|
||||
// +k8s:maxItems=0
|
||||
type Max0Type []int
|
||||
|
||||
// +k8s:maxItems=10
|
||||
type Max10Type []int
|
||||
|
||||
// Note: no validation here
|
||||
type UnvalidatedPtrType []*int
|
||||
|
||||
type SliceType []int
|
||||
|
||||
// +k8s:maxItems=0
|
||||
type Max0TypedefType SliceType
|
||||
|
||||
// +k8s:maxItems=10
|
||||
type Max10TypedefType SliceType
|
||||
|
||||
type Struct struct {
|
||||
TypeMeta int
|
||||
|
||||
UnvalidatedField UnvalidatedType `json:"unvalidatedField"`
|
||||
|
||||
Max0Field Max0Type `json:"max0Field"`
|
||||
|
||||
Max10Field Max10Type `json:"max10Field"`
|
||||
|
||||
Max0TypedefField Max0TypedefType `json:"max0TypedefField"`
|
||||
|
||||
Max10TypedefField Max10TypedefType `json:"max10TypedefField"`
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 typedeftoslice
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
st := localSchemeBuilder.Test(t)
|
||||
|
||||
st.Value(&Struct{
|
||||
// All zero values
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
UnvalidatedField: make(UnvalidatedType, 0),
|
||||
Max0Field: make(Max0Type, 0),
|
||||
Max10Field: make(Max10Type, 0),
|
||||
Max0TypedefField: make(Max0TypedefType, 0),
|
||||
Max10TypedefField: make(Max10TypedefType, 0),
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
UnvalidatedField: make(UnvalidatedType, 1),
|
||||
Max10Field: make(Max10Type, 1),
|
||||
Max10TypedefField: make(Max10TypedefType, 1),
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
UnvalidatedField: make(UnvalidatedType, 9),
|
||||
Max10Field: make(Max10Type, 9),
|
||||
Max10TypedefField: make(Max10TypedefType, 9),
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
UnvalidatedField: make(UnvalidatedType, 10),
|
||||
Max10Field: make(Max10Type, 10),
|
||||
Max10TypedefField: make(Max10TypedefType, 10),
|
||||
}).ExpectValid()
|
||||
|
||||
testVal := &Struct{
|
||||
UnvalidatedField: make(UnvalidatedType, 11),
|
||||
Max0Field: make(Max0Type, 1),
|
||||
Max10Field: make(Max10Type, 11),
|
||||
Max0TypedefField: make(Max0TypedefType, 1),
|
||||
Max10TypedefField: make(Max10TypedefType, 11),
|
||||
}
|
||||
st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.TooMany(field.NewPath("max0Field"), 1, 0),
|
||||
field.TooMany(field.NewPath("max10Field"), 11, 10),
|
||||
field.TooMany(field.NewPath("max0TypedefField"), 1, 0),
|
||||
field.TooMany(field.NewPath("max10TypedefField"), 11, 10),
|
||||
})
|
||||
// Test validation ratcheting
|
||||
st.Value(&Struct{
|
||||
UnvalidatedField: make(UnvalidatedType, 1),
|
||||
Max0Field: make(Max0Type, 1),
|
||||
Max10Field: make(Max10Type, 11),
|
||||
Max0TypedefField: make(Max0TypedefType, 1),
|
||||
Max10TypedefField: make(Max10TypedefType, 11),
|
||||
}).OldValue(&Struct{
|
||||
UnvalidatedField: make(UnvalidatedType, 1),
|
||||
Max0Field: make(Max0Type, 1),
|
||||
Max10Field: make(Max10Type, 11),
|
||||
Max0TypedefField: make(Max0TypedefType, 1),
|
||||
Max10TypedefField: make(Max10TypedefType, 11),
|
||||
}).ExpectValid()
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
//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 typedeftoslice
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
|
||||
equality "k8s.io/apimachinery/pkg/api/equality"
|
||||
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 {
|
||||
// type Struct
|
||||
scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
|
||||
switch op.Request.SubresourcePath() {
|
||||
case "/":
|
||||
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, subresource: %v", obj, op.Request.SubresourcePath()))}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate_Max0Type validates an instance of Max0Type according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_Max0Type(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj Max0Type) (errs field.ErrorList) {
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_Max0TypedefType validates an instance of Max0TypedefType according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_Max0TypedefType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj Max0TypedefType) (errs field.ErrorList) {
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_Max10Type validates an instance of Max10Type according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_Max10Type(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj Max10Type) (errs field.ErrorList) {
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_Max10TypedefType validates an instance of Max10TypedefType according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_Max10TypedefType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj Max10TypedefType) (errs field.ErrorList) {
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_Struct validates an instance of Struct according
|
||||
// to declarative validation rules in the API schema.
|
||||
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.UnvalidatedField has no validation
|
||||
|
||||
// field Struct.Max0Field
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj Max0Type) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_Max0Type(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("max0Field"), obj.Max0Field, safe.Field(oldObj, func(oldObj *Struct) Max0Type { return oldObj.Max0Field }))...)
|
||||
|
||||
// field Struct.Max10Field
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj Max10Type) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_Max10Type(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("max10Field"), obj.Max10Field, safe.Field(oldObj, func(oldObj *Struct) Max10Type { return oldObj.Max10Field }))...)
|
||||
|
||||
// field Struct.Max0TypedefField
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj Max0TypedefType) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_Max0TypedefType(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("max0TypedefField"), obj.Max0TypedefField, safe.Field(oldObj, func(oldObj *Struct) Max0TypedefType { return oldObj.Max0TypedefField }))...)
|
||||
|
||||
// field Struct.Max10TypedefField
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj Max10TypedefType) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_Max10TypedefType(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("max10TypedefField"), obj.Max10TypedefField, safe.Field(oldObj, func(oldObj *Struct) Max10TypedefType { return oldObj.Max10TypedefField }))...)
|
||||
|
||||
return errs
|
||||
}
|
||||
@@ -27,13 +27,72 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
minimumTagName = "k8s:minimum"
|
||||
maxItemsTagName = "k8s:maxItems"
|
||||
minimumTagName = "k8s:minimum"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTagValidator(maxItemsTagValidator{})
|
||||
RegisterTagValidator(minimumTagValidator{})
|
||||
}
|
||||
|
||||
type maxItemsTagValidator struct{}
|
||||
|
||||
func (maxItemsTagValidator) Init(_ Config) {}
|
||||
|
||||
func (maxItemsTagValidator) TagName() string {
|
||||
return maxItemsTagName
|
||||
}
|
||||
|
||||
var maxItemsTagValidScopes = sets.New(
|
||||
ScopeType,
|
||||
ScopeField,
|
||||
ScopeListVal,
|
||||
ScopeMapVal,
|
||||
)
|
||||
|
||||
func (maxItemsTagValidator) ValidScopes() sets.Set[Scope] {
|
||||
return maxItemsTagValidScopes
|
||||
}
|
||||
|
||||
var (
|
||||
maxItemsValidator = types.Name{Package: libValidationPkg, Name: "MaxItems"}
|
||||
)
|
||||
|
||||
func (maxItemsTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) {
|
||||
var result Validations
|
||||
|
||||
// NOTE: pointers to lists are not supported, so we should never see a pointer here.
|
||||
if t := util.NativeType(context.Type); t.Kind != types.Slice && t.Kind != types.Array {
|
||||
return Validations{}, fmt.Errorf("can only be used on list types (%s)", rootTypeString(context.Type, t))
|
||||
}
|
||||
|
||||
intVal, err := strconv.Atoi(tag.Value)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to parse tag payload as int: %w", err)
|
||||
}
|
||||
if intVal < 0 {
|
||||
return result, fmt.Errorf("must be greater than or equal to zero")
|
||||
}
|
||||
// Note: maxItems short-circuits other validations.
|
||||
result.AddFunction(Function(maxItemsTagName, ShortCircuit, maxItemsValidator, intVal))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (mitv maxItemsTagValidator) Docs() TagDoc {
|
||||
return TagDoc{
|
||||
Tag: mitv.TagName(),
|
||||
Scopes: mitv.ValidScopes().UnsortedList(),
|
||||
Description: "Indicates that a list field has a limit on its size.",
|
||||
Payloads: []TagPayloadDoc{{
|
||||
Description: "<non-negative integer>",
|
||||
Docs: "This field must be no more than X items long.",
|
||||
}},
|
||||
PayloadsType: codetags.ValueTypeInt,
|
||||
PayloadsRequired: true,
|
||||
}
|
||||
}
|
||||
|
||||
type minimumTagValidator struct{}
|
||||
|
||||
func (minimumTagValidator) Init(_ Config) {}
|
||||
|
||||
Reference in New Issue
Block a user