mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-09 08:55:55 -04:00
Merge pull request #134279 from yongruilin/master_customunique
feat(validation-gen): Introduce k8s:customUnique to control listmap uniqueness
This commit is contained in:
commit
0bdf1f89c3
29 changed files with 284 additions and 97 deletions
|
|
@ -113,6 +113,7 @@ func Validate_CertificateSigningRequestStatus(ctx context.Context, op operation.
|
|||
// field certificatesv1.CertificateSigningRequestStatus.Conditions
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []certificatesv1.CertificateSigningRequestCondition) (errs field.ErrorList) {
|
||||
// Uniqueness validation is implemented via custom, handwritten validation
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ func Validate_CertificateSigningRequestStatus(ctx context.Context, op operation.
|
|||
// field certificatesv1beta1.CertificateSigningRequestStatus.Conditions
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []certificatesv1beta1.CertificateSigningRequestCondition) (errs field.ErrorList) {
|
||||
// Uniqueness validation is implemented via custom, handwritten validation
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -177,6 +177,11 @@ func testValidateUpdateForDeclarative(t *testing.T, apiVersion string) {
|
|||
),
|
||||
subresources: []string{"/approval"}, // Can only modify Approved and Denied conditions on /approval subresource
|
||||
},
|
||||
"ratcheting: allow existing duplicate types - valid": {
|
||||
old: makeValidCSR(withApprovedCondition(), withApprovedCondition(), withDeniedCondition(), withDeniedCondition()),
|
||||
update: makeValidCSR(withDeniedCondition(), withDeniedCondition(), withApprovedCondition(), withApprovedCondition()),
|
||||
subresources: []string{"/status"},
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range testCases {
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ message CertificateSigningRequestStatus {
|
|||
// +optional
|
||||
// +k8s:listType=map
|
||||
// +k8s:listMapKey=type
|
||||
// +k8s:customUnique
|
||||
// +k8s:optional
|
||||
// +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember
|
||||
// +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ type CertificateSigningRequestStatus struct {
|
|||
// +optional
|
||||
// +k8s:listType=map
|
||||
// +k8s:listMapKey=type
|
||||
// +k8s:customUnique
|
||||
// +k8s:optional
|
||||
// +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember
|
||||
// +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ message CertificateSigningRequestStatus {
|
|||
// +optional
|
||||
// +k8s:listType=map
|
||||
// +k8s:listMapKey=type
|
||||
// +k8s:customUnique
|
||||
// +k8s:optional
|
||||
// +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember
|
||||
// +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ type CertificateSigningRequestStatus struct {
|
|||
// +optional
|
||||
// +k8s:listType=map
|
||||
// +k8s:listMapKey=type
|
||||
// +k8s:customUnique
|
||||
// +k8s:optional
|
||||
// +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember
|
||||
// +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember
|
||||
|
|
|
|||
|
|
@ -350,6 +350,8 @@ func Validate_T1(ctx context.Context, op operation.Operation, fldPath *field.Pat
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a other.StructType, b other.StructType) bool { return a.StringField == b.StringField }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *other.StructType) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.SliceOfOtherStruct values")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a other.StructType, b other.StructType) bool { return a.StringField == b.StringField })...)
|
||||
return
|
||||
}(fldPath.Child("listMapOfOtherStruct"), obj.ListMapOfOtherStruct, safe.Field(oldObj, func(oldObj *T1) []other.StructType { return oldObj.ListMapOfOtherStruct }))...)
|
||||
|
||||
|
|
|
|||
|
|
@ -176,6 +176,8 @@ func Validate_StructSlice(ctx context.Context, op operation.Operation, fldPath *
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a ComparableStructWithKey, b ComparableStructWithKey) bool { return a.Key == b.Key }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ComparableStructWithKey) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapSliceComparableField[*]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a ComparableStructWithKey, b ComparableStructWithKey) bool { return a.Key == b.Key })...)
|
||||
return
|
||||
}(fldPath.Child("mapSliceComparableField"), obj.MapSliceComparableField, safe.Field(oldObj, func(oldObj *StructSlice) []ComparableStructWithKey { return oldObj.MapSliceComparableField }))...)
|
||||
|
||||
|
|
@ -190,6 +192,8 @@ func Validate_StructSlice(ctx context.Context, op operation.Operation, fldPath *
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a NonComparableStructWithKey, b NonComparableStructWithKey) bool { return a.Key == b.Key }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStructWithKey) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapSliceNonComparableField[*]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a NonComparableStructWithKey, b NonComparableStructWithKey) bool { return a.Key == b.Key })...)
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a NonComparableStructWithKey, b NonComparableStructWithKey) bool { return a.Key == b.Key }, validate.SemanticDeepEqual, Validate_NonComparableStructWithKey)...)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key1 == "b" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList {
|
||||
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *Item) *string { return &o.StringField }, validate.DirectEqualPtr, validate.ImmutableByCompare)
|
||||
})...)
|
||||
// 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.Key1 == b.Key1 })...)
|
||||
return
|
||||
}(fldPath.Child("listField"), obj.ListField, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.ListField }))...)
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.StringKey == "target" && item.IntKey == 42 && item.BoolKey == true }, 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 Items[stringKey=target,intKey=42,boolKey=true]")
|
||||
})...)
|
||||
// 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.StringKey == b.StringKey && a.IntKey == b.IntKey && a.BoolKey == b.BoolKey
|
||||
})...)
|
||||
return
|
||||
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.Items }))...)
|
||||
|
||||
|
|
@ -79,6 +83,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.StringKey == "target" && item.IntKey == 42 && item.BoolKey == true }, 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 OutOfOrder[boolKey=42,stringKey=target,intKey=42]")
|
||||
})...)
|
||||
// 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.StringKey == b.StringKey && a.IntKey == b.IntKey && a.BoolKey == b.BoolKey
|
||||
})...)
|
||||
return
|
||||
}(fldPath.Child("outOfOrder"), obj.OutOfOrder, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.OutOfOrder }))...)
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
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 Items[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("items"), obj.Items, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.Items }))...)
|
||||
|
||||
|
|
@ -87,6 +89,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *IntKeyItem) bool { return item.IntField == 42 }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *IntKeyItem) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item IntKeyItems[intField=42]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a IntKeyItem, b IntKeyItem) bool { return a.IntField == b.IntField })...)
|
||||
return
|
||||
}(fldPath.Child("intKeyItems"), obj.IntKeyItems, safe.Field(oldObj, func(oldObj *Struct) []IntKeyItem { return oldObj.IntKeyItems }))...)
|
||||
|
||||
|
|
@ -101,6 +105,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *BoolKeyItem) bool { return item.BoolField == true }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *BoolKeyItem) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item BoolKeyItems[boolField=true]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a BoolKeyItem, b BoolKeyItem) bool { return a.BoolField == b.BoolField })...)
|
||||
return
|
||||
}(fldPath.Child("boolKeyItems"), obj.BoolKeyItems, safe.Field(oldObj, func(oldObj *Struct) []BoolKeyItem { return oldObj.BoolKeyItems }))...)
|
||||
|
||||
|
|
@ -115,6 +121,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *TypedefItem) bool { return item.ID == "typedef-target" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *TypedefItem) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item TypedefItems[id=typedef-target]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a TypedefItem, b TypedefItem) bool { return a.ID == b.ID })...)
|
||||
return
|
||||
}(fldPath.Child("typedefItems"), obj.TypedefItems, safe.Field(oldObj, func(oldObj *Struct) TypedefItemList { return oldObj.TypedefItems }))...)
|
||||
|
||||
|
|
@ -153,6 +161,8 @@ func Validate_StructWithNestedTypedef(ctx context.Context, op operation.Operatio
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *NestedTypedefItem) bool { return item.Key == "nested-target" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NestedTypedefItem) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item NestedItems[key=nested-target]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a NestedTypedefItem, b NestedTypedefItem) bool { return a.Key == b.Key })...)
|
||||
return
|
||||
}(fldPath.Child("nestedItems"), obj.NestedItems, safe.Field(oldObj, func(oldObj *StructWithNestedTypedef) []NestedTypedefItem { return oldObj.NestedItems }))...)
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item Items[key=target].stringField")
|
||||
})
|
||||
})...)
|
||||
// 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("items"), obj.Items, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.Items }))...)
|
||||
|
||||
|
|
@ -83,6 +85,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
return validate.NEQ(ctx, op, fldPath, obj, oldObj, "forbidden")
|
||||
})
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a RatchetItem, b RatchetItem) bool { return a.Key == b.Key })...)
|
||||
return
|
||||
}(fldPath.Child("ratchetItems"), obj.RatchetItems, safe.Field(oldObj, func(oldObj *Struct) []RatchetItem { return oldObj.RatchetItems }))...)
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ func Validate_ConflictingItemList(ctx context.Context, op operation.Operation, f
|
|||
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")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a DualItem, b DualItem) bool { return a.ID == b.ID })...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
|
@ -65,6 +67,8 @@ func Validate_DualItemList(ctx context.Context, op operation.Operation, fldPath
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *DualItem) bool { return item.ID == "typedef-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 DualItems[id=typedef-target] from typedef")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a DualItem, b DualItem) bool { return a.ID == b.ID })...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
|
@ -76,6 +80,8 @@ func Validate_ItemList(ctx context.Context, op operation.Operation, fldPath *fie
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key == "validated" }, 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 ItemList[key=validated]")
|
||||
})...)
|
||||
// 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 errs
|
||||
}
|
||||
|
|
@ -86,6 +92,8 @@ func Validate_ItemListAlias(ctx context.Context, op operation.Operation, fldPath
|
|||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key == "aliased" }, 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 ItemListAlias[key=aliased]")
|
||||
})...)
|
||||
// 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 errs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
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 Task, b Task) bool { return a.Name == b.Name })...)
|
||||
errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_union_simple_Struct_tasks_, func(list []Task) bool {
|
||||
for i := range list {
|
||||
if list[i].Name == "failed" {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tag
|
|||
// Validate_TaskList validates an instance of TaskList according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_TaskList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj TaskList) (errs field.ErrorList) {
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a Task, b Task) bool { return a.Name == b.Name })...)
|
||||
errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_union_typedef_TaskList_, func(list TaskList) bool {
|
||||
for i := range list {
|
||||
if list[i].Name == "failed" {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
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 Task, b Task) bool { return a.Name == b.Name })...)
|
||||
errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_zerorooneof_simple_Struct_tasks_, func(list []Task) bool {
|
||||
for i := range list {
|
||||
if list[i].Name == "failed" {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tes
|
|||
// Validate_TaskList validates an instance of TaskList according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_TaskList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj TaskList) (errs field.ErrorList) {
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a Task, b Task) bool { return a.Name == b.Name })...)
|
||||
errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_zerorooneof_typedef_TaskList_, func(list TaskList) bool {
|
||||
for i := range list {
|
||||
if list[i].Name == "failed" {
|
||||
|
|
|
|||
|
|
@ -23,33 +23,29 @@ import (
|
|||
)
|
||||
|
||||
func TestUniqueness(t *testing.T) {
|
||||
// TODO: enable this once we have a way to either opt-out from this validation
|
||||
// or settle the decision on how to handle the ratcheting cases.
|
||||
/*
|
||||
st := localSchemeBuilder.Test(t)
|
||||
st := localSchemeBuilder.Test(t)
|
||||
|
||||
st.Value(&Struct{
|
||||
ListField: []OtherStruct{
|
||||
{"key1", 1, "one"}, // unique
|
||||
{"key2", 2, "two"}, // dup
|
||||
{"key2", 2, "two"},
|
||||
},
|
||||
ListTypedefField: []OtherTypedefStruct{
|
||||
{"key1", 1, "one"}, // unique
|
||||
{"key2", 2, "two"}, // dup
|
||||
{"key2", 2, "two"},
|
||||
},
|
||||
TypedefField: ListType{
|
||||
{"key1", 1, "one"}, // unique
|
||||
{"key2", 2, "two"}, // dup
|
||||
{"key2", 2, "two"},
|
||||
},
|
||||
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.Duplicate(field.NewPath("listField").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("listTypedefField").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("typedefField").Index(2), nil),
|
||||
})
|
||||
*/
|
||||
st.Value(&Struct{
|
||||
ListField: []OtherStruct{
|
||||
{"key1", 1, "one"}, // unique
|
||||
{"key2", 2, "two"}, // dup
|
||||
{"key2", 2, "two"},
|
||||
},
|
||||
ListTypedefField: []OtherTypedefStruct{
|
||||
{"key1", 1, "one"}, // unique
|
||||
{"key2", 2, "two"}, // dup
|
||||
{"key2", 2, "two"},
|
||||
},
|
||||
TypedefField: ListType{
|
||||
{"key1", 1, "one"}, // unique
|
||||
{"key2", 2, "two"}, // dup
|
||||
{"key2", 2, "two"},
|
||||
},
|
||||
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.Duplicate(field.NewPath("listField").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("listTypedefField").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("typedefField").Index(2), nil),
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateCorrelation(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,17 @@ func RegisterValidations(scheme *testscheme.Scheme) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Validate_ListType validates an instance of ListType according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ListType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj ListType) (errs field.ErrorList) {
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
})...)
|
||||
|
||||
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) {
|
||||
|
|
@ -65,6 +76,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
}, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
})...)
|
||||
return
|
||||
}(fldPath.Child("listField"), obj.ListField, safe.Field(oldObj, func(oldObj *Struct) []OtherStruct { return oldObj.ListField }))...)
|
||||
|
||||
|
|
@ -79,6 +94,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
}, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
})...)
|
||||
return
|
||||
}(fldPath.Child("listTypedefField"), obj.ListTypedefField, safe.Field(oldObj, func(oldObj *Struct) []OtherTypedefStruct { return oldObj.ListTypedefField }))...)
|
||||
|
||||
|
|
@ -93,6 +112,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
}, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_ListType(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("typedefField"), obj.TypedefField, safe.Field(oldObj, func(oldObj *Struct) ListType { return oldObj.TypedefField }))...)
|
||||
|
||||
|
|
@ -109,6 +130,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
}, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListComparableField[*]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
})...)
|
||||
return
|
||||
}(fldPath.Child("listComparableField"), obj.ListComparableField, safe.Field(oldObj, func(oldObj *Struct) []OtherStruct { return oldObj.ListComparableField }))...)
|
||||
|
||||
|
|
@ -125,6 +150,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
}, validate.SemanticDeepEqual, 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 Struct.ListNonComparableField[*]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a NonComparableStruct, b NonComparableStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
})...)
|
||||
return
|
||||
}(fldPath.Child("listNonComparableField"), obj.ListNonComparableField, safe.Field(oldObj, func(oldObj *Struct) []NonComparableStruct { return oldObj.ListNonComparableField }))...)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,33 +24,29 @@ import (
|
|||
)
|
||||
|
||||
func TestUniqueness(t *testing.T) {
|
||||
// TODO: enable this once we have a way to either opt-out from this validation
|
||||
// or settle the decision on how to handle the ratcheting cases.
|
||||
/*
|
||||
st := localSchemeBuilder.Test(t)
|
||||
st := localSchemeBuilder.Test(t)
|
||||
|
||||
st.Value(&Struct{
|
||||
ListField: []OtherStruct{
|
||||
{"key1", "one"},
|
||||
{"key2", "two"},
|
||||
{"key2", "two"},
|
||||
},
|
||||
ListTypedefField: []OtherTypedefStruct{
|
||||
{"key1", "one"},
|
||||
{"key2", "two"},
|
||||
{"key2", "two"},
|
||||
},
|
||||
TypedefField: ListType{
|
||||
{"key1", "one"},
|
||||
{"key2", "two"},
|
||||
{"key2", "two"},
|
||||
},
|
||||
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.Duplicate(field.NewPath("listField").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("listTypedefField").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("typedefField").Index(2), nil),
|
||||
})
|
||||
*/
|
||||
st.Value(&Struct{
|
||||
ListField: []OtherStruct{
|
||||
{"key1", "one"},
|
||||
{"key2", "two"},
|
||||
{"key2", "two"},
|
||||
},
|
||||
ListTypedefField: []OtherTypedefStruct{
|
||||
{"key1", "one"},
|
||||
{"key2", "two"},
|
||||
{"key2", "two"},
|
||||
},
|
||||
TypedefField: ListType{
|
||||
{"key1", "one"},
|
||||
{"key2", "two"},
|
||||
{"key2", "two"},
|
||||
},
|
||||
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.Duplicate(field.NewPath("listField").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("listTypedefField").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("typedefField").Index(2), nil),
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateCorrelation(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,15 @@ func RegisterValidations(scheme *testscheme.Scheme) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Validate_ListType validates an instance of ListType according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ListType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj ListType) (errs field.ErrorList) {
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField })...)
|
||||
|
||||
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) {
|
||||
|
|
@ -63,6 +72,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField })...)
|
||||
return
|
||||
}(fldPath.Child("listField"), obj.ListField, safe.Field(oldObj, func(oldObj *Struct) []OtherStruct { return oldObj.ListField }))...)
|
||||
|
||||
|
|
@ -75,6 +86,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool { return a.KeyField == b.KeyField })...)
|
||||
return
|
||||
}(fldPath.Child("listTypedefField"), obj.ListTypedefField, safe.Field(oldObj, func(oldObj *Struct) []OtherTypedefStruct { return oldObj.ListTypedefField }))...)
|
||||
|
||||
|
|
@ -87,6 +100,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_ListType(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("typedefField"), obj.TypedefField, safe.Field(oldObj, func(oldObj *Struct) ListType { return oldObj.TypedefField }))...)
|
||||
|
||||
|
|
@ -101,6 +116,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListComparableField[*]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField })...)
|
||||
return
|
||||
}(fldPath.Child("listComparableField"), obj.ListComparableField, safe.Field(oldObj, func(oldObj *Struct) []OtherStruct { return oldObj.ListComparableField }))...)
|
||||
|
||||
|
|
@ -115,6 +132,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a NonComparableStruct, b NonComparableStruct) bool { return a.KeyField == b.KeyField }, validate.SemanticDeepEqual, 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 Struct.ListNonComparableField[*]")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a NonComparableStruct, b NonComparableStruct) bool { return a.KeyField == b.KeyField })...)
|
||||
return
|
||||
}(fldPath.Child("listNonComparableField"), obj.ListNonComparableField, safe.Field(oldObj, func(oldObj *Struct) []NonComparableStruct { return oldObj.ListNonComparableField }))...)
|
||||
|
||||
|
|
|
|||
|
|
@ -218,6 +218,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.StringField == b.StringField }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListMapOfStructField vals")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.StringField == b.StringField })...)
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.StringField == b.StringField }, validate.DirectEqual, Validate_OtherStruct)...)
|
||||
return
|
||||
|
|
@ -235,6 +237,8 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.StringField == b.StringField }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList {
|
||||
return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListMapOfOpaqueStructField vals")
|
||||
})...)
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.StringField == b.StringField })...)
|
||||
return
|
||||
}(fldPath.Child("listMapOfOpaqueStructField"), obj.ListMapOfOpaqueStructField, safe.Field(oldObj, func(oldObj *Struct) []OtherStruct { return oldObj.ListMapOfOpaqueStructField }))...)
|
||||
|
||||
|
|
|
|||
|
|
@ -57,11 +57,6 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
|
||||
// optional value-type fields with zero-value defaults are purely documentation
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
return
|
||||
}(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }))...)
|
||||
|
||||
|
|
@ -85,11 +80,6 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *int) (errs field.ErrorList) {
|
||||
// optional value-type fields with zero-value defaults are purely documentation
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
return
|
||||
}(fldPath.Child("intField"), &obj.IntField, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.IntField }))...)
|
||||
|
||||
|
|
@ -113,11 +103,6 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *bool) (errs field.ErrorList) {
|
||||
// optional value-type fields with zero-value defaults are purely documentation
|
||||
// don't revalidate unchanged data
|
||||
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
return
|
||||
}(fldPath.Child("boolField"), &obj.BoolField, safe.Field(oldObj, func(oldObj *Struct) *bool { return &oldObj.BoolField }))...)
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,17 @@ type Struct struct {
|
|||
// +k8s:unique=map
|
||||
// +k8s:listMapKey=key
|
||||
AtomicListUniqueMap []Item `json:"atomicListUniqueMap"`
|
||||
|
||||
// customUnique with listType=set
|
||||
// +k8s:listType=set
|
||||
// +k8s:customUnique
|
||||
CustomUniqueListWithTypeSet []string `json:"customUniqueListWithTypeSet"`
|
||||
|
||||
// customUnique with listType=map
|
||||
// +k8s:listType=map
|
||||
// +k8s:listMapKey=key
|
||||
// +k8s:customUnique
|
||||
CustomUniqueListWithTypeMap []Item `json:"customUniqueListWithTypeMap"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ func TestUnique(t *testing.T) {
|
|||
{Key: "key1", Data: "one"},
|
||||
{Key: "key2", Data: "two"},
|
||||
},
|
||||
CustomUniqueListWithTypeSet: []string{"a", "b", "a"},
|
||||
CustomUniqueListWithTypeMap: []Item{{Key: "a"}, {Key: "b"}, {Key: "a"}},
|
||||
}).ExpectValid()
|
||||
|
||||
// Test empty lists
|
||||
|
|
@ -51,6 +53,8 @@ func TestUnique(t *testing.T) {
|
|||
SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{},
|
||||
AtomicListUniqueSet: []Item{},
|
||||
AtomicListUniqueMap: []Item{},
|
||||
CustomUniqueListWithTypeSet: []string{},
|
||||
CustomUniqueListWithTypeMap: []Item{},
|
||||
}).ExpectValid()
|
||||
|
||||
// Test single element lists
|
||||
|
|
@ -59,6 +63,8 @@ func TestUnique(t *testing.T) {
|
|||
SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{{Key1: "a", Key2: "b", Data: "one"}},
|
||||
AtomicListUniqueSet: []Item{{Key: "single", Data: "one"}},
|
||||
AtomicListUniqueMap: []Item{{Key: "single", Data: "one"}},
|
||||
CustomUniqueListWithTypeSet: []string{"single"},
|
||||
CustomUniqueListWithTypeMap: []Item{{Key: "single"}},
|
||||
}).ExpectValid()
|
||||
|
||||
// Test duplicate values (should fail validation)
|
||||
|
|
@ -79,6 +85,8 @@ func TestUnique(t *testing.T) {
|
|||
{Key: "key2", Data: "two"},
|
||||
{Key: "key1", Data: "three"},
|
||||
},
|
||||
CustomUniqueListWithTypeSet: []string{"a", "b", "a"},
|
||||
CustomUniqueListWithTypeMap: []Item{{Key: "a"}, {Key: "b"}, {Key: "a"}},
|
||||
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(3), nil),
|
||||
field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(4), nil),
|
||||
|
|
@ -96,6 +104,8 @@ func TestUnique(t *testing.T) {
|
|||
{Key: "a", Data: "two"},
|
||||
{Key: "", Data: "three"},
|
||||
},
|
||||
CustomUniqueListWithTypeSet: []string{"", "a", ""},
|
||||
CustomUniqueListWithTypeMap: []Item{{Key: ""}, {Key: "a"}, {Key: ""}},
|
||||
}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{
|
||||
field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(2), nil),
|
||||
field.Duplicate(field.NewPath("atomicListUniqueMap").Index(2), nil),
|
||||
|
|
@ -119,6 +129,8 @@ func TestRatcheting(t *testing.T) {
|
|||
{Key: "key1", Data: "one"},
|
||||
{Key: "key2", Data: "two"},
|
||||
},
|
||||
CustomUniqueListWithTypeSet: []string{"a", "b", "a"},
|
||||
CustomUniqueListWithTypeMap: []Item{{Key: "a"}, {Key: "b"}, {Key: "a"}},
|
||||
}
|
||||
|
||||
// Same data, different order.
|
||||
|
|
@ -136,6 +148,8 @@ func TestRatcheting(t *testing.T) {
|
|||
{Key: "key2", Data: "two"},
|
||||
{Key: "key1", Data: "one"},
|
||||
},
|
||||
CustomUniqueListWithTypeSet: []string{"a", "a", "b"},
|
||||
CustomUniqueListWithTypeMap: []Item{{Key: "a"}, {Key: "a"}, {Key: "b"}},
|
||||
}
|
||||
|
||||
// Test that reordering doesn't trigger validation errors
|
||||
|
|
|
|||
|
|
@ -106,5 +106,19 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
|||
return
|
||||
}(fldPath.Child("atomicListUniqueMap"), obj.AtomicListUniqueMap, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.AtomicListUniqueMap }))...)
|
||||
|
||||
// field Struct.CustomUniqueListWithTypeSet
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string) (errs field.ErrorList) {
|
||||
// Uniqueness validation is implemented via custom, handwritten validation
|
||||
return
|
||||
}(fldPath.Child("customUniqueListWithTypeSet"), obj.CustomUniqueListWithTypeSet, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.CustomUniqueListWithTypeSet }))...)
|
||||
|
||||
// field Struct.CustomUniqueListWithTypeMap
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []Item) (errs field.ErrorList) {
|
||||
// Uniqueness validation is implemented via custom, handwritten validation
|
||||
return
|
||||
}(fldPath.Child("customUniqueListWithTypeMap"), obj.CustomUniqueListWithTypeMap, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.CustomUniqueListWithTypeMap }))...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1105,10 +1105,12 @@ func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild
|
|||
fldRatchetingChecked := false
|
||||
if !validations.Empty() {
|
||||
emitComments(validations.Comments, bufsw)
|
||||
emitRatchetingCheck(c, fld.childType, bufsw)
|
||||
fldRatchetingChecked = true
|
||||
bufsw.Do("// call field-attached validations\n", nil)
|
||||
emitCallsToValidators(c, validations.Functions, bufsw)
|
||||
if len(validations.Functions) > 0 {
|
||||
emitRatchetingCheck(c, fld.childType, bufsw)
|
||||
fldRatchetingChecked = true
|
||||
bufsw.Do("// call field-attached validations\n", nil)
|
||||
emitCallsToValidators(c, validations.Functions, bufsw)
|
||||
}
|
||||
}
|
||||
|
||||
// If the node is nil, this must be a type in a package we are not
|
||||
|
|
@ -1132,11 +1134,14 @@ func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild
|
|||
// validations, call its validation function.
|
||||
if validations := fld.fieldValIterations; g.hasValidations(fld.node.elem.node) && !validations.Empty() {
|
||||
emitComments(validations.Comments, bufsw)
|
||||
if !fldRatchetingChecked {
|
||||
emitRatchetingCheck(c, fld.childType, bufsw)
|
||||
fldRatchetingChecked = true
|
||||
if len(validations.Functions) > 0 {
|
||||
if !fldRatchetingChecked {
|
||||
emitRatchetingCheck(c, fld.childType, bufsw)
|
||||
fldRatchetingChecked = true
|
||||
}
|
||||
emitCallsToValidators(c, validations.Functions, bufsw)
|
||||
}
|
||||
emitCallsToValidators(c, validations.Functions, bufsw)
|
||||
|
||||
}
|
||||
// Descend into this field.
|
||||
g.emitValidationForChild(c, fld, bufsw)
|
||||
|
|
@ -1145,21 +1150,25 @@ func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild
|
|||
// validations, call its validation function.
|
||||
if validations := fld.fieldKeyIterations; g.hasValidations(fld.node.key.node) && !validations.Empty() {
|
||||
emitComments(validations.Comments, bufsw)
|
||||
if !fldRatchetingChecked {
|
||||
emitRatchetingCheck(c, fld.childType, bufsw)
|
||||
fldRatchetingChecked = true
|
||||
if len(validations.Functions) > 0 {
|
||||
if !fldRatchetingChecked {
|
||||
emitRatchetingCheck(c, fld.childType, bufsw)
|
||||
fldRatchetingChecked = true
|
||||
}
|
||||
emitCallsToValidators(c, validations.Functions, bufsw)
|
||||
}
|
||||
emitCallsToValidators(c, validations.Functions, bufsw)
|
||||
}
|
||||
// If this field is a map and the value-type has
|
||||
// validations, call its validation function.
|
||||
if validations := fld.fieldValIterations; g.hasValidations(fld.node.elem.node) && !validations.Empty() {
|
||||
emitComments(validations.Comments, bufsw)
|
||||
if !fldRatchetingChecked {
|
||||
emitRatchetingCheck(c, fld.childType, bufsw)
|
||||
fldRatchetingChecked = true
|
||||
if len(validations.Functions) > 0 {
|
||||
if !fldRatchetingChecked {
|
||||
emitRatchetingCheck(c, fld.childType, bufsw)
|
||||
fldRatchetingChecked = true
|
||||
}
|
||||
emitCallsToValidators(c, validations.Functions, bufsw)
|
||||
}
|
||||
emitCallsToValidators(c, validations.Functions, bufsw)
|
||||
}
|
||||
// Descend into this field.
|
||||
g.emitValidationForChild(c, fld, bufsw)
|
||||
|
|
|
|||
|
|
@ -27,9 +27,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
listTypeTagName = "k8s:listType"
|
||||
ListMapKeyTagName = "k8s:listMapKey"
|
||||
uniqueTagName = "k8s:unique"
|
||||
listTypeTagName = "k8s:listType"
|
||||
ListMapKeyTagName = "k8s:listMapKey"
|
||||
uniqueTagName = "k8s:unique"
|
||||
customUniqueTagName = "k8s:customUnique"
|
||||
)
|
||||
|
||||
// globalListMeta is shared between list-related validators.
|
||||
|
|
@ -40,6 +41,7 @@ func init() {
|
|||
RegisterTagValidator(listTypeTagValidator{byPath: globalListMeta})
|
||||
RegisterTagValidator(listMapKeyTagValidator{byPath: globalListMeta})
|
||||
RegisterTagValidator(uniqueTagValidator{byPath: globalListMeta})
|
||||
RegisterTagValidator(customUniqueTagValidator{byPath: globalListMeta})
|
||||
|
||||
// Finish work on the accumulated list metadata.
|
||||
RegisterFieldValidator(listValidator{byPath: globalListMeta})
|
||||
|
|
@ -70,6 +72,10 @@ type listMetadata struct {
|
|||
semantic listSemantic
|
||||
keyFields []string // For semantic == map.
|
||||
keyNames []string // For semantic == map.
|
||||
|
||||
// customUnique indicates that k8s:customUnique is set on this list.
|
||||
// It disables generation of uniqueness validation for this list.
|
||||
customUnique bool
|
||||
}
|
||||
|
||||
// makeListMapMatchFunc generates a function that compares two list-map
|
||||
|
|
@ -137,16 +143,18 @@ func (lttv listTypeTagValidator) GetValidations(context Context, tag codetags.Ta
|
|||
}
|
||||
case "set":
|
||||
lm.ownership = ownershipShared
|
||||
// If uniqueTagValidator has run, lm.semantic will be non-empty and non-atomic.
|
||||
// If uniqueTagValidator has run for `unique=set` or `unique=map`,
|
||||
// lm.semantic will be non-empty and non-atomic.
|
||||
if lm.semantic != "" && lm.semantic != semanticAtomic {
|
||||
return Validations{}, fmt.Errorf("unique tag can only be used with listType=atomic")
|
||||
return Validations{}, fmt.Errorf("unique tag is redundant for listType=%q", tag.Value)
|
||||
}
|
||||
lm.semantic = semanticSet
|
||||
case "map":
|
||||
lm.ownership = ownershipShared
|
||||
// If uniqueTagValidator has run, lm.semantic will be non-empty and non-atomic.
|
||||
// If uniqueTagValidator has run for `unique=set` or `unique=map`,
|
||||
// lm.semantic will be non-empty and non-atomic.
|
||||
if lm.semantic != "" && lm.semantic != semanticAtomic {
|
||||
return Validations{}, fmt.Errorf("unique tag can only be used with listType=atomic")
|
||||
return Validations{}, fmt.Errorf("unique tag is redundant for listType=%q", tag.Value)
|
||||
}
|
||||
if util.NativeType(t.Elem).Kind != types.Struct {
|
||||
return Validations{}, fmt.Errorf("only lists of structs can be list-maps")
|
||||
|
|
@ -267,7 +275,7 @@ func (utv uniqueTagValidator) GetValidations(context Context, tag codetags.Tag)
|
|||
|
||||
// If listType has already run and set a non-atomic ownership, this is an error.
|
||||
if lm.ownership != "" && lm.ownership != ownershipSingle {
|
||||
return Validations{}, fmt.Errorf("unique tag can only be used with listType=atomic")
|
||||
return Validations{}, fmt.Errorf("unique tag may not be used with listType=set or listType=map")
|
||||
}
|
||||
|
||||
if lm.semantic != "" && lm.semantic != semanticAtomic {
|
||||
|
|
@ -306,6 +314,50 @@ func (utv uniqueTagValidator) Docs() TagDoc {
|
|||
return doc
|
||||
}
|
||||
|
||||
type customUniqueTagValidator struct {
|
||||
byPath map[string]*listMetadata
|
||||
}
|
||||
|
||||
func (customUniqueTagValidator) Init(Config) {}
|
||||
|
||||
func (customUniqueTagValidator) TagName() string {
|
||||
return customUniqueTagName
|
||||
}
|
||||
|
||||
func (customUniqueTagValidator) ValidScopes() sets.Set[Scope] {
|
||||
return listTagsValidScopes
|
||||
}
|
||||
|
||||
func (cutv customUniqueTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) {
|
||||
// NOTE: pointers to lists are not supported, so we should never see a pointer here.
|
||||
t := util.NativeType(context.Type)
|
||||
if t.Kind != types.Slice && t.Kind != types.Array {
|
||||
return Validations{}, fmt.Errorf("can only be used on list types (%s)", t.Kind)
|
||||
}
|
||||
|
||||
lm := cutv.byPath[context.Path.String()]
|
||||
if lm == nil {
|
||||
lm = &listMetadata{}
|
||||
cutv.byPath[context.Path.String()] = lm
|
||||
}
|
||||
|
||||
lm.customUnique = true
|
||||
|
||||
// This tag doesn't generate any validations. It just accumulates
|
||||
// information for other tags to use.
|
||||
return Validations{}, nil
|
||||
}
|
||||
|
||||
func (cutv customUniqueTagValidator) Docs() TagDoc {
|
||||
doc := TagDoc{
|
||||
Tag: cutv.TagName(),
|
||||
Scopes: cutv.ValidScopes().UnsortedList(),
|
||||
Description: "Indicates that uniqueness validation for this list is implemented via custom, handwritten validation. This disables generation of uniqueness validation for this list.",
|
||||
Payloads: nil,
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
type listValidator struct {
|
||||
byPath map[string]*listMetadata
|
||||
}
|
||||
|
|
@ -360,8 +412,13 @@ func (lv listValidator) GetValidations(context Context) (Validations, error) {
|
|||
if err := lv.check(lm); err != nil {
|
||||
return Validations{}, err
|
||||
}
|
||||
|
||||
result := Validations{}
|
||||
if lm.customUnique {
|
||||
// Uniqueness validation is disabled in generated validation for this list.
|
||||
// It would defer to handwritten validation to check the uniqueness.
|
||||
result.AddComment("Uniqueness validation is implemented via custom, handwritten validation")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Generate uniqueness checks for lists with higher-order semantics.
|
||||
if lm.semantic == semanticSet {
|
||||
|
|
@ -379,11 +436,7 @@ func (lv listValidator) GetValidations(context Context) (Validations, error) {
|
|||
WithComment(comment)
|
||||
result.AddFunction(f)
|
||||
}
|
||||
// TODO: Replace with the following once we have a way to either opt-out from
|
||||
// uniqueness validation of list-maps or settle the decision on how to handle
|
||||
// the ratcheting cases of it.
|
||||
// if lm.semantic == semanticMap {
|
||||
if lm.semantic == semanticMap && lm.ownership == ownershipSingle {
|
||||
if lm.semantic == semanticMap {
|
||||
// TODO: There are some fields which are declared as maps which do not
|
||||
// enforce uniqueness in manual validation. Those either need to not be
|
||||
// maps or we need to allow types to opt-out from this validation. SSA
|
||||
|
|
|
|||
Loading…
Reference in a new issue