diff --git a/README.md b/README.md index 32fc641..8c40597 100644 --- a/README.md +++ b/README.md @@ -138,84 +138,51 @@ Custom Field Type package main import ( - "errors" + "database/sql" + "database/sql/driver" "fmt" "reflect" - sql "database/sql/driver" - "gopkg.in/bluesuncorp/validator.v6" ) -var validate *validator.Validate - -type valuer struct { - Name string -} - -func (v valuer) Value() (sql.Value, error) { - - if v.Name == "errorme" { - return nil, errors.New("some kind of error") - } - - if v.Name == "blankme" { - return "", nil - } - - if len(v.Name) == 0 { - return nil, nil - } - - return v.Name, nil -} - -// ValidateValuerType implements validator.CustomTypeFunc -func ValidateValuerType(field reflect.Value) interface{} { - if valuer, ok := field.Interface().(sql.Valuer); ok { - val, err := valuer.Value() - if err != nil { - // handle the error how you want - return nil - } - - return val - } - - return nil +// DbBackedUser User struct +type DbBackedUser struct { + Name sql.NullString `validate:"required"` + Age sql.NullInt64 `validate:"required"` } func main() { - customTypes := map[reflect.Type]validator.CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - config := validator.Config{ TagName: "validate", ValidationFuncs: validator.BakedInValidators, - CustomTypeFuncs: customTypes, } - validate = validator.New(config) + validate := validator.New(config) - validateCustomFieldType() -} + // register all sql.Null* types to use the ValidateValuer CustomTypeFunc + validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) -func validateCustomFieldType() { - val := valuer{ - Name: "blankme", - } + x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} + errs := validate.Struct(x) - errs := validate.Field(val, "required") - if errs != nil { - fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag - return + if len(errs) > 0 { + fmt.Printf("Errs:\n%+v\n", errs) } - - // all ok } +// ValidateValuer implements validator.CustomTypeFunc +func ValidateValuer(field reflect.Value) interface{} { + if valuer, ok := field.Interface().(driver.Valuer); ok { + val, err := valuer.Value() + if err == nil { + return val + } + // handle the error how you want + } + return nil +} ``` Benchmarks diff --git a/examples/custom/custom.go b/examples/custom/custom.go new file mode 100644 index 0000000..897b17f --- /dev/null +++ b/examples/custom/custom.go @@ -0,0 +1,48 @@ +package main + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + + "gopkg.in/bluesuncorp/validator.v6" +) + +// DbBackedUser User struct +type DbBackedUser struct { + Name sql.NullString `validate:"required"` + Age sql.NullInt64 `validate:"required"` +} + +func main() { + + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate := validator.New(config) + + // register all sql.Null* types to use the ValidateValuer CustomTypeFunc + validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) + + x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} + errs := validate.Struct(x) + + if len(errs) > 0 { + fmt.Printf("Errs:\n%+v\n", errs) + } +} + +// ValidateValuer implements validator.CustomTypeFunc +func ValidateValuer(field reflect.Value) interface{} { + if valuer, ok := field.Interface().(driver.Valuer); ok { + val, err := valuer.Value() + if err == nil { + return val + } + // handle the error how you want + } + return nil +} diff --git a/examples/simple.go b/examples/simple/simple.go similarity index 100% rename from examples/simple.go rename to examples/simple/simple.go diff --git a/validator.go b/validator.go index e557d28..d8f35b6 100644 --- a/validator.go +++ b/validator.go @@ -157,6 +157,20 @@ func (v *Validate) RegisterValidation(key string, f Func) error { return nil } +// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types +func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { + + if v.config.CustomTypeFuncs == nil { + v.config.CustomTypeFuncs = map[reflect.Type]CustomTypeFunc{} + } + + for _, t := range types { + v.config.CustomTypeFuncs[reflect.TypeOf(t)] = fn + } + + v.config.hasCustomFuncs = true +} + // Field validates a single field using tag style validation and returns ValidationErrors // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error diff --git a/validator_test.go b/validator_test.go index 37b6dee..4b93f92 100644 --- a/validator_test.go +++ b/validator_test.go @@ -1,14 +1,14 @@ package validator import ( + "database/sql" + "database/sql/driver" "errors" "fmt" "reflect" "testing" "time" - sql "database/sql/driver" - . "gopkg.in/bluesuncorp/assert.v1" ) @@ -126,7 +126,7 @@ type valuer struct { Name string } -func (v valuer) Value() (sql.Value, error) { +func (v valuer) Value() (driver.Value, error) { if v.Name == "errorme" { return nil, errors.New("some kind of error") @@ -178,7 +178,7 @@ type CustomMadeUpStruct struct { } func ValidateValuerType(field reflect.Value) interface{} { - if valuer, ok := field.Interface().(sql.Valuer); ok { + if valuer, ok := field.Interface().(driver.Valuer); ok { val, err := valuer.Value() if err != nil { // handle the error how you want @@ -191,10 +191,68 @@ func ValidateValuerType(field reflect.Value) interface{} { return nil } +func TestSQLValue2Validation(t *testing.T) { + + config := Config{ + TagName: "validate", + ValidationFuncs: BakedInValidators, + } + + validate := New(config) + validate.RegisterCustomTypeFunc(ValidateValuerType, valuer{}, (*driver.Valuer)(nil), sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) + validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) + validate.RegisterCustomTypeFunc(OverrideIntTypeForSomeReason, 1) + + val := valuer{ + Name: "", + } + + errs := validate.Field(val, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + val.Name = "Valid Name" + errs = validate.Field(val, "required") + Equal(t, errs, nil) + + val.Name = "errorme" + + PanicMatches(t, func() { errs = validate.Field(val, "required") }, "SQL Driver Valuer error: some kind of error") + + type myValuer valuer + + myVal := valuer{ + Name: "", + } + + errs = validate.Field(myVal, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + cust := MadeUpCustomType{ + FirstName: "Joey", + LastName: "Bloggs", + } + + c := CustomMadeUpStruct{MadeUp: cust, OverriddenInt: 2} + + errs = validate.Struct(c) + Equal(t, errs, nil) + + c.MadeUp.FirstName = "" + c.OverriddenInt = 1 + + errs = validate.Struct(c) + NotEqual(t, errs, nil) + Equal(t, len(errs), 2) + AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required") + AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt") +} + func TestSQLValueValidation(t *testing.T) { customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason