mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-16 21:32:11 +08:00
Always return ValidationErrors, expose failing index
This way callers can always expect ValidationErrors, and in case of slice validation, they can also get indexes of the failing elements.
This commit is contained in:
parent
efa3175007
commit
cb9b68b1b5
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user