mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-22 09:40:07 +00:00
Add tests for unique tag combo & update-codegen
This commit is contained in:
@@ -142,7 +142,7 @@ func Validate_StructSlice(ctx context.Context, op operation.Operation, fldPath *
|
||||
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 *ComparableStruct) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field SetSliceComparableField[*]")
|
||||
})...)
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
|
||||
return
|
||||
}(fldPath.Child("setSliceComparableField"), obj.SetSliceComparableField, safe.Field(oldObj, func(oldObj *StructSlice) []ComparableStruct { return oldObj.SetSliceComparableField }))...)
|
||||
@@ -158,7 +158,7 @@ func Validate_StructSlice(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field SetSliceNonComparableField[*]")
|
||||
})...)
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual)...)
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, Validate_NonComparableStruct)...)
|
||||
|
||||
@@ -46,6 +46,13 @@ type Struct struct {
|
||||
// +k8s:listMapKey=id
|
||||
// +k8s:item(id: "typedef-target")=+k8s:validateFalse="item TypedefItems[id=typedef-target]"
|
||||
TypedefItems TypedefItemList `json:"typedefItems"`
|
||||
|
||||
// Test atomic + unique=map + item combination
|
||||
// +k8s:listType=atomic
|
||||
// +k8s:unique=map
|
||||
// +k8s:listMapKey=key
|
||||
// +k8s:item(key: "target")=+k8s:validateFalse="item AtomicUniqueMapItems[key=target]"
|
||||
AtomicUniqueMapItems []Item `json:"atomicUniqueMapItems"`
|
||||
}
|
||||
|
||||
type StructWithNestedTypedef struct {
|
||||
|
||||
@@ -103,4 +103,31 @@ func Test(t *testing.T) {
|
||||
{Key: "b", Name: "n2"},
|
||||
},
|
||||
}).ExpectValid()
|
||||
|
||||
// Test atomic + unique=map + item combination
|
||||
st.Value(&Struct{
|
||||
AtomicUniqueMapItems: []Item{
|
||||
{Key: "a", Data: "d1"},
|
||||
{Key: "target", Data: "d2"},
|
||||
},
|
||||
}).ExpectValidateFalseByPath(map[string][]string{
|
||||
`atomicUniqueMapItems[1]`: {
|
||||
"item AtomicUniqueMapItems[key=target]",
|
||||
},
|
||||
})
|
||||
|
||||
st.Value(&Struct{
|
||||
AtomicUniqueMapItems: []Item{
|
||||
{Key: "a", Data: "d1"},
|
||||
{Key: "b", Data: "d2"},
|
||||
},
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
AtomicUniqueMapItems: []Item{},
|
||||
}).ExpectValid()
|
||||
|
||||
st.Value(&Struct{
|
||||
AtomicUniqueMapItems: nil,
|
||||
}).ExpectValid()
|
||||
}
|
||||
|
||||
@@ -118,6 +118,22 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return
|
||||
}(fldPath.Child("typedefItems"), obj.TypedefItems, safe.Field(oldObj, func(oldObj *Struct) TypedefItemList { return oldObj.TypedefItems }))...)
|
||||
|
||||
// field Struct.AtomicUniqueMapItems
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []Item) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key == "target" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item AtomicUniqueMapItems[key=target]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a Item, b Item) bool { return a.Key == b.Key })...)
|
||||
return
|
||||
}(fldPath.Child("atomicUniqueMapItems"), obj.AtomicUniqueMapItems, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.AtomicUniqueMapItems }))...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ type Struct struct {
|
||||
|
||||
// +k8s:item(id: "field-target")=+k8s:validateFalse="item DualItems[id=field-target] from field"
|
||||
DualItems DualItemList `json:"dualItems"`
|
||||
|
||||
// +k8s:item(id: "target")=+k8s:validateFalse="item ConflictingItems[id=target] from field"
|
||||
ConflictingItems ConflictingItemList `json:"conflictingItems"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
@@ -59,3 +62,8 @@ type DualItem struct {
|
||||
// +k8s:listMapKey=id
|
||||
// +k8s:item(id: "typedef-target")=+k8s:validateFalse="item DualItems[id=typedef-target] from typedef"
|
||||
type DualItemList []DualItem
|
||||
|
||||
// +k8s:listType=map
|
||||
// +k8s:listMapKey=id
|
||||
// +k8s:item(id: "target")=+k8s:validateFalse="item ConflictingItems[id=target] from typedef"
|
||||
type ConflictingItemList []DualItem
|
||||
|
||||
@@ -77,4 +77,17 @@ func Test(t *testing.T) {
|
||||
`dualItems[1]`: {"item DualItems[id=typedef-target] from typedef"},
|
||||
`dualItems[2]`: {"item DualItems[id=field-target] from field"},
|
||||
})
|
||||
|
||||
// Test tag on field and typedef with same key.
|
||||
st.Value(&Struct{
|
||||
ConflictingItems: ConflictingItemList{
|
||||
{ID: "a", Name: "n1"},
|
||||
{ID: "target", Name: "n2"},
|
||||
},
|
||||
}).ExpectValidateFalseByPath(map[string][]string{
|
||||
`conflictingItems[1]`: {
|
||||
"item ConflictingItems[id=target] from typedef",
|
||||
"item ConflictingItems[id=target] from field",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -49,6 +49,16 @@ func RegisterValidations(scheme *testscheme.Scheme) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate_ConflictingItemList validates an instance of ConflictingItemList according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ConflictingItemList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj ConflictingItemList) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *DualItem) bool { return item.ID == "target" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *DualItem) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ConflictingItems[id=target] from typedef")
|
||||
})...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DualItemList validates an instance of DualItemList according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DualItemList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj DualItemList) (errs field.ErrorList) {
|
||||
@@ -125,5 +135,21 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return
|
||||
}(fldPath.Child("dualItems"), obj.DualItems, safe.Field(oldObj, func(oldObj *Struct) DualItemList { return oldObj.DualItems }))...)
|
||||
|
||||
// field Struct.ConflictingItems
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj ConflictingItemList) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *DualItem) bool { return item.ID == "target" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *DualItem) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ConflictingItems[id=target] from field")
|
||||
})...)
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_ConflictingItemList(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("conflictingItems"), obj.ConflictingItems, safe.Field(oldObj, func(oldObj *Struct) ConflictingItemList { return oldObj.ConflictingItems }))...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, validate.ImmutableByCompare)...)
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
|
||||
return
|
||||
}(fldPath.Child("sliceSetComparableField"), obj.SliceSetComparableField, safe.Field(oldObj, func(oldObj *ImmutableStruct) []ComparableStruct { return oldObj.SliceSetComparableField }))...)
|
||||
@@ -109,7 +109,7 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, validate.ImmutableByReflect)...)
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual)...)
|
||||
return
|
||||
}(fldPath.Child("sliceSetNonComparableField"), obj.SliceSetNonComparableField, safe.Field(oldObj, func(oldObj *ImmutableStruct) []NonComparableStruct { return oldObj.SliceSetNonComparableField }))...)
|
||||
@@ -135,7 +135,7 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, validate.ImmutableByCompare)...)
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
|
||||
return
|
||||
}(fldPath.Child("sliceSetPrimitiveField"), obj.SliceSetPrimitiveField, safe.Field(oldObj, func(oldObj *ImmutableStruct) []int { return oldObj.SliceSetPrimitiveField }))...)
|
||||
@@ -149,7 +149,7 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, validate.ImmutableByReflect)...)
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual)...)
|
||||
return
|
||||
}(fldPath.Child("sliceSetFalselyComparableField"), obj.SliceSetFalselyComparableField, safe.Field(oldObj, func(oldObj *ImmutableStruct) []FalselyComparableStruct { return oldObj.SliceSetFalselyComparableField }))...)
|
||||
@@ -170,7 +170,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
|
||||
return
|
||||
}(fldPath.Child("sliceStringField"), obj.SliceStringField, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.SliceStringField }))...)
|
||||
@@ -183,7 +183,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
|
||||
return
|
||||
}(fldPath.Child("sliceIntField"), obj.SliceIntField, safe.Field(oldObj, func(oldObj *Struct) []int { return oldObj.SliceIntField }))...)
|
||||
@@ -196,7 +196,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
|
||||
return
|
||||
}(fldPath.Child("sliceComparableField"), obj.SliceComparableField, safe.Field(oldObj, func(oldObj *Struct) []ComparableStruct { return oldObj.SliceComparableField }))...)
|
||||
@@ -209,7 +209,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual)...)
|
||||
return
|
||||
}(fldPath.Child("sliceNonComparableField"), obj.SliceNonComparableField, safe.Field(oldObj, func(oldObj *Struct) []NonComparableStruct { return oldObj.SliceNonComparableField }))...)
|
||||
@@ -222,7 +222,7 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
// listType=set requires unique values
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual)...)
|
||||
return
|
||||
}(fldPath.Child("sliceFalselyComparableField"), obj.SliceFalselyComparableField, safe.Field(oldObj, func(oldObj *Struct) []FalselyComparableStruct { return oldObj.SliceFalselyComparableField }))...)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:validation-gen=TypeMeta
|
||||
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
|
||||
|
||||
// This is a test package.
|
||||
package unique
|
||||
|
||||
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
|
||||
|
||||
var localSchemeBuilder = testscheme.New()
|
||||
|
||||
type Struct struct {
|
||||
TypeMeta int
|
||||
|
||||
// Basic unique=set on primitive slice
|
||||
// +k8s:listType=atomic
|
||||
// +k8s:unique=set
|
||||
PrimitiveListUniqueSet []string `json:"primitiveListUniqueSet"`
|
||||
|
||||
// unique=map with multiple keys
|
||||
// +k8s:listType=atomic
|
||||
// +k8s:unique=map
|
||||
// +k8s:listMapKey=key1
|
||||
// +k8s:listMapKey=key2
|
||||
SliceMapFieldWithMultipleKeys []ItemWithMultipleKeys `json:"sliceMapFieldWithMultipleKeys"`
|
||||
|
||||
// atomic + unique=set combination
|
||||
// +k8s:listType=atomic
|
||||
// +k8s:unique=set
|
||||
AtomicListUniqueSet []Item `json:"atomicListUniqueSet"`
|
||||
|
||||
// atomic + unique=map combination
|
||||
// +k8s:listType=atomic
|
||||
// +k8s:unique=map
|
||||
// +k8s:listMapKey=key
|
||||
AtomicListUniqueMap []Item `json:"atomicListUniqueMap"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Key string `json:"key"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type ItemWithMultipleKeys struct {
|
||||
Key1 string `json:"key1"`
|
||||
Key2 string `json:"key2"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
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 unique
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func TestUnique(t *testing.T) {
|
||||
st := localSchemeBuilder.Test(t)
|
||||
|
||||
// Test empty struct (should be valid)
|
||||
st.Value(&Struct{}).ExpectValid()
|
||||
|
||||
// Test valid cases with no duplicates
|
||||
st.Value(&Struct{
|
||||
PrimitiveListUniqueSet: []string{"aaa", "bbb"},
|
||||
SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{
|
||||
{Key1: "a", Key2: "x", Data: "first"},
|
||||
{Key1: "a", Key2: "y", Data: "second"},
|
||||
},
|
||||
AtomicListUniqueSet: []Item{
|
||||
{Key: "key1", Data: "one"},
|
||||
{Key: "key2", Data: "two"},
|
||||
},
|
||||
AtomicListUniqueMap: []Item{
|
||||
{Key: "key1", Data: "one"},
|
||||
{Key: "key2", Data: "two"},
|
||||
},
|
||||
}).ExpectValid()
|
||||
|
||||
// Test empty lists
|
||||
st.Value(&Struct{
|
||||
PrimitiveListUniqueSet: []string{},
|
||||
SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{},
|
||||
AtomicListUniqueSet: []Item{},
|
||||
AtomicListUniqueMap: []Item{},
|
||||
}).ExpectValid()
|
||||
|
||||
// Test single element lists
|
||||
st.Value(&Struct{
|
||||
PrimitiveListUniqueSet: []string{"single"},
|
||||
SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{{Key1: "a", Key2: "b", Data: "one"}},
|
||||
AtomicListUniqueSet: []Item{{Key: "single", Data: "one"}},
|
||||
AtomicListUniqueMap: []Item{{Key: "single", Data: "one"}},
|
||||
}).ExpectValid()
|
||||
|
||||
// Test duplicate values (should fail validation)
|
||||
st.Value(&Struct{
|
||||
PrimitiveListUniqueSet: []string{"aaa", "bbb", "ccc", "ccc", "bbb", "aaa"},
|
||||
SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{
|
||||
{Key1: "a", Key2: "x", Data: "first"},
|
||||
{Key1: "a", Key2: "y", Data: "second"},
|
||||
{Key1: "a", Key2: "x", Data: "third"},
|
||||
},
|
||||
AtomicListUniqueSet: []Item{
|
||||
{Key: "key1", Data: "one"},
|
||||
{Key: "key2", Data: "two"},
|
||||
{Key: "key1", Data: "one"},
|
||||
},
|
||||
AtomicListUniqueMap: []Item{
|
||||
{Key: "key1", Data: "one"},
|
||||
{Key: "key2", Data: "two"},
|
||||
{Key: "key1", Data: "three"},
|
||||
},
|
||||
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(3), nil),
|
||||
field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(4), nil),
|
||||
field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(5), nil),
|
||||
field.Duplicate(field.NewPath("sliceMapFieldWithMultipleKeys").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("atomicListUniqueSet").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("atomicListUniqueMap").Index(2), nil),
|
||||
})
|
||||
|
||||
// Test with zero values and empty strings
|
||||
st.Value(&Struct{
|
||||
PrimitiveListUniqueSet: []string{"", "a", ""},
|
||||
AtomicListUniqueMap: []Item{
|
||||
{Key: "", Data: "one"},
|
||||
{Key: "a", Data: "two"},
|
||||
{Key: "", Data: "three"},
|
||||
},
|
||||
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("atomicListUniqueMap").Index(2), nil),
|
||||
})
|
||||
}
|
||||
|
||||
func TestRatcheting(t *testing.T) {
|
||||
st := localSchemeBuilder.Test(t)
|
||||
|
||||
struct1 := Struct{
|
||||
PrimitiveListUniqueSet: []string{"aaa", "bbb"},
|
||||
SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{
|
||||
{Key1: "a", Key2: "x", Data: "first"},
|
||||
{Key1: "a", Key2: "y", Data: "second"},
|
||||
},
|
||||
AtomicListUniqueSet: []Item{
|
||||
{Key: "key1", Data: "one"},
|
||||
{Key: "key2", Data: "two"},
|
||||
},
|
||||
AtomicListUniqueMap: []Item{
|
||||
{Key: "key1", Data: "one"},
|
||||
{Key: "key2", Data: "two"},
|
||||
},
|
||||
}
|
||||
|
||||
// Same data, different order.
|
||||
struct2 := Struct{
|
||||
PrimitiveListUniqueSet: []string{"bbb", "aaa"},
|
||||
SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{
|
||||
{Key1: "a", Key2: "y", Data: "second"},
|
||||
{Key1: "a", Key2: "x", Data: "first"},
|
||||
},
|
||||
AtomicListUniqueSet: []Item{
|
||||
{Key: "key2", Data: "two"},
|
||||
{Key: "key1", Data: "one"},
|
||||
},
|
||||
AtomicListUniqueMap: []Item{
|
||||
{Key: "key2", Data: "two"},
|
||||
{Key: "key1", Data: "one"},
|
||||
},
|
||||
}
|
||||
|
||||
// Test that reordering doesn't trigger validation errors
|
||||
st.Value(&struct1).OldValue(&struct2).ExpectValid()
|
||||
st.Value(&struct2).OldValue(&struct1).ExpectValid()
|
||||
|
||||
// Test that the same data is considered valid regardless of order
|
||||
st.Value(&struct1).ExpectValid()
|
||||
st.Value(&struct2).ExpectValid()
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
//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 unique
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
|
||||
equality "k8s.io/apimachinery/pkg/api/equality"
|
||||
operation "k8s.io/apimachinery/pkg/api/operation"
|
||||
safe "k8s.io/apimachinery/pkg/api/safe"
|
||||
validate "k8s.io/apimachinery/pkg/api/validate"
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme"
|
||||
)
|
||||
|
||||
func init() { localSchemeBuilder.Register(RegisterValidations) }
|
||||
|
||||
// RegisterValidations adds validation functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterValidations(scheme *testscheme.Scheme) error {
|
||||
// type Struct
|
||||
scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
|
||||
switch op.Request.SubresourcePath() {
|
||||
case "/":
|
||||
return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj))
|
||||
}
|
||||
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate_Struct validates an instance of Struct according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) {
|
||||
// field Struct.TypeMeta has no validation
|
||||
|
||||
// field Struct.PrimitiveListUniqueSet
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if 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)...)
|
||||
return
|
||||
}(fldPath.Child("primitiveListUniqueSet"), obj.PrimitiveListUniqueSet, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.PrimitiveListUniqueSet }))...)
|
||||
|
||||
// field Struct.SliceMapFieldWithMultipleKeys
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []ItemWithMultipleKeys) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if 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 ItemWithMultipleKeys, b ItemWithMultipleKeys) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 })...)
|
||||
return
|
||||
}(fldPath.Child("sliceMapFieldWithMultipleKeys"), obj.SliceMapFieldWithMultipleKeys, safe.Field(oldObj, func(oldObj *Struct) []ItemWithMultipleKeys { return oldObj.SliceMapFieldWithMultipleKeys }))...)
|
||||
|
||||
// field Struct.AtomicListUniqueSet
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []Item) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if 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)...)
|
||||
return
|
||||
}(fldPath.Child("atomicListUniqueSet"), obj.AtomicListUniqueSet, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.AtomicListUniqueSet }))...)
|
||||
|
||||
// field Struct.AtomicListUniqueMap
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []Item) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if 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 Item, b Item) bool { return a.Key == b.Key })...)
|
||||
return
|
||||
}(fldPath.Child("atomicListUniqueMap"), obj.AtomicListUniqueMap, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.AtomicListUniqueMap }))...)
|
||||
|
||||
return errs
|
||||
}
|
||||
Reference in New Issue
Block a user