diff --git a/binding/binding.go b/binding/binding.go index 57562845..a2cbf488 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -45,17 +45,17 @@ type BindingUri interface { 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 // of the request. Gin provides a default implementation for this using // https://github.com/go-playground/validator/tree/v8.18.2. -type StructValidator interface { - // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. +type ValidatorImp interface { + // 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 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. // Otherwise nil must be returned. - ValidateStruct(interface{}) error + Validate(interface{}) error // Engine returns the underlying validator engine which powers the // StructValidator implementation. @@ -65,7 +65,7 @@ type StructValidator interface { // Validator is the default validator which implements the StructValidator // interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 // under the hood. -var Validator StructValidator = &defaultValidator{} +var Validator ValidatorImp = &defaultValidator{} // These implement the Binding interface and can be used to bind the data // present in the request to struct instances. @@ -112,5 +112,5 @@ func validate(obj interface{}) error { if Validator == nil { return nil } - return Validator.ValidateStruct(obj) + return Validator.Validate(obj) } diff --git a/binding/binding_test.go b/binding/binding_test.go index 4424bab9..a3af5875 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -34,7 +34,7 @@ type QueryTest 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 { @@ -180,6 +180,19 @@ func TestBindingJSON(t *testing.T) { `{"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) { testBodyBindingUseNumber(t, JSON, "json", @@ -1114,6 +1127,20 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody 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) { assert.Equal(t, name, b.Name()) diff --git a/binding/default_validator.go b/binding/default_validator.go index a4c1a7f6..cc0f9169 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -6,9 +6,13 @@ package binding import ( "reflect" + "fmt" "sync" + "strings" + "log" "github.com/go-playground/validator/v10" + // "github.com/ahmetb/go-linq" ) type defaultValidator struct { @@ -16,22 +20,63 @@ type defaultValidator struct { 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. -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) valueType := value.Kind() + log.Printf("valueType: %v, %#v\n", valueType, valueType) if valueType == reflect.Ptr { - valueType = value.Elem().Kind() + value = value.Elem() + valueType = value.Kind() } - if valueType == reflect.Struct { - v.lazyinit() - if err := v.validate.Struct(obj); err != nil { - return err + + switch valueType { + case reflect.Struct: + 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