diff --git a/binding/binding.go b/binding/binding.go index 5ca6dc7..968e898 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -4,12 +4,7 @@ package binding -import ( - "net/http" - "reflect" - - "gopkg.in/bluesuncorp/validator.v5" -) +import "net/http" const ( MIMEJSON = "application/json" @@ -26,7 +21,16 @@ type Binding interface { Bind(*http.Request, interface{}) error } -var validate = validator.New("binding", validator.BakedInValidators) +type StructValidator interface { + // ValidateStruct 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 +} + +var Validator StructValidator = &defaultValidator{} var ( JSON = jsonBinding{} @@ -49,28 +53,9 @@ func Default(method, contentType string) Binding { } } -func ValidateField(f interface{}, tag string) error { - if err := validate.Field(f, tag); err != nil { - return error(err) - } - return nil -} - func Validate(obj interface{}) error { - if kindOfData(obj) != reflect.Struct { + if Validator == nil { return nil } - if err := validate.Struct(obj); err != nil { - return error(err) - } - return nil -} - -func kindOfData(data interface{}) reflect.Kind { - value := reflect.ValueOf(data) - valueType := value.Kind() - if valueType == reflect.Ptr { - valueType = value.Elem().Kind() - } - return valueType + return Validator.ValidateStruct(obj) } diff --git a/binding/binding_test.go b/binding/binding_test.go index 75dda21..db1678e 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -64,6 +64,24 @@ func TestBindingXML(t *testing.T) { "bar", "foo") } +func TestValidationFails(t *testing.T) { + var obj FooStruct + req := requestWithBody("POST", "/", `{"bar": "foo"}`) + err := JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func TestValidationDisabled(t *testing.T) { + backup := Validator + Validator = nil + defer func() { Validator = backup }() + + var obj FooStruct + req := requestWithBody("POST", "/", `{"bar": "foo"}`) + err := JSON.Bind(req, &obj) + assert.NoError(t, err) +} + func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, b.Name(), "form") diff --git a/binding/default_validator.go b/binding/default_validator.go new file mode 100644 index 0000000..fe29222 --- /dev/null +++ b/binding/default_validator.go @@ -0,0 +1,38 @@ +package binding + +import ( + "reflect" + "sync" + + "gopkg.in/bluesuncorp/validator.v5" +) + +type defaultValidator struct { + once sync.Once + validate *validator.Validate +} + +var _ StructValidator = &defaultValidator{} + +func (v *defaultValidator) ValidateStruct(obj interface{}) error { + if kindOfData(obj) == reflect.Struct { + v.lazyinit() + return v.validate.Struct(obj) + } + return nil +} + +func (v *defaultValidator) lazyinit() { + v.once.Do(func() { + v.validate = validator.New("binding", validator.BakedInValidators) + }) +} + +func kindOfData(data interface{}) reflect.Kind { + value := reflect.ValueOf(data) + valueType := value.Kind() + if valueType == reflect.Ptr { + valueType = value.Elem().Kind() + } + return valueType +}