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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@ -18,34 +17,33 @@ type defaultValidator struct {
|
|||||||
validate *validator.Validate
|
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.
|
type sliceFieldError struct {
|
||||||
func (err sliceValidateError) Error() string {
|
validator.FieldError
|
||||||
n := len(err)
|
index int
|
||||||
switch n {
|
}
|
||||||
case 0:
|
|
||||||
return ""
|
func (fe sliceFieldError) Index() int {
|
||||||
default:
|
return fe.index
|
||||||
var b strings.Builder
|
}
|
||||||
if err[0] != nil {
|
|
||||||
fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error())
|
func (fe sliceFieldError) Error() string {
|
||||||
}
|
return fmt.Sprintf("[%d]: %s", fe.index, fe.FieldError.Error())
|
||||||
if n > 1 {
|
}
|
||||||
for i := 1; i < n; i++ {
|
|
||||||
if err[i] != nil {
|
func (fe sliceFieldError) Unwrap() error {
|
||||||
b.WriteString("\n")
|
return fe.FieldError
|
||||||
fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ StructValidator = &defaultValidator{}
|
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 {
|
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -59,16 +57,18 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
|||||||
return v.validateStruct(obj)
|
return v.validateStruct(obj)
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
count := value.Len()
|
count := value.Len()
|
||||||
validateRet := make(sliceValidateError, 0)
|
var errs validator.ValidationErrors
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
|
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 {
|
if len(errs) > 0 {
|
||||||
return nil
|
return errs
|
||||||
}
|
}
|
||||||
return validateRet
|
return nil
|
||||||
default:
|
default:
|
||||||
return nil
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSliceValidateError(t *testing.T) {
|
func TestSliceFieldError(t *testing.T) {
|
||||||
tests := []struct {
|
var fe validator.FieldError = dummyFieldError{msg: "test error"}
|
||||||
name string
|
|
||||||
err sliceValidateError
|
var err SliceFieldError = sliceFieldError{fe, 10}
|
||||||
want string
|
assert.Equal(t, 10, err.Index())
|
||||||
}{
|
assert.Equal(t, "[10]: test error", err.Error())
|
||||||
{"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"},
|
assert.Equal(t, fe, errors.Unwrap(err))
|
||||||
{"has zero elements", sliceValidateError{}, ""},
|
}
|
||||||
{"has one element", sliceValidateError{errors.New("test one error")}, "[0]: test one error"},
|
|
||||||
{"has two elements",
|
type dummyFieldError struct {
|
||||||
sliceValidateError{
|
validator.FieldError
|
||||||
errors.New("first error"),
|
msg string
|
||||||
errors.New("second error"),
|
}
|
||||||
},
|
|
||||||
"[0]: first error\n[1]: second error",
|
func (fe dummyFieldError) Error() string {
|
||||||
},
|
return fe.msg
|
||||||
{"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 TestDefaultValidator(t *testing.T) {
|
func TestDefaultValidator(t *testing.T) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user