Add alpha and beta tests

This commit is contained in:
Your Name
2026-02-05 06:03:53 +00:00
parent d477d1ff2b
commit 90578014e6
36 changed files with 3875 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package atomicslice
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type AtomicSliceStruct struct {
TypeMeta int
// Case: Standard eachVal
// +k8s:listType=atomic
// +k8s:eachVal=+k8s:minimum=10
Standard []int `json:"standard"`
// Case: Alpha eachVal
// +k8s:listType=atomic
// +k8s:alpha=+k8s:eachVal=+k8s:minimum=10
Alpha []int `json:"Alpha"`
// Case: Beta eachVal
// +k8s:listType=atomic
// +k8s:beta=+k8s:eachVal=+k8s:minimum=10
Beta []int `json:"Beta"`
// Case: Standard eachVal, Alpha validation
// +k8s:listType=atomic
// +k8s:eachVal=+k8s:alpha=+k8s:minimum=10
AlphaValidation []int `json:"AlphaValidation"`
// Case: Standard eachVal, Beta validation
// +k8s:listType=atomic
// +k8s:eachVal=+k8s:beta=+k8s:minimum=10
BetaValidation []int `json:"BetaValidation"`
}

View File

@@ -0,0 +1,50 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package atomicslice
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestAtomicSlice(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&AtomicSliceStruct{
Standard: []int{5},
Alpha: []int{5},
Beta: []int{5},
AlphaValidation: []int{5},
BetaValidation: []int{5},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
// Case: Standard -> Normal Error
field.Invalid(field.NewPath("standard").Index(0), 5, "").WithOrigin("minimum"),
// Case: Alpha eachVal -> Alpha Error
field.Invalid(field.NewPath("Alpha").Index(0), 5, "").WithOrigin("minimum").MarkAlpha(),
// Case: Beta eachVal -> Beta Error
field.Invalid(field.NewPath("Beta").Index(0), 5, "").WithOrigin("minimum").MarkBeta(),
// Case: Standard eachVal, Alpha validation -> Alpha Error
field.Invalid(field.NewPath("AlphaValidation").Index(0), 5, "").WithOrigin("minimum").MarkAlpha(),
// Case: Standard eachVal, Beta validation -> Beta Error
field.Invalid(field.NewPath("BetaValidation").Index(0), 5, "").WithOrigin("minimum").MarkBeta(),
})
}

View File

@@ -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 atomicslice
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 AtomicSliceStruct
scheme.AddValidationFunc((*AtomicSliceStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_AtomicSliceStruct(ctx, op, nil /* fldPath */, obj.(*AtomicSliceStruct), safe.Cast[*AtomicSliceStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_AtomicSliceStruct validates an instance of AtomicSliceStruct according
// to declarative validation rules in the API schema.
func Validate_AtomicSliceStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *AtomicSliceStruct) (errs field.ErrorList) {
// field AtomicSliceStruct.TypeMeta has no validation
// field AtomicSliceStruct.Standard
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10)
})...)
return
}(fldPath.Child("standard"), obj.Standard, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.Standard }), oldObj != nil)...)
// field AtomicSliceStruct.Alpha
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10)
}).MarkAlpha()...)
return
}(fldPath.Child("Alpha"), obj.Alpha, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.Alpha }), oldObj != nil)...)
// field AtomicSliceStruct.Beta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10)
}).MarkBeta()...)
return
}(fldPath.Child("Beta"), obj.Beta, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.Beta }), oldObj != nil)...)
// field AtomicSliceStruct.AlphaValidation
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()
})...)
return
}(fldPath.Child("AlphaValidation"), obj.AlphaValidation, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.AlphaValidation }), oldObj != nil)...)
// field AtomicSliceStruct.BetaValidation
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()
})...)
return
}(fldPath.Child("BetaValidation"), obj.BetaValidation, safe.Field(oldObj, func(oldObj *AtomicSliceStruct) []int { return oldObj.BetaValidation }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,46 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package enums
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type Struct struct {
TypeMeta int
EnumField Enum `json:"enumField"`
EnumFieldBeta BetaEnum `json:"enumFieldBeta"`
}
// +k8s:alpha=+k8s:enum
type Enum string
const (
EnumA Enum = "A"
)
// +k8s:beta=+k8s:enum
type BetaEnum string
const (
BetaEnumA BetaEnum = "A"
)

View File

@@ -0,0 +1,57 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package enums
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestAlpha(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
EnumField: "A",
EnumFieldBeta: "A",
}).ExpectValid()
// Test failures marked as alpha
st.Value(&Struct{
EnumField: "B",
EnumFieldBeta: "A",
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.NotSupported(field.NewPath("enumField"), Enum("B"), []string{"A"}).MarkAlpha(),
})
}
func TestBeta(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
EnumField: "A",
EnumFieldBeta: "A",
}).ExpectValid()
// Test failures marked as beta
st.Value(&Struct{
EnumField: "A",
EnumFieldBeta: "B",
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.NotSupported(field.NewPath("enumFieldBeta"), BetaEnum("B"), []string{"A"}).MarkBeta(),
})
}

View File

@@ -0,0 +1,102 @@
//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 enums
import (
context "context"
fmt "fmt"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
sets "k8s.io/apimachinery/pkg/util/sets"
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
}
var symbolsForBetaEnum = sets.New(BetaEnumA)
// Validate_BetaEnum validates an instance of BetaEnum according
// to declarative validation rules in the API schema.
func Validate_BetaEnum(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *BetaEnum) (errs field.ErrorList) {
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForBetaEnum, nil).MarkBeta()...)
return errs
}
var symbolsForEnum = sets.New(EnumA)
// Validate_Enum validates an instance of Enum according
// to declarative validation rules in the API schema.
func Validate_Enum(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Enum) (errs field.ErrorList) {
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum, nil).MarkAlpha()...)
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.EnumField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *Enum, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_Enum(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("enumField"), &obj.EnumField, safe.Field(oldObj, func(oldObj *Struct) *Enum { return &oldObj.EnumField }), oldObj != nil)...)
// field Struct.EnumFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *BetaEnum, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_BetaEnum(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("enumFieldBeta"), &obj.EnumFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *BetaEnum { return &oldObj.EnumFieldBeta }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,117 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package listkeys
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type MapItem struct {
Key string `json:"key"`
Value int `json:"value"`
}
type MultiKeyItem struct {
Key1 string `json:"key1"`
Key2 int `json:"key2"`
Value int `json:"value"`
}
type ListKeyStruct struct {
TypeMeta int
// Case: Alpha listType, Standard Key
// +k8s:alpha=+k8s:listType=map
// +k8s:listMapKey=key
AlphaListTypeStandardKey []MapItem `json:"alphaListTypeStandardKey"`
// Case: Standard listType, Alpha Key
// +k8s:listType=map
// +k8s:alpha=+k8s:listMapKey=key
StandardListTypeAlphaKey []MapItem `json:"standardListTypeAlphaKey"`
// Case: Alpha listType, Alpha Key
// +k8s:alpha=+k8s:listType=map
// +k8s:alpha=+k8s:listMapKey=key
AlphaListTypeAlphaKey []MapItem `json:"alphaListTypeAlphaKey"`
// Case: Standard listType, Alpha Key1, Standard Key2
// +k8s:listType=map
// +k8s:alpha=+k8s:listMapKey=key1
// +k8s:listMapKey=key2
StandardListTypeMixedKeys1 []MultiKeyItem `json:"standardListTypeMixedKeys1"`
// Case: Standard listType, Standard Key1, Alpha Key2
// +k8s:listType=map
// +k8s:listMapKey=key1
// +k8s:alpha=+k8s:listMapKey=key2
StandardListTypeMixedKeys2 []MultiKeyItem `json:"standardListTypeMixedKeys2"`
// Case: Alpha listType, Alpha Key1, Standard Key2
// +k8s:alpha=+k8s:listType=map
// +k8s:alpha=+k8s:listMapKey=key1
// +k8s:listMapKey=key2
AlphaListTypeMixedKeys1 []MultiKeyItem `json:"alphaListTypeMixedKeys1"`
// Case: Alpha listType, Standard Key1, Alpha Key2
// +k8s:alpha=+k8s:listType=map
// +k8s:listMapKey=key1
// +k8s:alpha=+k8s:listMapKey=key2
AlphaListTypeMixedKeys2 []MultiKeyItem `json:"alphaListTypeMixedKeys2"`
// Case: Standard listType, Beta Key1, Standard Key2
// +k8s:listType=map
// +k8s:beta=+k8s:listMapKey=key1
// +k8s:listMapKey=key2
StandardListTypeMixedKeysBeta1 []MultiKeyItem `json:"standardListTypeMixedKeysBeta1"`
// Case: Standard listType, Standard Key1, Beta Key2
// +k8s:listType=map
// +k8s:listMapKey=key1
// +k8s:beta=+k8s:listMapKey=key2
StandardListTypeMixedKeysBeta2 []MultiKeyItem `json:"standardListTypeMixedKeysBeta2"`
// Case: Beta listType, Beta Key1, Standard Key2
// +k8s:beta=+k8s:listType=map
// +k8s:beta=+k8s:listMapKey=key1
// +k8s:listMapKey=key2
BetaListTypeMixedKeys1 []MultiKeyItem `json:"betaListTypeMixedKeys1"`
// Case: Beta listType, Standard Key1, Beta Key2
// +k8s:beta=+k8s:listType=map
// +k8s:listMapKey=key1
// +k8s:beta=+k8s:listMapKey=key2
BetaListTypeMixedKeys2 []MultiKeyItem `json:"betaListTypeMixedKeys2"`
// Case: Beta listType, Standard Key
// +k8s:beta=+k8s:listType=map
// +k8s:listMapKey=key
BetaListTypeStandardKey []MapItem `json:"betaListTypeStandardKey"`
// Case: Standard listType, Beta Key
// +k8s:listType=map
// +k8s:beta=+k8s:listMapKey=key
StandardListTypeBetaKey []MapItem `json:"standardListTypeBetaKey"`
// Case: Beta listType, Beta Key
// +k8s:beta=+k8s:listType=map
// +k8s:beta=+k8s:listMapKey=key
BetaListTypeBetaKey []MapItem `json:"betaListTypeBetaKey"`
}

View File

@@ -0,0 +1,147 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package listkeys
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestListKeys(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&ListKeyStruct{
// Case 1: Alpha listType, Standard Key
AlphaListTypeStandardKey: []MapItem{
{Key: "a", Value: 1},
{Key: "a", Value: 2},
},
// Case 2: Standard listType, Alpha Key
StandardListTypeAlphaKey: []MapItem{
{Key: "b", Value: 1},
{Key: "b", Value: 2},
},
// Case 3: Alpha listType, Alpha Key
AlphaListTypeAlphaKey: []MapItem{
{Key: "c", Value: 1},
{Key: "c", Value: 2},
},
// Case 4: Standard listType, Alpha Key1, Standard Key2
StandardListTypeMixedKeys1: []MultiKeyItem{
{Key1: "d", Key2: 1, Value: 1},
{Key1: "d", Key2: 1, Value: 2},
},
// Case 5: Standard listType, Standard Key1, Alpha Key2
StandardListTypeMixedKeys2: []MultiKeyItem{
{Key1: "e", Key2: 1, Value: 1},
{Key1: "e", Key2: 1, Value: 2},
},
// Case 6: Alpha listType, Alpha Key1, Standard Key2
AlphaListTypeMixedKeys1: []MultiKeyItem{
{Key1: "f", Key2: 1, Value: 1},
{Key1: "f", Key2: 1, Value: 2},
},
// Case 7: Alpha listType, Standard Key1, Alpha Key2
AlphaListTypeMixedKeys2: []MultiKeyItem{
{Key1: "g", Key2: 1, Value: 1},
{Key1: "g", Key2: 1, Value: 2},
},
// Case 11: Standard listType, Beta Key1, Standard Key2
StandardListTypeMixedKeysBeta1: []MultiKeyItem{
{Key1: "k", Key2: 1, Value: 1},
{Key1: "k", Key2: 1, Value: 2},
},
// Case 12: Standard listType, Standard Key1, Beta Key2
StandardListTypeMixedKeysBeta2: []MultiKeyItem{
{Key1: "l", Key2: 1, Value: 1},
{Key1: "l", Key2: 1, Value: 2},
},
// Case 13: Beta listType, Beta Key1, Standard Key2
BetaListTypeMixedKeys1: []MultiKeyItem{
{Key1: "m", Key2: 1, Value: 1},
{Key1: "m", Key2: 1, Value: 2},
},
// Case 14: Beta listType, Standard Key1, Beta Key2
BetaListTypeMixedKeys2: []MultiKeyItem{
{Key1: "n", Key2: 1, Value: 1},
{Key1: "n", Key2: 1, Value: 2},
},
// Case 8: Beta listType, Standard Key
BetaListTypeStandardKey: []MapItem{
{Key: "h", Value: 1},
{Key: "h", Value: 2},
},
// Case 9: Standard listType, Beta Key
StandardListTypeBetaKey: []MapItem{
{Key: "i", Value: 1},
{Key: "i", Value: 2},
},
// Case 10: Beta listType, Beta Key
BetaListTypeBetaKey: []MapItem{
{Key: "j", Value: 1},
{Key: "j", Value: 2},
},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
// Case 1: Alpha listType, Standard Key -> Alpha Error
field.Duplicate(field.NewPath("alphaListTypeStandardKey").Index(1), MapItem{Key: "a", Value: 2}).MarkAlpha(),
// Case 2: Standard listType, Alpha Key -> Normal Error
// The alpha key is treated as a functional key for the list validation.
field.Duplicate(field.NewPath("standardListTypeAlphaKey").Index(1), MapItem{Key: "b", Value: 2}),
// Case 3: Alpha listType, Alpha Key -> Alpha Error
field.Duplicate(field.NewPath("alphaListTypeAlphaKey").Index(1), MapItem{Key: "c", Value: 2}).MarkAlpha(),
// Case 4: Standard listType, Alpha Key1, Standard Key2 -> Normal Error
// Both keys participate in the uniqueness check.
field.Duplicate(field.NewPath("standardListTypeMixedKeys1").Index(1), MultiKeyItem{Key1: "d", Key2: 1, Value: 2}),
// Case 5: Standard listType, Standard Key1, Alpha Key2 -> Normal Error
field.Duplicate(field.NewPath("standardListTypeMixedKeys2").Index(1), MultiKeyItem{Key1: "e", Key2: 1, Value: 2}),
// Case 6: Alpha listType, Alpha Key1, Standard Key2 -> Alpha Error
field.Duplicate(field.NewPath("alphaListTypeMixedKeys1").Index(1), MultiKeyItem{Key1: "f", Key2: 1, Value: 2}).MarkAlpha(),
// Case 7: Alpha listType, Standard Key1, Alpha Key2 -> Alpha Error
field.Duplicate(field.NewPath("alphaListTypeMixedKeys2").Index(1), MultiKeyItem{Key1: "g", Key2: 1, Value: 2}).MarkAlpha(),
// Case 11: Standard listType, Beta Key1, Standard Key2 -> Normal Error
field.Duplicate(field.NewPath("standardListTypeMixedKeysBeta1").Index(1), MultiKeyItem{Key1: "k", Key2: 1, Value: 2}),
// Case 12: Standard listType, Standard Key1, Beta Key2 -> Normal Error
field.Duplicate(field.NewPath("standardListTypeMixedKeysBeta2").Index(1), MultiKeyItem{Key1: "l", Key2: 1, Value: 2}),
// Case 13: Beta listType, Beta Key1, Standard Key2 -> Beta Error
field.Duplicate(field.NewPath("betaListTypeMixedKeys1").Index(1), MultiKeyItem{Key1: "m", Key2: 1, Value: 2}).MarkBeta(),
// Case 14: Beta listType, Standard Key1, Beta Key2 -> Beta Error
field.Duplicate(field.NewPath("betaListTypeMixedKeys2").Index(1), MultiKeyItem{Key1: "n", Key2: 1, Value: 2}).MarkBeta(),
// Case 8: Beta listType, Standard Key -> Beta Error
field.Duplicate(field.NewPath("betaListTypeStandardKey").Index(1), MapItem{Key: "h", Value: 2}).MarkBeta(),
// Case 9: Standard listType, Beta Key -> Normal Error
// The beta key is treated as a functional key for the list validation.
field.Duplicate(field.NewPath("standardListTypeBetaKey").Index(1), MapItem{Key: "i", Value: 2}),
// Case 10: Beta listType, Beta Key -> Beta Error
field.Duplicate(field.NewPath("betaListTypeBetaKey").Index(1), MapItem{Key: "j", Value: 2}).MarkBeta(),
})
}

View File

@@ -0,0 +1,240 @@
//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 listkeys
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 ListKeyStruct
scheme.AddValidationFunc((*ListKeyStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ListKeyStruct(ctx, op, nil /* fldPath */, obj.(*ListKeyStruct), safe.Cast[*ListKeyStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_ListKeyStruct validates an instance of ListKeyStruct according
// to declarative validation rules in the API schema.
func Validate_ListKeyStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ListKeyStruct) (errs field.ErrorList) {
// field ListKeyStruct.TypeMeta has no validation
// field ListKeyStruct.AlphaListTypeStandardKey
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key }).MarkAlpha()...)
return
}(fldPath.Child("alphaListTypeStandardKey"), obj.AlphaListTypeStandardKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.AlphaListTypeStandardKey }), oldObj != nil)...)
// field ListKeyStruct.StandardListTypeAlphaKey
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...)
return
}(fldPath.Child("standardListTypeAlphaKey"), obj.StandardListTypeAlphaKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.StandardListTypeAlphaKey }), oldObj != nil)...)
// field ListKeyStruct.AlphaListTypeAlphaKey
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key }).MarkAlpha()...)
return
}(fldPath.Child("alphaListTypeAlphaKey"), obj.AlphaListTypeAlphaKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.AlphaListTypeAlphaKey }), oldObj != nil)...)
// field ListKeyStruct.StandardListTypeMixedKeys1
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 })...)
return
}(fldPath.Child("standardListTypeMixedKeys1"), obj.StandardListTypeMixedKeys1, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.StandardListTypeMixedKeys1 }), oldObj != nil)...)
// field ListKeyStruct.StandardListTypeMixedKeys2
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 })...)
return
}(fldPath.Child("standardListTypeMixedKeys2"), obj.StandardListTypeMixedKeys2, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.StandardListTypeMixedKeys2 }), oldObj != nil)...)
// field ListKeyStruct.AlphaListTypeMixedKeys1
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 }).MarkAlpha()...)
return
}(fldPath.Child("alphaListTypeMixedKeys1"), obj.AlphaListTypeMixedKeys1, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.AlphaListTypeMixedKeys1 }), oldObj != nil)...)
// field ListKeyStruct.AlphaListTypeMixedKeys2
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 }).MarkAlpha()...)
return
}(fldPath.Child("alphaListTypeMixedKeys2"), obj.AlphaListTypeMixedKeys2, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.AlphaListTypeMixedKeys2 }), oldObj != nil)...)
// field ListKeyStruct.StandardListTypeMixedKeysBeta1
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 })...)
return
}(fldPath.Child("standardListTypeMixedKeysBeta1"), obj.StandardListTypeMixedKeysBeta1, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.StandardListTypeMixedKeysBeta1 }), oldObj != nil)...)
// field ListKeyStruct.StandardListTypeMixedKeysBeta2
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 })...)
return
}(fldPath.Child("standardListTypeMixedKeysBeta2"), obj.StandardListTypeMixedKeysBeta2, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.StandardListTypeMixedKeysBeta2 }), oldObj != nil)...)
// field ListKeyStruct.BetaListTypeMixedKeys1
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 }).MarkBeta()...)
return
}(fldPath.Child("betaListTypeMixedKeys1"), obj.BetaListTypeMixedKeys1, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.BetaListTypeMixedKeys1 }), oldObj != nil)...)
// field ListKeyStruct.BetaListTypeMixedKeys2
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MultiKeyItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MultiKeyItem, b MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 }).MarkBeta()...)
return
}(fldPath.Child("betaListTypeMixedKeys2"), obj.BetaListTypeMixedKeys2, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MultiKeyItem { return oldObj.BetaListTypeMixedKeys2 }), oldObj != nil)...)
// field ListKeyStruct.BetaListTypeStandardKey
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key }).MarkBeta()...)
return
}(fldPath.Child("betaListTypeStandardKey"), obj.BetaListTypeStandardKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.BetaListTypeStandardKey }), oldObj != nil)...)
// field ListKeyStruct.StandardListTypeBetaKey
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...)
return
}(fldPath.Child("standardListTypeBetaKey"), obj.StandardListTypeBetaKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.StandardListTypeBetaKey }), oldObj != nil)...)
// field ListKeyStruct.BetaListTypeBetaKey
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key }).MarkBeta()...)
return
}(fldPath.Child("betaListTypeBetaKey"), obj.BetaListTypeBetaKey, safe.Field(oldObj, func(oldObj *ListKeyStruct) []MapItem { return oldObj.BetaListTypeBetaKey }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,79 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package listmapitem
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type MapItem struct {
Key string `json:"key"`
Value int `json:"value"`
}
type InnerItem struct {
Value int `json:"value"`
}
type ListMapItemStruct struct {
TypeMeta int
// Case: Standard Item
// +k8s:listType=map
// +k8s:listMapKey=key
// +k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:minimum=10
StandardItem []MapItem `json:"standardItem"`
// Case: Alpha Item tag
// +k8s:listType=map
// +k8s:listMapKey=key
// +k8s:alpha=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:minimum=10
AlphaItemTag []MapItem `json:"alphaItemTag"`
// Case: Alpha validation
// +k8s:listType=map
// +k8s:listMapKey=key
// +k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:alpha=+k8s:minimum=10
AlphaValidation []MapItem `json:"alphaValidation"`
// Case: Double Alpha
// +k8s:listType=map
// +k8s:listMapKey=key
// +k8s:alpha=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:alpha=+k8s:minimum=10
DoubleAlpha []MapItem `json:"doubleAlpha"`
// Case: Beta Item tag
// +k8s:listType=map
// +k8s:listMapKey=key
// +k8s:beta=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:minimum=10
BetaItemTag []MapItem `json:"betaItemTag"`
// Case: Beta validation
// +k8s:listType=map
// +k8s:listMapKey=key
// +k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:beta=+k8s:minimum=10
BetaValidation []MapItem `json:"betaValidation"`
// Case: Double Beta
// +k8s:listType=map
// +k8s:listMapKey=key
// +k8s:beta=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:beta=+k8s:minimum=10
DoubleBeta []MapItem `json:"doubleBeta"`
}

View File

@@ -0,0 +1,59 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package listmapitem
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestListMapItem(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&ListMapItemStruct{
StandardItem: []MapItem{{Key: "foo", Value: 5}},
AlphaItemTag: []MapItem{{Key: "foo", Value: 5}},
AlphaValidation: []MapItem{{Key: "foo", Value: 5}},
DoubleAlpha: []MapItem{{Key: "foo", Value: 5}},
BetaItemTag: []MapItem{{Key: "foo", Value: 5}},
BetaValidation: []MapItem{{Key: "foo", Value: 5}},
DoubleBeta: []MapItem{{Key: "foo", Value: 5}},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
// Case 1: Standard Item -> Normal Error
field.Invalid(field.NewPath("standardItem").Index(0).Child("value"), 5, "").WithOrigin("minimum"),
// Case 2: Alpha Item tag -> Alpha Error
field.Invalid(field.NewPath("alphaItemTag").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkAlpha(),
// Case 3: Alpha validation -> Alpha Error
field.Invalid(field.NewPath("alphaValidation").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkAlpha(),
// Case 4: Double Alpha -> Alpha Error
field.Invalid(field.NewPath("doubleAlpha").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkAlpha(),
// Case 5: Beta Item tag -> Beta Error
field.Invalid(field.NewPath("betaItemTag").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkBeta(),
// Case 6: Beta validation -> Beta Error
field.Invalid(field.NewPath("betaValidation").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkBeta(),
// Case 7: Double Beta -> Beta Error
field.Invalid(field.NewPath("doubleBeta").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkBeta(),
})
}

View File

@@ -0,0 +1,198 @@
//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 listmapitem
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 ListMapItemStruct
scheme.AddValidationFunc((*ListMapItemStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ListMapItemStruct(ctx, op, nil /* fldPath */, obj.(*ListMapItemStruct), safe.Cast[*ListMapItemStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_ListMapItemStruct validates an instance of ListMapItemStruct according
// to declarative validation rules in the API schema.
func Validate_ListMapItemStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ListMapItemStruct) (errs field.ErrorList) {
// field ListMapItemStruct.TypeMeta has no validation
// field ListMapItemStruct.StandardItem
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...)
func() { // cohort {"key": "foo"}
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList {
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10)
})
})...)
}()
return
}(fldPath.Child("standardItem"), obj.StandardItem, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.StandardItem }), oldObj != nil)...)
// field ListMapItemStruct.AlphaItemTag
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...)
func() { // cohort {"key": "foo"}
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList {
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10)
})
}).MarkAlpha()...)
}()
return
}(fldPath.Child("alphaItemTag"), obj.AlphaItemTag, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.AlphaItemTag }), oldObj != nil)...)
// field ListMapItemStruct.AlphaValidation
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...)
func() { // cohort {"key": "foo"}
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList {
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()
})
})...)
}()
return
}(fldPath.Child("alphaValidation"), obj.AlphaValidation, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.AlphaValidation }), oldObj != nil)...)
// field ListMapItemStruct.DoubleAlpha
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...)
func() { // cohort {"key": "foo"}
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList {
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()
})
}).MarkAlpha()...)
}()
return
}(fldPath.Child("doubleAlpha"), obj.DoubleAlpha, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.DoubleAlpha }), oldObj != nil)...)
// field ListMapItemStruct.BetaItemTag
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...)
func() { // cohort {"key": "foo"}
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList {
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10)
})
}).MarkBeta()...)
}()
return
}(fldPath.Child("betaItemTag"), obj.BetaItemTag, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.BetaItemTag }), oldObj != nil)...)
// field ListMapItemStruct.BetaValidation
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...)
func() { // cohort {"key": "foo"}
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList {
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()
})
})...)
}()
return
}(fldPath.Child("betaValidation"), obj.BetaValidation, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.BetaValidation }), oldObj != nil)...)
// field ListMapItemStruct.DoubleBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []MapItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with map semantics require unique keys
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a MapItem, b MapItem) bool { return a.Key == b.Key })...)
func() { // cohort {"key": "foo"}
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList {
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *MapItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()
})
}).MarkBeta()...)
}()
return
}(fldPath.Child("doubleBeta"), obj.DoubleBeta, safe.Field(oldObj, func(oldObj *ListMapItemStruct) []MapItem { return oldObj.DoubleBeta }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,71 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package listset
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type ListSetStruct struct {
TypeMeta int
// Case: Alpha listType=set
// +k8s:alpha=+k8s:listType=set
Set []ComplexSetItem `json:"set"`
// Case: Beta listType=set
// +k8s:beta=+k8s:listType=set
BetaSet []ComplexSetItem `json:"betaSet"`
// Case: Chained subfield validation
// +k8s:listType=set
// +k8s:eachVal=+k8s:subfield(value)=+k8s:alpha=+k8s:minimum=10
ChainedSubfieldSet []SimpleSetItem `json:"chainedSubfieldSet"`
// Case: Alpha listType=set, Beta item validation
// +k8s:alpha=+k8s:listType=set
SetBetaItem []ComplexSetItemBeta `json:"setBetaItem"`
// Case: Beta listType=set, Beta item validation
// +k8s:beta=+k8s:listType=set
BetaSetBetaItem []ComplexSetItemBeta `json:"betaSetBetaItem"`
// Case: Chained subfield validation (Beta)
// +k8s:listType=set
// +k8s:eachVal=+k8s:subfield(value)=+k8s:beta=+k8s:minimum=10
ChainedSubfieldSetBeta []SimpleSetItem `json:"chainedSubfieldSetBeta"`
}
type ComplexSetItem struct {
// +k8s:alpha=+k8s:minimum=10
Value int `json:"value"`
StringVal string `json:"stringVal"`
}
type ComplexSetItemBeta struct {
// +k8s:beta=+k8s:minimum=10
Value int `json:"value"`
StringVal string `json:"stringVal"`
}
type SimpleSetItem struct {
Value int `json:"value"`
StringVal string `json:"stringVal"`
}

View File

@@ -0,0 +1,70 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package listset
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestListSet(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&ListSetStruct{
Set: []ComplexSetItem{
{StringVal: "abc", Value: 10},
{StringVal: "abc", Value: 10}, // Duplicate value (alpha)
},
BetaSet: []ComplexSetItem{
{StringVal: "abc", Value: 10},
{StringVal: "abc", Value: 10}, // Duplicate value (beta)
},
ChainedSubfieldSet: []SimpleSetItem{
{StringVal: "def", Value: 9}, // Value 9 < 10 (alpha)
},
SetBetaItem: []ComplexSetItemBeta{
{StringVal: "ghi", Value: 10},
{StringVal: "ghi", Value: 10}, // Duplicate value (alpha)
},
BetaSetBetaItem: []ComplexSetItemBeta{
{StringVal: "jkl", Value: 10},
{StringVal: "jkl", Value: 10}, // Duplicate value (beta)
},
ChainedSubfieldSetBeta: []SimpleSetItem{
{StringVal: "mno", Value: 9}, // Value 9 < 10 (beta)
},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
// Set: Duplicate value is SHADOWED
field.Duplicate(field.NewPath("set").Index(1), ComplexSetItem{StringVal: "abc", Value: 10}).MarkAlpha(),
// BetaSet: Duplicate value is SHADOWED
field.Duplicate(field.NewPath("betaSet").Index(1), ComplexSetItem{StringVal: "abc", Value: 10}).MarkBeta(),
// ChainedSubfieldSet: Value 9 < 10 is SHADOWED
field.Invalid(field.NewPath("chainedSubfieldSet").Index(0).Child("value"), 9, "").WithOrigin("minimum").MarkAlpha(),
// SetBetaItem: Duplicate value is SHADOWED (Alpha list)
field.Duplicate(field.NewPath("setBetaItem").Index(1), ComplexSetItemBeta{StringVal: "ghi", Value: 10}).MarkAlpha(),
// BetaSetBetaItem: Duplicate value is SHADOWED (Beta list)
field.Duplicate(field.NewPath("betaSetBetaItem").Index(1), ComplexSetItemBeta{StringVal: "jkl", Value: 10}).MarkBeta(),
// ChainedSubfieldSetBeta: Value 9 < 10 is SHADOWED (Beta item)
field.Invalid(field.NewPath("chainedSubfieldSetBeta").Index(0).Child("value"), 9, "").WithOrigin("minimum").MarkBeta(),
})
}

View File

@@ -0,0 +1,192 @@
//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 listset
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 ListSetStruct
scheme.AddValidationFunc((*ListSetStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ListSetStruct(ctx, op, nil /* fldPath */, obj.(*ListSetStruct), safe.Cast[*ListSetStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_ComplexSetItem validates an instance of ComplexSetItem according
// to declarative validation rules in the API schema.
func Validate_ComplexSetItem(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ComplexSetItem) (errs field.ErrorList) {
// field ComplexSetItem.Value
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...)
return
}(fldPath.Child("value"), &obj.Value, safe.Field(oldObj, func(oldObj *ComplexSetItem) *int { return &oldObj.Value }), oldObj != nil)...)
// field ComplexSetItem.StringVal has no validation
return errs
}
// Validate_ComplexSetItemBeta validates an instance of ComplexSetItemBeta according
// to declarative validation rules in the API schema.
func Validate_ComplexSetItemBeta(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ComplexSetItemBeta) (errs field.ErrorList) {
// field ComplexSetItemBeta.Value
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()...)
return
}(fldPath.Child("value"), &obj.Value, safe.Field(oldObj, func(oldObj *ComplexSetItemBeta) *int { return &oldObj.Value }), oldObj != nil)...)
// field ComplexSetItemBeta.StringVal has no validation
return errs
}
// Validate_ListSetStruct validates an instance of ListSetStruct according
// to declarative validation rules in the API schema.
func Validate_ListSetStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ListSetStruct) (errs field.ErrorList) {
// field ListSetStruct.TypeMeta has no validation
// field ListSetStruct.Set
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []ComplexSetItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with set semantics require unique values
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...)
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItem)...)
return
}(fldPath.Child("set"), obj.Set, safe.Field(oldObj, func(oldObj *ListSetStruct) []ComplexSetItem { return oldObj.Set }), oldObj != nil)...)
// field ListSetStruct.BetaSet
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []ComplexSetItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with set semantics require unique values
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkBeta()...)
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItem)...)
return
}(fldPath.Child("betaSet"), obj.BetaSet, safe.Field(oldObj, func(oldObj *ListSetStruct) []ComplexSetItem { return oldObj.BetaSet }), oldObj != nil)...)
// field ListSetStruct.ChainedSubfieldSet
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []SimpleSetItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SimpleSetItem) field.ErrorList {
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *SimpleSetItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()
})
})...)
// lists with set semantics require unique values
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
return
}(fldPath.Child("chainedSubfieldSet"), obj.ChainedSubfieldSet, safe.Field(oldObj, func(oldObj *ListSetStruct) []SimpleSetItem { return oldObj.ChainedSubfieldSet }), oldObj != nil)...)
// field ListSetStruct.SetBetaItem
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []ComplexSetItemBeta, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with set semantics require unique values
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...)
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItemBeta)...)
return
}(fldPath.Child("setBetaItem"), obj.SetBetaItem, safe.Field(oldObj, func(oldObj *ListSetStruct) []ComplexSetItemBeta { return oldObj.SetBetaItem }), oldObj != nil)...)
// field ListSetStruct.BetaSetBetaItem
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []ComplexSetItemBeta, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with set semantics require unique values
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkBeta()...)
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItemBeta)...)
return
}(fldPath.Child("betaSetBetaItem"), obj.BetaSetBetaItem, safe.Field(oldObj, func(oldObj *ListSetStruct) []ComplexSetItemBeta { return oldObj.BetaSetBetaItem }), oldObj != nil)...)
// field ListSetStruct.ChainedSubfieldSetBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []SimpleSetItem, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SimpleSetItem) field.ErrorList {
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", func(o *SimpleSetItem) *int { return &o.Value }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()
})
})...)
// lists with set semantics require unique values
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
return
}(fldPath.Child("chainedSubfieldSetBeta"), obj.ChainedSubfieldSetBeta, safe.Field(oldObj, func(oldObj *ListSetStruct) []SimpleSetItem { return oldObj.ChainedSubfieldSetBeta }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,60 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package mapvalidation
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type MapValidationStruct struct {
TypeMeta int
// Case: Standard eachVal
// +k8s:eachVal=+k8s:maxLength=2
StandardEachVal map[string]string `json:"standardEachVal"`
// Case: Alpha eachVal
// +k8s:alpha=+k8s:eachVal=+k8s:maxLength=2
AlphaEachVal map[string]string `json:"AlphaEachVal"`
// Case: Beta eachVal
// +k8s:beta=+k8s:eachVal=+k8s:maxLength=2
BetaEachVal map[string]string `json:"BetaEachVal"`
// Case: Standard eachKey
// +k8s:eachKey=+k8s:maxLength=2
StandardEachKey map[string]string `json:"standardEachKey"`
// Case: Alpha eachKey
// +k8s:alpha=+k8s:eachKey=+k8s:maxLength=2
AlphaEachKey map[string]string `json:"AlphaEachKey"`
// Case: Beta eachKey
// +k8s:beta=+k8s:eachKey=+k8s:maxLength=2
BetaEachKey map[string]string `json:"BetaEachKey"`
// Case: Alpha Validation
// +k8s:eachVal=+k8s:alpha=+k8s:maxLength=2
AlphaValidation map[string]string `json:"AlphaValidation"`
// Case: Beta Validation
// +k8s:eachVal=+k8s:beta=+k8s:maxLength=2
BetaValidation map[string]string `json:"BetaValidation"`
}

View File

@@ -0,0 +1,62 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mapvalidation
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestMapValidation(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&MapValidationStruct{
StandardEachVal: map[string]string{"a": "foo"},
AlphaEachVal: map[string]string{"a": "foo"},
BetaEachVal: map[string]string{"a": "foo"},
StandardEachKey: map[string]string{"foo": "a"},
AlphaEachKey: map[string]string{"foo": "a"},
BetaEachKey: map[string]string{"foo": "a"},
AlphaValidation: map[string]string{"a": "foo"},
BetaValidation: map[string]string{"a": "foo"},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
// Case: Standard eachVal -> Normal Error
field.TooLong(field.NewPath("standardEachVal").Key("a"), "foo", 2).WithOrigin("maxLength"),
// Case: Alpha eachVal -> Alpha Error
field.TooLong(field.NewPath("AlphaEachVal").Key("a"), "foo", 2).WithOrigin("maxLength").MarkAlpha(),
// Case: Beta eachVal -> Beta Error
field.TooLong(field.NewPath("BetaEachVal").Key("a"), "foo", 2).WithOrigin("maxLength").MarkBeta(),
// Case: Standard eachKey -> Normal Error
field.TooLong(field.NewPath("standardEachKey"), "foo", 2).WithOrigin("maxLength"),
// Case: Alpha eachKey -> Alpha Error
field.TooLong(field.NewPath("AlphaEachKey"), "foo", 2).WithOrigin("maxLength").MarkAlpha(),
// Case: Beta eachKey -> Beta Error
field.TooLong(field.NewPath("BetaEachKey"), "foo", 2).WithOrigin("maxLength").MarkBeta(),
// Case: Alpha Validation -> Alpha Error
field.TooLong(field.NewPath("AlphaValidation").Key("a"), "foo", 2).WithOrigin("maxLength").MarkAlpha(),
// Case: Beta Validation -> Beta Error
field.TooLong(field.NewPath("BetaValidation").Key("a"), "foo", 2).WithOrigin("maxLength").MarkBeta(),
})
}

View File

@@ -0,0 +1,170 @@
//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 mapvalidation
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 MapValidationStruct
scheme.AddValidationFunc((*MapValidationStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_MapValidationStruct(ctx, op, nil /* fldPath */, obj.(*MapValidationStruct), safe.Cast[*MapValidationStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_MapValidationStruct validates an instance of MapValidationStruct according
// to declarative validation rules in the API schema.
func Validate_MapValidationStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapValidationStruct) (errs field.ErrorList) {
// field MapValidationStruct.TypeMeta has no validation
// field MapValidationStruct.StandardEachVal
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2)
})...)
return
}(fldPath.Child("standardEachVal"), obj.StandardEachVal, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.StandardEachVal }), oldObj != nil)...)
// field MapValidationStruct.AlphaEachVal
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2)
}).MarkAlpha()...)
return
}(fldPath.Child("AlphaEachVal"), obj.AlphaEachVal, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.AlphaEachVal }), oldObj != nil)...)
// field MapValidationStruct.BetaEachVal
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2)
}).MarkBeta()...)
return
}(fldPath.Child("BetaEachVal"), obj.BetaEachVal, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.BetaEachVal }), oldObj != nil)...)
// field MapValidationStruct.StandardEachKey
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2)
})...)
return
}(fldPath.Child("standardEachKey"), obj.StandardEachKey, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.StandardEachKey }), oldObj != nil)...)
// field MapValidationStruct.AlphaEachKey
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2)
}).MarkAlpha()...)
return
}(fldPath.Child("AlphaEachKey"), obj.AlphaEachKey, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.AlphaEachKey }), oldObj != nil)...)
// field MapValidationStruct.BetaEachKey
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2)
}).MarkBeta()...)
return
}(fldPath.Child("BetaEachKey"), obj.BetaEachKey, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.BetaEachKey }), oldObj != nil)...)
// field MapValidationStruct.AlphaValidation
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha()
})...)
return
}(fldPath.Child("AlphaValidation"), obj.AlphaValidation, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.AlphaValidation }), oldObj != nil)...)
// field MapValidationStruct.BetaValidation
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2).MarkBeta()
})...)
return
}(fldPath.Child("BetaValidation"), obj.BetaValidation, safe.Field(oldObj, func(oldObj *MapValidationStruct) map[string]string { return oldObj.BetaValidation }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,34 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package optionalrequired
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type Struct struct {
TypeMeta int
// +k8s:alpha=+k8s:required
RequiredField *string `json:"requiredField"`
// +k8s:beta=+k8s:required
RequiredFieldBeta *string `json:"requiredFieldBeta"`
}

View File

@@ -0,0 +1,64 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package optionalrequired
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
func TestAlpha(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
RequiredField: ptr.To("val"),
RequiredFieldBeta: ptr.To("val"),
}).ExpectValid()
// Test failures marked as alpha
st.Value(&Struct{
RequiredField: nil,
RequiredFieldBeta: ptr.To("val"),
}).OldValue(&Struct{
RequiredField: ptr.To("old"),
RequiredFieldBeta: ptr.To("old"),
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("requiredField"), "").MarkAlpha(),
})
}
func TestBeta(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
RequiredField: ptr.To("val"),
RequiredFieldBeta: ptr.To("val"),
}).ExpectValid()
// Test failures marked as beta
st.Value(&Struct{
RequiredField: ptr.To("val"),
RequiredFieldBeta: nil,
}).OldValue(&Struct{
RequiredField: ptr.To("old"),
RequiredFieldBeta: ptr.To("old"),
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("requiredFieldBeta"), "").MarkBeta(),
})
}

View File

@@ -0,0 +1,95 @@
//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 optionalrequired
import (
context "context"
fmt "fmt"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
field "k8s.io/apimachinery/pkg/util/validation/field"
testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *testscheme.Scheme) error {
// 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.RequiredField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("requiredField"), obj.RequiredField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.RequiredField }), oldObj != nil)...)
// field Struct.RequiredFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("requiredFieldBeta"), obj.RequiredFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.RequiredFieldBeta }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,90 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package simple
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type Struct struct {
TypeMeta int
// +k8s:alpha=+k8s:minimum=10
IntField int `json:"intField"`
// +k8s:beta=+k8s:minimum=10
IntFieldBeta int `json:"intFieldBeta"`
// +k8s:alpha=+k8s:maxLength=5
StringField string `json:"stringField"`
// +k8s:beta=+k8s:maxLength=5
StringFieldBeta string `json:"stringFieldBeta"`
// +k8s:alpha=+k8s:maxItems=2
SliceField []string `json:"sliceField"`
// +k8s:beta=+k8s:maxItems=2
SliceFieldBeta []string `json:"sliceFieldBeta"`
// +k8s:alpha=+k8s:format=k8s-uuid
UUIDField string `json:"uuidField"`
// +k8s:beta=+k8s:format=k8s-uuid
UUIDFieldBeta string `json:"uuidFieldBeta"`
// +k8s:alpha=+k8s:immutable
ImmutableField string `json:"immutableField"`
// +k8s:beta=+k8s:immutable
ImmutableFieldBeta string `json:"immutableFieldBeta"`
}
type SpecialValidationStruct struct {
TypeMeta int
// +k8s:alpha=+k8s:neq=5
NEQField int `json:"neqField"`
// +k8s:beta=+k8s:neq=5
NEQFieldBeta int `json:"neqFieldBeta"`
// +k8s:alpha=+k8s:forbidden
ForbiddenField *string `json:"forbiddenField"`
// +k8s:beta=+k8s:forbidden
ForbiddenFieldBeta *string `json:"forbiddenFieldBeta"`
// +k8s:alpha=+k8s:update=NoModify
UpdateField string `json:"updateField"`
// +k8s:beta=+k8s:update=NoModify
UpdateFieldBeta string `json:"updateFieldBeta"`
}
type StructWithValidateFalse struct {
TypeMeta int
// +k8s:alpha=+k8s:validateFalse="always fails"
ValidateFalse *string `json:"validateFalse"`
// +k8s:beta=+k8s:validateFalse="always fails"
ValidateFalseBeta *string `json:"validateFalseBeta"`
}

View File

@@ -0,0 +1,140 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package simple
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
func TestAlpha(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
IntField: 10,
StringField: "abc",
SliceField: []string{"a", "b"},
UUIDField: "a0a2a2d2-0b87-4964-a123-78d00a8787a6",
ImmutableField: "foo",
IntFieldBeta: 10,
StringFieldBeta: "abc",
SliceFieldBeta: []string{"a", "b"},
UUIDFieldBeta: "a0a2a2d2-0b87-4964-a123-78d00a8787a6",
ImmutableFieldBeta: "foo",
}).ExpectValid()
// Test failures marked as alpha
st.Value(&Struct{
IntField: 5,
StringField: "too-long",
SliceField: []string{"a", "b", "c"},
UUIDField: "not-a-uuid",
ImmutableField: "bar",
IntFieldBeta: 10,
StringFieldBeta: "abc",
SliceFieldBeta: []string{"a", "b"},
UUIDFieldBeta: "a0a2a2d2-0b87-4964-a123-78d00a8787a6",
ImmutableFieldBeta: "foo",
}).OldValue(&Struct{
ImmutableField: "foo",
ImmutableFieldBeta: "foo",
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("intField"), 5, "").WithOrigin("minimum").MarkAlpha(),
field.TooLong(field.NewPath("stringField"), "too-long", 5).WithOrigin("maxLength").MarkAlpha(),
field.TooMany(field.NewPath("sliceField"), 3, 2).WithOrigin("maxItems").MarkAlpha(),
field.Invalid(field.NewPath("uuidField"), "not-a-uuid", "").WithOrigin("format=k8s-uuid").MarkAlpha(),
field.Invalid(field.NewPath("immutableField"), "bar", "").WithOrigin("immutable").MarkAlpha(),
})
}
func TestBeta(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
IntField: 10,
StringField: "abc",
SliceField: []string{"a", "b"},
UUIDField: "a0a2a2d2-0b87-4964-a123-78d00a8787a6",
ImmutableField: "foo",
IntFieldBeta: 10,
StringFieldBeta: "abc",
SliceFieldBeta: []string{"a", "b"},
UUIDFieldBeta: "a0a2a2d2-0b87-4964-a123-78d00a8787a6",
ImmutableFieldBeta: "foo",
}).ExpectValid()
// Test failures marked as beta
st.Value(&Struct{
IntField: 10,
StringField: "abc",
SliceField: []string{"a", "b"},
UUIDField: "a0a2a2d2-0b87-4964-a123-78d00a8787a6",
ImmutableField: "foo",
IntFieldBeta: 5,
StringFieldBeta: "too-long",
SliceFieldBeta: []string{"a", "b", "c"},
UUIDFieldBeta: "not-a-uuid",
ImmutableFieldBeta: "bar",
}).OldValue(&Struct{
ImmutableField: "foo",
ImmutableFieldBeta: "foo",
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("intFieldBeta"), 5, "").WithOrigin("minimum").MarkBeta(),
field.TooLong(field.NewPath("stringFieldBeta"), "too-long", 5).WithOrigin("maxLength").MarkBeta(),
field.TooMany(field.NewPath("sliceFieldBeta"), 3, 2).WithOrigin("maxItems").MarkBeta(),
field.Invalid(field.NewPath("uuidFieldBeta"), "not-a-uuid", "").WithOrigin("format=k8s-uuid").MarkBeta(),
field.Invalid(field.NewPath("immutableFieldBeta"), "bar", "").WithOrigin("immutable").MarkBeta(),
})
}
func TestSpecialValidationStruct(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&SpecialValidationStruct{
NEQField: 10,
NEQFieldBeta: 10,
}).ExpectValid()
st.Value(&SpecialValidationStruct{
NEQField: 5,
NEQFieldBeta: 5,
ForbiddenField: ptr.To("val"),
ForbiddenFieldBeta: ptr.To("val"),
UpdateField: "new",
UpdateFieldBeta: "new",
}).OldValue(&SpecialValidationStruct{
UpdateField: "old",
UpdateFieldBeta: "old",
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("neqField"), 5, "must not be 5").WithOrigin("neq").MarkAlpha(),
field.Invalid(field.NewPath("neqFieldBeta"), 5, "must not be 5").WithOrigin("neq").MarkBeta(),
field.Forbidden(field.NewPath("forbiddenField"), "").MarkAlpha(),
field.Forbidden(field.NewPath("forbiddenFieldBeta"), "").MarkBeta(),
field.Invalid(field.NewPath("updateField"), "new", "field cannot be modified once set").WithOrigin("update").MarkAlpha(),
field.Invalid(field.NewPath("updateFieldBeta"), "new", "field cannot be modified once set").WithOrigin("update").MarkBeta(),
})
st.Value(&StructWithValidateFalse{
ValidateFalse: ptr.To("val"),
ValidateFalseBeta: ptr.To("val"),
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("validateFalse"), "val", "always fails").MarkAlpha(),
field.Invalid(field.NewPath("validateFalseBeta"), "val", "always fails").MarkBeta(),
})
}

View File

@@ -0,0 +1,368 @@
//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 simple
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 SpecialValidationStruct
scheme.AddValidationFunc((*SpecialValidationStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_SpecialValidationStruct(ctx, op, nil /* fldPath */, obj.(*SpecialValidationStruct), safe.Cast[*SpecialValidationStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// 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()))}
})
// type StructWithValidateFalse
scheme.AddValidationFunc((*StructWithValidateFalse)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_StructWithValidateFalse(ctx, op, nil /* fldPath */, obj.(*StructWithValidateFalse), safe.Cast[*StructWithValidateFalse](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_SpecialValidationStruct validates an instance of SpecialValidationStruct according
// to declarative validation rules in the API schema.
func Validate_SpecialValidationStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SpecialValidationStruct) (errs field.ErrorList) {
// field SpecialValidationStruct.TypeMeta has no validation
// field SpecialValidationStruct.NEQField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.NEQ(ctx, op, fldPath, obj, oldObj, 5).MarkAlpha()...)
return
}(fldPath.Child("neqField"), &obj.NEQField, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *int { return &oldObj.NEQField }), oldObj != nil)...)
// field SpecialValidationStruct.NEQFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.NEQ(ctx, op, fldPath, obj, oldObj, 5).MarkBeta()...)
return
}(fldPath.Child("neqFieldBeta"), &obj.NEQFieldBeta, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *int { return &oldObj.NEQFieldBeta }), oldObj != nil)...)
// field SpecialValidationStruct.ForbiddenField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("forbiddenField"), obj.ForbiddenField, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *string { return oldObj.ForbiddenField }), oldObj != nil)...)
// field SpecialValidationStruct.ForbiddenFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("forbiddenFieldBeta"), obj.ForbiddenFieldBeta, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *string { return oldObj.ForbiddenFieldBeta }), oldObj != nil)...)
// field SpecialValidationStruct.UpdateField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("updateField"), &obj.UpdateField, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *string { return &oldObj.UpdateField }), oldObj != nil)...)
// field SpecialValidationStruct.UpdateFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("updateFieldBeta"), &obj.UpdateFieldBeta, safe.Field(oldObj, func(oldObj *SpecialValidationStruct) *string { return &oldObj.UpdateFieldBeta }), oldObj != nil)...)
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.IntField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...)
return
}(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntField }), oldObj != nil)...)
// field Struct.IntFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()...)
return
}(fldPath.Child("intFieldBeta"), &obj.IntFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntFieldBeta }), oldObj != nil)...)
// field Struct.StringField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 5).MarkAlpha()...)
return
}(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }), oldObj != nil)...)
// field Struct.StringFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 5).MarkBeta()...)
return
}(fldPath.Child("stringFieldBeta"), &obj.StringFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringFieldBeta }), oldObj != nil)...)
// field Struct.SliceField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && 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, 2).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("sliceField"), obj.SliceField, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.SliceField }), oldObj != nil)...)
// field Struct.SliceFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && 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, 2).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("sliceFieldBeta"), obj.SliceFieldBeta, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.SliceFieldBeta }), oldObj != nil)...)
// field Struct.UUIDField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkAlpha()...)
return
}(fldPath.Child("uuidField"), &obj.UUIDField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.UUIDField }), oldObj != nil)...)
// field Struct.UUIDFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.UUID(ctx, op, fldPath, obj, oldObj).MarkBeta()...)
return
}(fldPath.Child("uuidFieldBeta"), &obj.UUIDFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.UUIDFieldBeta }), oldObj != nil)...)
// field Struct.ImmutableField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("immutableField"), &obj.ImmutableField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.ImmutableField }), oldObj != nil)...)
// field Struct.ImmutableFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("immutableFieldBeta"), &obj.ImmutableFieldBeta, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.ImmutableFieldBeta }), oldObj != nil)...)
return errs
}
// Validate_StructWithValidateFalse validates an instance of StructWithValidateFalse according
// to declarative validation rules in the API schema.
func Validate_StructWithValidateFalse(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StructWithValidateFalse) (errs field.ErrorList) {
// field StructWithValidateFalse.TypeMeta has no validation
// field StructWithValidateFalse.ValidateFalse
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "always fails").MarkAlpha()...)
return
}(fldPath.Child("validateFalse"), obj.ValidateFalse, safe.Field(oldObj, func(oldObj *StructWithValidateFalse) *string { return oldObj.ValidateFalse }), oldObj != nil)...)
// field StructWithValidateFalse.ValidateFalseBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "always fails").MarkBeta()...)
return
}(fldPath.Child("validateFalseBeta"), obj.ValidateFalseBeta, safe.Field(oldObj, func(oldObj *StructWithValidateFalse) *string { return oldObj.ValidateFalseBeta }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,60 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package structs
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type MixedStruct struct {
TypeMeta int
// +k8s:minimum=5
// +k8s:alpha=+k8s:minimum=10
IntField int `json:"intField"`
// +k8s:minimum=5
// +k8s:beta=+k8s:minimum=10
IntFieldBeta int `json:"intFieldBeta"`
// +k8s:maxItems=5
// +k8s:alpha=+k8s:maxItems=3
ListField []string `json:"listField"`
// +k8s:maxItems=5
// +k8s:beta=+k8s:maxItems=3
ListFieldBeta []string `json:"listFieldBeta"`
}
type ConditionalStruct struct {
TypeMeta int
// +k8s:alpha=+k8s:ifEnabled(MyFeature)=+k8s:minimum=10
ConditionalField int `json:"conditionalField"`
// +k8s:beta=+k8s:ifEnabled(MyFeature)=+k8s:minimum=10
ConditionalFieldBeta int `json:"conditionalFieldBeta"`
// +k8s:alpha=+k8s:alpha=+k8s:minimum=20
RecursiveAlpha int `json:"recursiveAlpha"`
// +k8s:beta=+k8s:beta=+k8s:minimum=20
RecursiveBeta int `json:"recursiveBeta"`
}

View File

@@ -0,0 +1,92 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package structs
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestMixed(t *testing.T) {
st := localSchemeBuilder.Test(t)
// Valid case (meets both normal and alpha requirements)
st.Value(&MixedStruct{
IntField: 15,
IntFieldBeta: 15,
ListField: []string{"a", "b", "c"},
ListFieldBeta: []string{"a", "b", "c"},
}).ExpectValid()
// Fails alpha validation but passes normal validation
// IntField: 5 <= 8 < 10 (alpha fails)
// ListField: 3 < 4 <= 5 (alpha fails)
st.Value(&MixedStruct{
IntField: 8,
IntFieldBeta: 8,
ListField: []string{"a", "b", "c", "d"},
ListFieldBeta: []string{"a", "b", "c", "d"},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("intField"), 8, "").WithOrigin("minimum").MarkAlpha(),
field.Invalid(field.NewPath("intFieldBeta"), 8, "").WithOrigin("minimum").MarkBeta(),
field.TooMany(field.NewPath("listField"), 4, 3).WithOrigin("maxItems").MarkAlpha(),
field.TooMany(field.NewPath("listFieldBeta"), 4, 3).WithOrigin("maxItems").MarkBeta(),
})
// Fails both normal and alpha validation
// IntField: 4 < 5 (normal fails) AND 4 < 10 (alpha fails)
// ListField: 6 > 5 (normal fails) AND 6 > 3 (alpha fails)
st.Value(&MixedStruct{
IntField: 4,
IntFieldBeta: 4,
ListField: []string{"a", "b", "c", "d", "e", "f"},
ListFieldBeta: []string{"a", "b", "c", "d", "e", "f"},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("intField"), 4, "").WithOrigin("minimum"),
field.Invalid(field.NewPath("intField"), 4, "").WithOrigin("minimum").MarkAlpha(),
field.Invalid(field.NewPath("intFieldBeta"), 4, "").WithOrigin("minimum"),
field.Invalid(field.NewPath("intFieldBeta"), 4, "").WithOrigin("minimum").MarkBeta(),
field.TooMany(field.NewPath("listField"), 6, 5).WithOrigin("maxItems"),
field.TooMany(field.NewPath("listField"), 6, 3).WithOrigin("maxItems").MarkAlpha(),
field.TooMany(field.NewPath("listFieldBeta"), 6, 5).WithOrigin("maxItems"),
field.TooMany(field.NewPath("listFieldBeta"), 6, 3).WithOrigin("maxItems").MarkBeta(),
})
}
func TestConditionalStruct(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&ConditionalStruct{
ConditionalField: 15,
ConditionalFieldBeta: 15,
RecursiveAlpha: 25,
RecursiveBeta: 25,
}).Opts([]string{"MyFeature"}).ExpectValid()
st.Value(&ConditionalStruct{
ConditionalField: 5,
ConditionalFieldBeta: 5,
RecursiveAlpha: 10,
RecursiveBeta: 10,
}).Opts([]string{"MyFeature"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("conditionalField"), 5, "").WithOrigin("minimum").MarkAlpha(),
field.Invalid(field.NewPath("conditionalFieldBeta"), 5, "").WithOrigin("minimum").MarkBeta(),
field.Invalid(field.NewPath("recursiveAlpha"), 10, "").WithOrigin("minimum").MarkAlpha(),
field.Invalid(field.NewPath("recursiveBeta"), 10, "").WithOrigin("minimum").MarkBeta(),
})
}

View File

@@ -0,0 +1,198 @@
//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 structs
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 ConditionalStruct
scheme.AddValidationFunc((*ConditionalStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ConditionalStruct(ctx, op, nil /* fldPath */, obj.(*ConditionalStruct), safe.Cast[*ConditionalStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type MixedStruct
scheme.AddValidationFunc((*MixedStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_MixedStruct(ctx, op, nil /* fldPath */, obj.(*MixedStruct), safe.Cast[*MixedStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_ConditionalStruct validates an instance of ConditionalStruct according
// to declarative validation rules in the API schema.
func Validate_ConditionalStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ConditionalStruct) (errs field.ErrorList) {
// field ConditionalStruct.TypeMeta has no validation
// field ConditionalStruct.ConditionalField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "MyFeature", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10)
}).MarkAlpha()...)
return
}(fldPath.Child("conditionalField"), &obj.ConditionalField, safe.Field(oldObj, func(oldObj *ConditionalStruct) *int { return &oldObj.ConditionalField }), oldObj != nil)...)
// field ConditionalStruct.ConditionalFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "MyFeature", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10)
}).MarkBeta()...)
return
}(fldPath.Child("conditionalFieldBeta"), &obj.ConditionalFieldBeta, safe.Field(oldObj, func(oldObj *ConditionalStruct) *int { return &oldObj.ConditionalFieldBeta }), oldObj != nil)...)
// field ConditionalStruct.RecursiveAlpha
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 20).MarkAlpha()...)
return
}(fldPath.Child("recursiveAlpha"), &obj.RecursiveAlpha, safe.Field(oldObj, func(oldObj *ConditionalStruct) *int { return &oldObj.RecursiveAlpha }), oldObj != nil)...)
// field ConditionalStruct.RecursiveBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 20).MarkBeta()...)
return
}(fldPath.Child("recursiveBeta"), &obj.RecursiveBeta, safe.Field(oldObj, func(oldObj *ConditionalStruct) *int { return &oldObj.RecursiveBeta }), oldObj != nil)...)
return errs
}
// Validate_MixedStruct validates an instance of MixedStruct according
// to declarative validation rules in the API schema.
func Validate_MixedStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedStruct) (errs field.ErrorList) {
// field MixedStruct.TypeMeta has no validation
// field MixedStruct.IntField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 5)...)
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha()...)
return
}(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *MixedStruct) *int { return &oldObj.IntField }), oldObj != nil)...)
// field MixedStruct.IntFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *int, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 5)...)
errs = append(errs, validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta()...)
return
}(fldPath.Child("intFieldBeta"), &obj.IntFieldBeta, safe.Field(oldObj, func(oldObj *MixedStruct) *int { return &oldObj.IntFieldBeta }), oldObj != nil)...)
// field MixedStruct.ListField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && 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, 5); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 3).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("listField"), obj.ListField, safe.Field(oldObj, func(oldObj *MixedStruct) []string { return oldObj.ListField }), oldObj != nil)...)
// field MixedStruct.ListFieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && 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, 5); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 3).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("listFieldBeta"), obj.ListFieldBeta, safe.Field(oldObj, func(oldObj *MixedStruct) []string { return oldObj.ListFieldBeta }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,38 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package subfields
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type Struct struct {
TypeMeta int
// +k8s:alpha=+k8s:subfield(inner)=+k8s:minimum=5
Subfield SubStruct `json:"subfield"`
// +k8s:beta=+k8s:subfield(inner)=+k8s:minimum=5
SubfieldBeta SubStruct `json:"subfieldBeta"`
}
type SubStruct struct {
Inner int `json:"inner"`
}

View File

@@ -0,0 +1,57 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package subfields
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestAlpha(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
Subfield: SubStruct{Inner: 5},
SubfieldBeta: SubStruct{Inner: 5},
}).ExpectValid()
// Test failures marked as alpha
st.Value(&Struct{
Subfield: SubStruct{Inner: 1},
SubfieldBeta: SubStruct{Inner: 5},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("subfield", "inner"), 1, "").WithOrigin("minimum").MarkAlpha(),
})
}
func TestBeta(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
Subfield: SubStruct{Inner: 5},
SubfieldBeta: SubStruct{Inner: 5},
}).ExpectValid()
// Test failures marked as beta
st.Value(&Struct{
Subfield: SubStruct{Inner: 5},
SubfieldBeta: SubStruct{Inner: 1},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("subfieldBeta", "inner"), 1, "").WithOrigin("minimum").MarkBeta(),
})
}

View File

@@ -0,0 +1,89 @@
//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 subfields
import (
context "context"
fmt "fmt"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
field "k8s.io/apimachinery/pkg/util/validation/field"
testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *testscheme.Scheme) error {
// 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.Subfield
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *SubStruct, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
func() { // cohort inner
errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "inner", func(o *SubStruct) *int { return &o.Inner }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 5)
}).MarkAlpha()...)
}()
return
}(fldPath.Child("subfield"), &obj.Subfield, safe.Field(oldObj, func(oldObj *Struct) *SubStruct { return &oldObj.Subfield }), oldObj != nil)...)
// field Struct.SubfieldBeta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *SubStruct, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
func() { // cohort inner
errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "inner", func(o *SubStruct) *int { return &o.Inner }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList {
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 5)
}).MarkBeta()...)
}()
return
}(fldPath.Child("subfieldBeta"), &obj.SubfieldBeta, safe.Field(oldObj, func(oldObj *Struct) *SubStruct { return &oldObj.SubfieldBeta }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,104 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package unions
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type Struct struct {
TypeMeta int
// +k8s:alpha=+k8s:unionDiscriminator
D D `json:"d"`
// +k8s:alpha=+k8s:unionMember
// +k8s:optional
M1 *M1 `json:"m1"`
// +k8s:alpha(since:"1.35")=+k8s:unionMember
// +k8s:optional
M2 *M2 `json:"m2"`
}
type UnionStructBeta struct {
TypeMeta int
// +k8s:beta=+k8s:unionDiscriminator
DBeta BetaD `json:"dBeta"`
// +k8s:beta=+k8s:unionMember
// +k8s:optional
M1Beta *BetaM1 `json:"m1Beta"`
// +k8s:beta(since:"1.35")=+k8s:unionMember
// +k8s:optional
M2Beta *BetaM2 `json:"m2Beta"`
}
type MyStruct struct {
TypeMeta int
// +k8s:alpha=+k8s:zeroOrOneOfMember
// +k8s:optional
Z1 *Z1 `json:"z1"`
// +k8s:alpha=+k8s:zeroOrOneOfMember
// +k8s:optional
Z2 *Z2 `json:"z2"`
}
type MyStructBeta struct {
TypeMeta int
// +k8s:beta=+k8s:zeroOrOneOfMember
// +k8s:optional
Z1Beta *BetaZ1 `json:"z1Beta"`
// +k8s:beta=+k8s:zeroOrOneOfMember
// +k8s:optional
Z2Beta *BetaZ2 `json:"z2Beta"`
}
type D string
const (
DM1 D = "M1"
DM2 D = "M2"
)
type M1 struct{}
type M2 struct{}
type BetaD string
const (
BetaDM1 BetaD = "M1Beta"
BetaDM2 BetaD = "M2Beta"
)
type BetaM1 struct{}
type BetaM2 struct{}
type Z1 struct{}
type Z2 struct{}
type BetaZ1 struct{}
type BetaZ2 struct{}

View File

@@ -0,0 +1,73 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unions
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestAlpha(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
D: DM1,
M1: &M1{},
}).ExpectValid()
st.Value(&Struct{
D: DM1,
M1: nil, // required by discriminator
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("m1"), nil, "").WithOrigin("union").MarkAlpha(),
})
st.Value(&MyStruct{
Z1: &Z1{},
Z2: &Z2{},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(nil, &MyStruct{
Z1: &Z1{}, Z2: &Z2{},
}, "only one of z1, z2 may be specified").WithOrigin("zeroOrOneOf").MarkAlpha(),
})
}
func TestBeta(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&UnionStructBeta{
DBeta: BetaDM1,
M1Beta: &BetaM1{},
}).ExpectValid()
st.Value(&UnionStructBeta{
DBeta: BetaDM1,
M1Beta: nil, // required by discriminator
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(field.NewPath("m1Beta"), nil, "").WithOrigin("union").MarkBeta(),
})
st.Value(&MyStructBeta{
Z1Beta: &BetaZ1{},
Z2Beta: &BetaZ2{},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Invalid(nil, &MyStructBeta{
Z1Beta: &BetaZ1{}, Z2Beta: &BetaZ2{},
}, "only one of z1Beta, z2Beta may be specified").WithOrigin("zeroOrOneOf").MarkBeta(),
})
}

View File

@@ -0,0 +1,317 @@
//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 unions
import (
context "context"
fmt "fmt"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
field "k8s.io/apimachinery/pkg/util/validation/field"
testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *testscheme.Scheme) error {
// type MyStruct
scheme.AddValidationFunc((*MyStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_MyStruct(ctx, op, nil /* fldPath */, obj.(*MyStruct), safe.Cast[*MyStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type MyStructBeta
scheme.AddValidationFunc((*MyStructBeta)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_MyStructBeta(ctx, op, nil /* fldPath */, obj.(*MyStructBeta), safe.Cast[*MyStructBeta](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// 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()))}
})
// type UnionStructBeta
scheme.AddValidationFunc((*UnionStructBeta)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_UnionStructBeta(ctx, op, nil /* fldPath */, obj.(*UnionStructBeta), safe.Cast[*UnionStructBeta](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStruct_ = validate.NewUnionMembership(validate.NewUnionMember("z1"), validate.NewUnionMember("z2"))
// Validate_MyStruct validates an instance of MyStruct according
// to declarative validation rules in the API schema.
func Validate_MyStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MyStruct) (errs field.ErrorList) {
errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStruct_, func(obj *MyStruct) bool {
if obj == nil {
return false
}
return obj.Z1 != nil
}, func(obj *MyStruct) bool {
if obj == nil {
return false
}
return obj.Z2 != nil
}).MarkAlpha()...)
// field MyStruct.TypeMeta has no validation
// field MyStruct.Z1
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *Z1, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("z1"), obj.Z1, safe.Field(oldObj, func(oldObj *MyStruct) *Z1 { return oldObj.Z1 }), oldObj != nil)...)
// field MyStruct.Z2
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *Z2, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("z2"), obj.Z2, safe.Field(oldObj, func(oldObj *MyStruct) *Z2 { return oldObj.Z2 }), oldObj != nil)...)
return errs
}
var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStructBeta_ = validate.NewUnionMembership(validate.NewUnionMember("z1Beta"), validate.NewUnionMember("z2Beta"))
// Validate_MyStructBeta validates an instance of MyStructBeta according
// to declarative validation rules in the API schema.
func Validate_MyStructBeta(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MyStructBeta) (errs field.ErrorList) {
errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStructBeta_, func(obj *MyStructBeta) bool {
if obj == nil {
return false
}
return obj.Z1Beta != nil
}, func(obj *MyStructBeta) bool {
if obj == nil {
return false
}
return obj.Z2Beta != nil
}).MarkBeta()...)
// field MyStructBeta.TypeMeta has no validation
// field MyStructBeta.Z1Beta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *BetaZ1, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("z1Beta"), obj.Z1Beta, safe.Field(oldObj, func(oldObj *MyStructBeta) *BetaZ1 { return oldObj.Z1Beta }), oldObj != nil)...)
// field MyStructBeta.Z2Beta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *BetaZ2, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("z2Beta"), obj.Z2Beta, safe.Field(oldObj, func(oldObj *MyStructBeta) *BetaZ2 { return oldObj.Z2Beta }), oldObj != nil)...)
return errs
}
var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_Struct_ = validate.NewDiscriminatedUnionMembership("d", validate.NewDiscriminatedUnionMember("m1", "M1"), validate.NewDiscriminatedUnionMember("m2", "M2"))
// 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) {
errs = append(errs, validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_Struct_, func(obj *Struct) string {
if obj == nil {
return ""
}
return string(obj.D)
}, func(obj *Struct) bool {
if obj == nil {
return false
}
return obj.M1 != nil
}, func(obj *Struct) bool {
if obj == nil {
return false
}
return obj.M2 != nil
}).MarkAlpha()...)
// field Struct.TypeMeta has no validation
// field Struct.D has no validation
// field Struct.M1
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *M1, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("m1"), obj.M1, safe.Field(oldObj, func(oldObj *Struct) *M1 { return oldObj.M1 }), oldObj != nil)...)
// field Struct.M2
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *M2, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("m2"), obj.M2, safe.Field(oldObj, func(oldObj *Struct) *M2 { return oldObj.M2 }), oldObj != nil)...)
return errs
}
var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_UnionStructBeta_ = validate.NewDiscriminatedUnionMembership("dBeta", validate.NewDiscriminatedUnionMember("m1Beta", "M1Beta"), validate.NewDiscriminatedUnionMember("m2Beta", "M2Beta"))
// Validate_UnionStructBeta validates an instance of UnionStructBeta according
// to declarative validation rules in the API schema.
func Validate_UnionStructBeta(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *UnionStructBeta) (errs field.ErrorList) {
errs = append(errs, validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_UnionStructBeta_, func(obj *UnionStructBeta) string {
if obj == nil {
return ""
}
return string(obj.DBeta)
}, func(obj *UnionStructBeta) bool {
if obj == nil {
return false
}
return obj.M1Beta != nil
}, func(obj *UnionStructBeta) bool {
if obj == nil {
return false
}
return obj.M2Beta != nil
}).MarkBeta()...)
// field UnionStructBeta.TypeMeta has no validation
// field UnionStructBeta.DBeta has no validation
// field UnionStructBeta.M1Beta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *BetaM1, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("m1Beta"), obj.M1Beta, safe.Field(oldObj, func(oldObj *UnionStructBeta) *BetaM1 { return oldObj.M1Beta }), oldObj != nil)...)
// field UnionStructBeta.M2Beta
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *BetaM2, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}(fldPath.Child("m2Beta"), obj.M2Beta, safe.Field(oldObj, func(oldObj *UnionStructBeta) *BetaM2 { return oldObj.M2Beta }), oldObj != nil)...)
return errs
}

View File

@@ -0,0 +1,36 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
package uniquetag
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type UniqueStruct struct {
TypeMeta int
// +k8s:listType=atomic
// +k8s:alpha=+k8s:unique=set
AlphaUniqueSet []string `json:"alphaUniqueSet"`
// +k8s:listType=atomic
// +k8s:beta=+k8s:unique=set
BetaUniqueSet []string `json:"betaUniqueSet"`
}

View File

@@ -0,0 +1,35 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package uniquetag
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func TestMisc(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&UniqueStruct{
AlphaUniqueSet: []string{"a", "a"},
BetaUniqueSet: []string{"a", "a"},
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{
field.Duplicate(field.NewPath("alphaUniqueSet").Index(1), "a").MarkAlpha(),
field.Duplicate(field.NewPath("betaUniqueSet").Index(1), "a").MarkBeta(),
})
}

View File

@@ -0,0 +1,84 @@
//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 uniquetag
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 UniqueStruct
scheme.AddValidationFunc((*UniqueStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_UniqueStruct(ctx, op, nil /* fldPath */, obj.(*UniqueStruct), safe.Cast[*UniqueStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_UniqueStruct validates an instance of UniqueStruct according
// to declarative validation rules in the API schema.
func Validate_UniqueStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *UniqueStruct) (errs field.ErrorList) {
// field UniqueStruct.TypeMeta has no validation
// field UniqueStruct.AlphaUniqueSet
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with set semantics require unique values
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha()...)
return
}(fldPath.Child("alphaUniqueSet"), obj.AlphaUniqueSet, safe.Field(oldObj, func(oldObj *UniqueStruct) []string { return oldObj.AlphaUniqueSet }), oldObj != nil)...)
// field UniqueStruct.BetaUniqueSet
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
// lists with set semantics require unique values
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkBeta()...)
return
}(fldPath.Child("betaUniqueSet"), obj.BetaUniqueSet, safe.Field(oldObj, func(oldObj *UniqueStruct) []string { return oldObj.BetaUniqueSet }), oldObj != nil)...)
return errs
}