From 4db49d0f4e9eafd697d766b9938ff18bada14437 Mon Sep 17 00:00:00 2001 From: yongruilin Date: Mon, 15 Sep 2025 20:35:51 +0000 Subject: [PATCH] feat(validation): enhance slice validation with declarative options --- pkg/apis/resource/validation/validation.go | 38 ++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go index b06472a4537..360b698ad3d 100644 --- a/pkg/apis/resource/validation/validation.go +++ b/pkg/apis/resource/validation/validation.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "regexp" + "slices" "strconv" "strings" @@ -1097,28 +1098,49 @@ func validateCIdentifier(id string, fldPath *field.Path) field.ErrorList { return allErrs } +// validationOption is an option for validation. +type validationOption int + +const ( + // The validation of each item is covered by declarative validation. + itemsCovered validationOption = iota + // The list size check is covered by declarative validation. + sizeCovered + // The uniqueness check is covered by declarative validation. + uniquenessCovered +) + // validateSlice ensures that a slice does not exceed a certain maximum size // and that all entries are valid. // A negative maxSize disables the length check. -func validateSlice[T any](slice []T, maxSize int, validateItem func(T, *field.Path) field.ErrorList, fldPath *field.Path) field.ErrorList { +func validateSlice[T any](slice []T, maxSize int, validateItem func(T, *field.Path) field.ErrorList, fldPath *field.Path, opts ...validationOption) field.ErrorList { var allErrs field.ErrorList for i, item := range slice { idxPath := fldPath.Index(i) - allErrs = append(allErrs, validateItem(item, idxPath)...) + errs := validateItem(item, idxPath) + if slices.Contains(opts, itemsCovered) { + errs = errs.MarkCoveredByDeclarative() + } + allErrs = append(allErrs, errs...) } if maxSize >= 0 && len(slice) > maxSize { // Dumping the entire field into the error message is likely to be too long, // in particular when it is already beyond the maximum size. Instead this // just shows the number of entries. - allErrs = append(allErrs, field.TooMany(fldPath, len(slice), maxSize)) + err := field.TooMany(fldPath, len(slice), maxSize) + if slices.Contains(opts, sizeCovered) { + err = err.MarkCoveredByDeclarative() + } + allErrs = append(allErrs, err) } return allErrs } // validateSet ensures that a slice contains no duplicates, does not // exceed a certain maximum size and that all entries are valid. -func validateSet[T any, K comparable](slice []T, maxSize int, validateItem func(item T, fldPath *field.Path) field.ErrorList, itemKey func(T) (K, string), fldPath *field.Path) field.ErrorList { - allErrs := validateSlice(slice, maxSize, validateItem, fldPath) +func validateSet[T any, K comparable](slice []T, maxSize int, validateItem func(item T, fldPath *field.Path) field.ErrorList, itemKey func(T) (K, string), fldPath *field.Path, opts ...validationOption) field.ErrorList { + allErrs := validateSlice(slice, maxSize, validateItem, fldPath, opts...) + allItems := sets.New[K]() for i, item := range slice { idxPath := fldPath.Index(i) @@ -1128,7 +1150,11 @@ func validateSet[T any, K comparable](slice []T, maxSize int, validateItem func( childPath = childPath.Child(fieldName) } if allItems.Has(key) { - allErrs = append(allErrs, field.Duplicate(childPath, key)) + err := field.Duplicate(childPath, key) + if slices.Contains(opts, uniquenessCovered) { + err = err.MarkCoveredByDeclarative() + } + allErrs = append(allErrs, err) } else { allItems.Insert(key) }