[ADD] slice/array binding support

This commit is contained in:
wuhuizuo 2019-12-25 09:24:20 +00:00
parent b9c96709c0
commit 13ea8367dc
3 changed files with 87 additions and 15 deletions

View File

@ -45,17 +45,17 @@ type BindingUri interface {
BindUri(map[string][]string, interface{}) error BindUri(map[string][]string, interface{}) error
} }
// StructValidator is the minimal interface which needs to be implemented in // ValidatorImp is the minimal interface which needs to be implemented in
// order for it to be used as the validator engine for ensuring the correctness // order for it to be used as the validator engine for ensuring the correctness
// of the request. Gin provides a default implementation for this using // of the request. Gin provides a default implementation for this using
// https://github.com/go-playground/validator/tree/v8.18.2. // https://github.com/go-playground/validator/tree/v8.18.2.
type StructValidator interface { type ValidatorImp interface {
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // Validate can receive any kind of type and it should never panic, even if the configuration is not right.
// If the received type is not a struct, any validation should be skipped and nil must be returned. // If the received type is not a struct, any validation should be skipped and nil must be returned.
// If the received type is a struct or pointer to a struct, the validation should be performed. // If the received type is a struct or pointer to a struct, the validation should be performed.
// If the struct is not valid or the validation itself fails, a descriptive error should be returned. // If the struct is not valid or the validation itself fails, a descriptive error should be returned.
// Otherwise nil must be returned. // Otherwise nil must be returned.
ValidateStruct(interface{}) error Validate(interface{}) error
// Engine returns the underlying validator engine which powers the // Engine returns the underlying validator engine which powers the
// StructValidator implementation. // StructValidator implementation.
@ -65,7 +65,7 @@ type StructValidator interface {
// Validator is the default validator which implements the StructValidator // Validator is the default validator which implements the StructValidator
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 // interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
// under the hood. // under the hood.
var Validator StructValidator = &defaultValidator{} var Validator ValidatorImp = &defaultValidator{}
// These implement the Binding interface and can be used to bind the data // These implement the Binding interface and can be used to bind the data
// present in the request to struct instances. // present in the request to struct instances.
@ -112,5 +112,5 @@ func validate(obj interface{}) error {
if Validator == nil { if Validator == nil {
return nil return nil
} }
return Validator.ValidateStruct(obj) return Validator.Validate(obj)
} }

View File

@ -34,7 +34,7 @@ type QueryTest struct {
} }
type FooStruct struct { type FooStruct struct {
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"` Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"`
} }
type FooBarStruct struct { type FooBarStruct struct {
@ -180,6 +180,19 @@ func TestBindingJSON(t *testing.T) {
`{"foo": "bar"}`, `{"bar": "foo"}`) `{"foo": "bar"}`, `{"bar": "foo"}`)
} }
func TestBindingJSONSlice(t *testing.T) {
EnableDecoderDisallowUnknownFields = true
defer func() {
EnableDecoderDisallowUnknownFields = false
}()
testBodyBindingSlice(t, JSON, "json", "/", "/", `[]`, ``)
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{}]`, `{}`)
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": 123}]`)
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"bar": 123}]`)
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": "123456789012345678901234567890123"}]`)
}
func TestBindingJSONUseNumber(t *testing.T) { func TestBindingJSONUseNumber(t *testing.T) {
testBodyBindingUseNumber(t, testBodyBindingUseNumber(t,
JSON, "json", JSON, "json",
@ -1114,6 +1127,20 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
assert.Error(t, err) assert.Error(t, err)
} }
func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name())
var obj1 []FooStruct
req := requestWithBody("POST", path, body)
err := b.Bind(req, &obj1)
assert.NoError(t, err)
var obj2 []FooStruct
req = requestWithBody("POST", badPath, badBody)
err = JSON.Bind(req, &obj2)
assert.Error(t, err)
}
func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())

View File

@ -6,9 +6,13 @@ package binding
import ( import (
"reflect" "reflect"
"fmt"
"sync" "sync"
"strings"
"log"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
// "github.com/ahmetb/go-linq"
) )
type defaultValidator struct { type defaultValidator struct {
@ -16,22 +20,63 @@ type defaultValidator struct {
validate *validator.Validate validate *validator.Validate
} }
var _ StructValidator = &defaultValidator{} type sliceValidateError []error
func (err sliceValidateError) Error() string {
var errMsgs []string
for i, e := range err {
if e == nil {
continue
}
errMsgs = append(errMsgs, fmt.Sprintf("[%d]: %s", i, err.Error()))
}
return strings.Join(errMsgs, "\n")
}
var _ ValidatorImp = &defaultValidator{}
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. // ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
func (v *defaultValidator) ValidateStruct(obj interface{}) error { func (v *defaultValidator) Validate(obj interface{}) error {
log.Println("called")
if obj == nil {
return nil
}
value := reflect.ValueOf(obj) value := reflect.ValueOf(obj)
valueType := value.Kind() valueType := value.Kind()
log.Printf("valueType: %v, %#v\n", valueType, valueType)
if valueType == reflect.Ptr { if valueType == reflect.Ptr {
valueType = value.Elem().Kind() value = value.Elem()
valueType = value.Kind()
} }
if valueType == reflect.Struct {
v.lazyinit() switch valueType {
if err := v.validate.Struct(obj); err != nil { case reflect.Struct:
return err log.Printf("goto validateStruct: %v, %#v\n", obj, obj)
return v.validateStruct(obj)
case reflect.Slice, reflect.Array:
count := value.Len()
validateRet := make(sliceValidateError, 0)
for i := 0; i < count; i++ {
log.Println("called inside slice")
if err := v.Validate(value.Index(i)); err != nil {
validateRet = append(validateRet, err)
}
} }
log.Println(validateRet)
if len(validateRet) == 0 {
return nil
}
return validateRet
default:
return nil
} }
return nil }
// validateStruct receives struct type
func (v *defaultValidator) validateStruct(obj interface{}) error {
v.lazyinit()
return v.validate.Struct(obj)
} }
// Engine returns the underlying validator engine which powers the default // Engine returns the underlying validator engine which powers the default