diff --git a/binding/default_validator.go b/binding/default_validator.go index 87fc4c66..16225302 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -7,7 +7,6 @@ package binding import ( "fmt" "reflect" - "strings" "sync" "github.com/go-playground/validator/v10" @@ -18,34 +17,33 @@ type defaultValidator struct { validate *validator.Validate } -type sliceValidateError []error +// SliceFieldError is returned for invalid slice or array elements. +// It extends validator.FieldError with the index of the failing element. +type SliceFieldError interface { + validator.FieldError + Index() int +} -// Error concatenates all error elements in sliceValidateError into a single string separated by \n. -func (err sliceValidateError) Error() string { - n := len(err) - switch n { - case 0: - return "" - default: - var b strings.Builder - if err[0] != nil { - fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error()) - } - if n > 1 { - for i := 1; i < n; i++ { - if err[i] != nil { - b.WriteString("\n") - fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error()) - } - } - } - return b.String() - } +type sliceFieldError struct { + validator.FieldError + index int +} + +func (fe sliceFieldError) Index() int { + return fe.index +} + +func (fe sliceFieldError) Error() string { + return fmt.Sprintf("[%d]: %s", fe.index, fe.FieldError.Error()) +} + +func (fe sliceFieldError) Unwrap() error { + return fe.FieldError } var _ StructValidator = &defaultValidator{} -// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. +// ValidateStruct receives any kind of type, but validates only structs, pointers, slices, and arrays. func (v *defaultValidator) ValidateStruct(obj interface{}) error { if obj == nil { return nil @@ -59,16 +57,18 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error { return v.validateStruct(obj) case reflect.Slice, reflect.Array: count := value.Len() - validateRet := make(sliceValidateError, 0) + var errs validator.ValidationErrors for i := 0; i < count; i++ { if err := v.ValidateStruct(value.Index(i).Interface()); err != nil { - validateRet = append(validateRet, err) + for _, fieldError := range err.(validator.ValidationErrors) { // nolint: errorlint + errs = append(errs, sliceFieldError{fieldError, i}) + } } } - if len(validateRet) == 0 { - return nil + if len(errs) > 0 { + return errs } - return validateRet + return nil default: return nil } diff --git a/binding/default_validator_benchmark_test.go b/binding/default_validator_benchmark_test.go deleted file mode 100644 index 839cf710..00000000 --- a/binding/default_validator_benchmark_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package binding - -import ( - "errors" - "strconv" - "testing" -) - -func BenchmarkSliceValidateError(b *testing.B) { - const size int = 100 - for i := 0; i < b.N; i++ { - e := make(sliceValidateError, size) - for j := 0; j < size; j++ { - e[j] = errors.New(strconv.Itoa(j)) - } - if len(e.Error()) == 0 { - b.Errorf("error") - } - } -} diff --git a/binding/default_validator_test.go b/binding/default_validator_test.go index e9debe59..fec34ef6 100644 --- a/binding/default_validator_test.go +++ b/binding/default_validator_test.go @@ -7,43 +7,27 @@ package binding import ( "errors" "testing" + + "github.com/go-playground/validator/v10" + "github.com/stretchr/testify/assert" ) -func TestSliceValidateError(t *testing.T) { - tests := []struct { - name string - err sliceValidateError - want string - }{ - {"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"}, - {"has zero elements", sliceValidateError{}, ""}, - {"has one element", sliceValidateError{errors.New("test one error")}, "[0]: test one error"}, - {"has two elements", - sliceValidateError{ - errors.New("first error"), - errors.New("second error"), - }, - "[0]: first error\n[1]: second error", - }, - {"has many elements", - sliceValidateError{ - errors.New("first error"), - errors.New("second error"), - nil, - nil, - nil, - errors.New("last error"), - }, - "[0]: first error\n[1]: second error\n[5]: last error", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.err.Error(); got != tt.want { - t.Errorf("sliceValidateError.Error() = %v, want %v", got, tt.want) - } - }) - } +func TestSliceFieldError(t *testing.T) { + var fe validator.FieldError = dummyFieldError{msg: "test error"} + + var err SliceFieldError = sliceFieldError{fe, 10} + assert.Equal(t, 10, err.Index()) + assert.Equal(t, "[10]: test error", err.Error()) + assert.Equal(t, fe, errors.Unwrap(err)) +} + +type dummyFieldError struct { + validator.FieldError + msg string +} + +func (fe dummyFieldError) Error() string { + return fe.msg } func TestDefaultValidator(t *testing.T) {