feat(validation): enhance slice validation with declarative options

This commit is contained in:
yongruilin
2025-09-15 20:35:51 +00:00
parent 947a8ebfd1
commit 4db49d0f4e

View File

@@ -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)
}