diff --git a/README.md b/README.md index c4da807..25233aa 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ It has the following **unique** features: - Cross Field and Cross Struct validations. - Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated. -- Handles type interface by determining it's underlying type prior to validation. +- Handles type interface by determining it's underlying type prior to validation. +- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29) Installation ------------ @@ -34,7 +35,9 @@ Usage and documentation Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs. -##### Example: +##### Examples: + +Struct & Field validation ```go package main @@ -73,6 +76,12 @@ func main() { validate = validator.New(config) + validateStruct() + validateField() +} + +func validateStruct() { + address := &Address{ Street: "Eavesdown Docks", Planet: "Persphone", @@ -109,6 +118,71 @@ func main() { // save user to database } + +func validateField() { + myEmail := "joeybloggs.gmail.com" + + errs := validate.Field(myEmail, "required,email") + + if errs != nil { + fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag + return + } + + // email ok, move on +} +``` + +Custom Field Type +```go +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 +} ``` Benchmarks @@ -120,12 +194,22 @@ hurt parallel performance too much. ```go $ go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkField-4 5000000 314 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTag-4 500000 2425 ns/op 20 B/op 2 allocs/op -BenchmarkStructSimple-4 500000 3117 ns/op 553 B/op 14 allocs/op -BenchmarkStructSimpleParallel-4 1000000 1149 ns/op 553 B/op 14 allocs/op -BenchmarkStructComplex-4 100000 19580 ns/op 3230 B/op 102 allocs/op -BenchmarkStructComplexParallel-4 200000 6686 ns/op 3232 B/op 102 allocs/op +BenchmarkFieldSuccess-4 5000000 318 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 316 ns/op 16 B/op 1 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 492 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 843 ns/op 416 B/op 6 allocs/op +BenchmarkFieldOrTagSuccess-4 500000 2384 ns/op 20 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1295 ns/op 384 B/op 6 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1175 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1822 ns/op 529 B/op 11 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1302 ns/op 56 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1847 ns/op 577 B/op 13 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 339 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 733 ns/op 529 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 7104 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailure-4 100000 11996 ns/op 2861 B/op 72 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2252 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 4691 ns/op 2862 B/op 72 allocs/op ``` How to Contribute diff --git a/baked_in.go b/baked_in.go index bf9e512..b323863 100644 --- a/baked_in.go +++ b/baked_in.go @@ -2,6 +2,7 @@ package validator import ( "fmt" + "net" "net/url" "reflect" "strconv" @@ -64,6 +65,35 @@ var BakedInValidators = map[string]Func{ "latitude": isLatitude, "longitude": isLongitude, "ssn": isSSN, + "ipv4": isIPv4, + "ipv6": isIPv6, + "ip": isIP, + "mac": isMac, +} + +func isMac(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + _, err := net.ParseMAC(field.String()) + return err == nil +} + +func isIPv4(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + ip := net.ParseIP(field.String()) + + return ip != nil && ip.To4() != nil +} + +func isIPv6(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + ip := net.ParseIP(field.String()) + + return ip != nil && ip.To4() == nil +} + +func isIP(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + ip := net.ParseIP(field.String()) + + return ip != nil } func isSSN(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { @@ -403,10 +433,8 @@ func isAlpha(topStruct reflect.Value, currentStruct reflect.Value, field reflect func hasValue(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { - - case reflect.Slice, reflect.Map, reflect.Array: - return !field.IsNil() && int64(field.Len()) > 0 - + case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: + return !field.IsNil() default: return field.IsValid() && field.Interface() != reflect.Zero(fieldType).Interface() } diff --git a/benchmarks_test.go b/benchmarks_test.go index 14c5d2b..af201e6 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -1,20 +1,68 @@ package validator -import "testing" +import ( + sql "database/sql/driver" + "reflect" + "testing" +) -func BenchmarkField(b *testing.B) { +func BenchmarkFieldSuccess(b *testing.B) { for n := 0; n < b.N; n++ { validate.Field("1", "len=1") } } -func BenchmarkFieldOrTag(b *testing.B) { +func BenchmarkFieldFailure(b *testing.B) { + for n := 0; n < b.N; n++ { + validate.Field("2", "len=1") + } +} + +func BenchmarkFieldCustomTypeSuccess(b *testing.B) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + val := valuer{ + Name: "1", + } + + for n := 0; n < b.N; n++ { + validate.Field(val, "len=1") + } +} + +func BenchmarkFieldCustomTypeFailure(b *testing.B) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + val := valuer{} + + for n := 0; n < b.N; n++ { + validate.Field(val, "len=1") + } +} + +func BenchmarkFieldOrTagSuccess(b *testing.B) { for n := 0; n < b.N; n++ { validate.Field("rgba(0,0,0,1)", "rgb|rgba") } } -func BenchmarkStructSimple(b *testing.B) { +func BenchmarkFieldOrTagFailure(b *testing.B) { + for n := 0; n < b.N; n++ { + validate.Field("#000", "rgb|rgba") + } +} + +func BenchmarkStructSimpleSuccess(b *testing.B) { type Foo struct { StringValue string `validate:"min=5,max=10"` @@ -22,15 +70,73 @@ func BenchmarkStructSimple(b *testing.B) { } validFoo := &Foo{StringValue: "Foobar", IntValue: 7} - invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} for n := 0; n < b.N; n++ { validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleFailure(b *testing.B) { + + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + + invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} + + for n := 0; n < b.N; n++ { validate.Struct(invalidFoo) } } -func BenchmarkStructSimpleParallel(b *testing.B) { +func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + val := valuer{ + Name: "1", + } + + type Foo struct { + Valuer valuer `validate:"len=1"` + IntValue int `validate:"min=5,max=10"` + } + + validFoo := &Foo{Valuer: val, IntValue: 7} + + for n := 0; n < b.N; n++ { + validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + val := valuer{} + + type Foo struct { + Valuer valuer `validate:"len=1"` + IntValue int `validate:"min=5,max=10"` + } + + validFoo := &Foo{Valuer: val, IntValue: 3} + + for n := 0; n < b.N; n++ { + validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleSuccessParallel(b *testing.B) { type Foo struct { StringValue string `validate:"min=5,max=10"` @@ -38,42 +144,32 @@ func BenchmarkStructSimpleParallel(b *testing.B) { } validFoo := &Foo{StringValue: "Foobar", IntValue: 7} - invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} b.RunParallel(func(pb *testing.PB) { for pb.Next() { validate.Struct(validFoo) - validate.Struct(invalidFoo) } }) } -func BenchmarkStructComplex(b *testing.B) { +func BenchmarkStructSimpleFailureParallel(b *testing.B) { - tFail := &TestString{ - Required: "", - Len: "", - Min: "", - Max: "12345678901", - MinMax: "", - Lt: "0123456789", - Lte: "01234567890", - Gt: "1", - Gte: "1", - OmitEmpty: "12345678901", - Sub: &SubTest{ - Test: "", - }, - Anonymous: struct { - A string `validate:"required"` - }{ - A: "", - }, - Iface: &Impl{ - F: "12", - }, + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` } + invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + validate.Struct(invalidFoo) + } + }) +} + +func BenchmarkStructComplexSuccess(b *testing.B) { + tSuccess := &TestString{ Required: "Required", Len: "length==10", @@ -103,11 +199,10 @@ func BenchmarkStructComplex(b *testing.B) { for n := 0; n < b.N; n++ { validate.Struct(tSuccess) - validate.Struct(tFail) } } -func BenchmarkStructComplexParallel(b *testing.B) { +func BenchmarkStructComplexFailure(b *testing.B) { tFail := &TestString{ Required: "", @@ -133,6 +228,13 @@ func BenchmarkStructComplexParallel(b *testing.B) { }, } + for n := 0; n < b.N; n++ { + validate.Struct(tFail) + } +} + +func BenchmarkStructComplexSuccessParallel(b *testing.B) { + tSuccess := &TestString{ Required: "Required", Len: "length==10", @@ -163,6 +265,38 @@ func BenchmarkStructComplexParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { validate.Struct(tSuccess) + } + }) +} + +func BenchmarkStructComplexFailureParallel(b *testing.B) { + + tFail := &TestString{ + Required: "", + Len: "", + Min: "", + Max: "12345678901", + MinMax: "", + Lt: "0123456789", + Lte: "01234567890", + Gt: "1", + Gte: "1", + OmitEmpty: "12345678901", + Sub: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + }{ + A: "", + }, + Iface: &Impl{ + F: "12", + }, + } + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { validate.Struct(tFail) } }) diff --git a/doc.go b/doc.go index df2db9f..db05710 100644 --- a/doc.go +++ b/doc.go @@ -102,6 +102,14 @@ Here is a list of the current built in validators: you know the struct will be valid, but need to verify it has been assigned. NOTE: only "required" and "omitempty" can be used on a struct itself. + exists + Is a special tag without a validation function attached. It is used when a field + is a Pointer, Interface or Invalid and you wish to validate that it exists. + Example: want to ensure a bool exists if you define the bool as a pointer and + use exists it will ensure there is a value; couldn't use required as it would + fail when the bool was false. exists will fail is the value is a Pointer, Interface + or Invalid and is nil. (Usage: exists) + omitempty Allows conditional validation, for example if a field is not set with a value (Determined by the "required" validator) then other validation @@ -121,16 +129,12 @@ Here is a list of the current built in validators: gt=0 will be applied to [] []string will be spared validation required will be applied to string - NOTE: in Example2 if the required validation failed, but all others passed - the hierarchy of FieldError's in the middle with have their IsPlaceHolder field - set to true. If a FieldError has IsSliceOrMap=true or IsMap=true then the - FieldError is a Slice or Map field and if IsPlaceHolder=true then contains errors - within its SliceOrArrayErrs or MapErrs fields. required - This validates that the value is not the data types default value. + This validates that the value is not the data types default zero value. For numbers ensures value is not zero. For strings ensures value is - not "". For slices, arrays, and maps, ensures the length is not zero. + not "". For slices, maps, pointers, interfaces, channels and functions + ensures the value is not nil. (Usage: required) len @@ -376,6 +380,24 @@ Here is a list of the current built in validators: This validates that a string value contains a valid U.S. Social Security Number. (Usage: ssn) + ip + This validates that a string value contains a valid IP Adress. + (Usage: ip) + + ipv4 + This validates that a string value contains a valid v4 IP Adress. + (Usage: ipv4) + + ipv6 + This validates that a string value contains a valid v6 IP Adress. + (Usage: ipv6) + + mac + This validates that a string value contains a valid MAC Adress defined + by go's ParseMAC accepted formats and types see: + http://golang.org/src/net/mac.go?s=866:918#L29 + (Usage: mac) + Validator notes: regex 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 54% rename from examples/simple.go rename to examples/simple/simple.go index 98dabed..b685dda 100644 --- a/examples/simple.go +++ b/examples/simple/simple.go @@ -1,7 +1,11 @@ package main import ( + "errors" "fmt" + "reflect" + + sql "database/sql/driver" "gopkg.in/bluesuncorp/validator.v6" ) @@ -35,6 +39,12 @@ func main() { validate = validator.New(config) + validateStruct() + validateField() +} + +func validateStruct() { + address := &Address{ Street: "Eavesdown Docks", Planet: "Persphone", @@ -71,3 +81,85 @@ func main() { // save user to database } + +func validateField() { + myEmail := "joeybloggs.gmail.com" + + errs := validate.Field(myEmail, "required,email") + + if errs != nil { + fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag + return + } + + // email ok, move on +} + +var validate2 *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 +} + +func main2() { + + 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, + } + + validate2 = validator.New(config) + + validateCustomFieldType() +} + +func validateCustomFieldType() { + val := valuer{ + Name: "blankme", + } + + errs := validate2.Field(val, "required") + if errs != nil { + fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag + return + } + + // all ok +} diff --git a/validator.go b/validator.go index 781bea3..709a1fc 100644 --- a/validator.go +++ b/validator.go @@ -29,6 +29,7 @@ const ( omitempty = "omitempty" skipValidationTag = "-" diveTag = "dive" + existsTag = "exists" fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" arrayIndexFieldName = "%s[%d]" mapIndexFieldName = "%s[%v]" @@ -45,7 +46,7 @@ var ( // returns new ValidationErrors to the pool func newValidationErrors() interface{} { - return map[string]*FieldError{} + return ValidationErrors{} } type tagCache struct { @@ -81,8 +82,15 @@ type Validate struct { type Config struct { TagName string ValidationFuncs map[string]Func + CustomTypeFuncs map[reflect.Type]CustomTypeFunc + hasCustomFuncs bool } +// CustomTypeFunc allows for overriding or adding custom field type handler functions +// field = field value of the type to return a value to be validated +// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29 +type CustomTypeFunc func(field reflect.Value) interface{} + // Func accepts all values needed for file and cross field validation // topStruct = top level struct when validating by struct otherwise nil // currentStruct = current level struct when validating by struct otherwise optional comparison value @@ -124,6 +132,11 @@ type FieldError struct { // New creates a new Validate instance for use. func New(config Config) *Validate { + + if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { + config.hasCustomFuncs = true + } + return &Validate{config: config} } @@ -145,12 +158,26 @@ 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 func (v *Validate) Field(field interface{}, tag string) ValidationErrors { - errs := errsPool.Get().(map[string]*FieldError) + errs := errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "") @@ -168,7 +195,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors { // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) ValidationErrors { - errs := errsPool.Get().(map[string]*FieldError) + errs := errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "") @@ -184,7 +211,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. func (v *Validate) Struct(current interface{}) ValidationErrors { - errs := errsPool.Get().(map[string]*FieldError) + errs := errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) v.tranverseStruct(sv, sv, sv, "", errs, true) @@ -243,12 +270,10 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. kind = current.Kind() } - typ := current.Type() - // this also allows for tags 'required' and 'omitempty' to be used on // nested struct fields because when len(tags) > 0 below and the value is nil // then required failes and we check for omitempty just before that - if (kind == reflect.Ptr || kind == reflect.Interface) && current.IsNil() { + if ((kind == reflect.Ptr || kind == reflect.Interface) && current.IsNil()) || kind == reflect.Invalid { if strings.Contains(tag, omitempty) { return @@ -264,19 +289,35 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. param = vals[1] } + if kind == reflect.Invalid { + errs[errPrefix+name] = &FieldError{ + Field: name, + Tag: vals[0], + Param: param, + Kind: kind, + } + return + } + errs[errPrefix+name] = &FieldError{ Field: name, Tag: vals[0], Param: param, Value: current.Interface(), Kind: kind, - Type: typ, + Type: current.Type(), } return } + // if we get here tag length is zero and we can leave + if kind == reflect.Invalid { + return + } } + typ := current.Type() + switch kind { case reflect.Struct, reflect.Interface: @@ -302,6 +343,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. if kind == reflect.Struct { + if v.config.hasCustomFuncs { + if fn, ok := v.config.CustomTypeFuncs[typ]; ok { + v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) + return + } + } + // required passed validation above so stop here // if only validating the structs existance. if strings.Contains(tag, structOnlyTag) { @@ -320,6 +368,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. } } + if v.config.hasCustomFuncs { + if fn, ok := v.config.CustomTypeFuncs[typ]; ok { + v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) + return + } + } + tags, isCached := tagsCache.Get(tag) if !isCached { @@ -329,7 +384,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. for _, t := range strings.Split(tag, tagSeparator) { if t == diveTag { - tags = append(tags, &tagCache{tagVals: [][]string{[]string{t}}}) + tags = append(tags, &tagCache{tagVals: [][]string{{t}}}) break } @@ -364,6 +419,10 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. for _, cTag := range tags { + if cTag.tagVals[0][0] == existsTag { + continue + } + if cTag.tagVals[0][0] == diveTag { dive = true diveSubTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",") diff --git a/validator_test.go b/validator_test.go index 4021e86..c17167a 100644 --- a/validator_test.go +++ b/validator_test.go @@ -1,12 +1,15 @@ package validator import ( + "database/sql" + "database/sql/driver" + "encoding/json" "fmt" - "path" "reflect" - "runtime" "testing" "time" + + . "gopkg.in/bluesuncorp/assert.v1" ) // NOTES: @@ -110,123 +113,449 @@ type TestSlice struct { var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators}) -func IsEqual(t *testing.T, val1, val2 interface{}) bool { - v1 := reflect.ValueOf(val1) - v2 := reflect.ValueOf(val2) +func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) { - if v1.Kind() == reflect.Ptr { - v1 = v1.Elem() - } + val, ok := errs[key] + EqualSkip(t, 2, ok, true) + NotEqualSkip(t, 2, val, nil) + EqualSkip(t, 2, val.Field, field) + EqualSkip(t, 2, val.Tag, expectedTag) +} + +type valuer struct { + Name string +} + +func (v valuer) Value() (driver.Value, error) { - if v2.Kind() == reflect.Ptr { - v2 = v2.Elem() + if v.Name == "errorme" { + panic("SQL Driver Valuer error: some kind of error") + // return nil, errors.New("some kind of error") } - if !v1.IsValid() && !v2.IsValid() { - return true + if len(v.Name) == 0 { + return nil, nil } - switch v1.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - if v1.IsNil() { - v1 = reflect.ValueOf(nil) + return v.Name, nil +} + +type MadeUpCustomType struct { + FirstName string + LastName string +} + +func ValidateCustomType(field reflect.Value) interface{} { + if cust, ok := field.Interface().(MadeUpCustomType); ok { + + if len(cust.FirstName) == 0 || len(cust.LastName) == 0 { + return "" } + + return cust.FirstName + " " + cust.LastName } - switch v2.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - if v2.IsNil() { - v2 = reflect.ValueOf(nil) + return "" +} + +func OverrideIntTypeForSomeReason(field reflect.Value) interface{} { + + if i, ok := field.Interface().(int); ok { + if i == 1 { + return "1" + } + + if i == 2 { + return "12" } } - v1Underlying := reflect.Zero(reflect.TypeOf(v1)).Interface() - v2Underlying := reflect.Zero(reflect.TypeOf(v2)).Interface() + return "" +} - if v1 == v1Underlying { - if v2 == v2Underlying { - goto CASE4 - } else { - goto CASE3 - } - } else { - if v2 == v2Underlying { - goto CASE2 - } else { - goto CASE1 +type CustomMadeUpStruct struct { + MadeUp MadeUpCustomType `validate:"required"` + OverriddenInt int `validate:"gt=1"` +} + +func ValidateValuerType(field reflect.Value) interface{} { + if valuer, ok := field.Interface().(driver.Valuer); ok { + val, err := valuer.Value() + if err != nil { + // handle the error how you want + return nil } + + return val } -CASE1: - // fmt.Println("CASE 1") - return reflect.DeepEqual(v1.Interface(), v2.Interface()) -CASE2: - // fmt.Println("CASE 2") - return reflect.DeepEqual(v1.Interface(), v2) -CASE3: - // fmt.Println("CASE 3") - return reflect.DeepEqual(v1, v2.Interface()) -CASE4: - // fmt.Println("CASE 4") - return reflect.DeepEqual(v1, v2) + return nil } -func Equal(t *testing.T, val1, val2 interface{}) { - EqualSkip(t, 2, val1, val2) +func TestExistsValidation(t *testing.T) { + + jsonText := "{ \"truthiness2\": true }" + + type Thing struct { + Truthiness *bool `json:"truthiness" validate:"exists,required"` + } + + var ting Thing + + err := json.Unmarshal([]byte(jsonText), &ting) + Equal(t, err, nil) + NotEqual(t, ting, nil) + Equal(t, ting.Truthiness, nil) + + errs := validate.Struct(ting) + NotEqual(t, errs, nil) + AssertError(t, errs, "Thing.Truthiness", "Truthiness", "exists") + + jsonText = "{ \"truthiness\": true }" + + err = json.Unmarshal([]byte(jsonText), &ting) + Equal(t, err, nil) + NotEqual(t, ting, nil) + Equal(t, ting.Truthiness, true) + + errs = validate.Struct(ting) + Equal(t, errs, nil) } -func EqualSkip(t *testing.T, skip int, val1, val2 interface{}) { +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: "", + } - if !IsEqual(t, val1, val2) { + errs := validate.Field(val, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") - _, file, line, _ := runtime.Caller(skip) - fmt.Printf("%s:%d %v does not equal %v\n", path.Base(file), line, val1, val2) - t.FailNow() + val.Name = "Valid Name" + errs = validate.Field(val, "required") + Equal(t, errs, nil) + + val.Name = "errorme" + + PanicMatches(t, func() { 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 NotEqual(t *testing.T, val1, val2 interface{}) { - NotEqualSkip(t, 2, val1, val2) +func TestSQLValueValidation(t *testing.T) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType + customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + 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 NotEqualSkip(t *testing.T, skip int, val1, val2 interface{}) { +func TestMACValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"3D:F2:C9:A6:B3:4F", true}, + {"3D-F2-C9-A6-B3:4F", false}, + {"123", false}, + {"", false}, + {"abacaba", false}, + {"00:25:96:FF:FE:12:34:56", true}, + {"0025:96FF:FE12:3456", false}, + } + + for i, test := range tests { + + errs := validate.Field(test.param, "mac") - if IsEqual(t, val1, val2) { - _, file, line, _ := runtime.Caller(skip) - fmt.Printf("%s:%d %v should not be equal %v\n", path.Base(file), line, val1, val2) - t.FailNow() + if test.expected == true { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d mac failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d mac failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "mac" { + t.Fatalf("Index: %d mac failed Error: %s", i, errs) + } + } + } } } -func PanicMatches(t *testing.T, fn func(), matches string) { - PanicMatchesSkip(t, 2, fn, matches) +func TestIPValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.1", true}, + {"172.16.0.1", true}, + {"192.168.0.1", true}, + {"192.168.255.254", true}, + {"192.168.255.256", false}, + {"172.16.255.254", true}, + {"172.16.256.255", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", true}, + {"2001:cdba:0:0:0:0:3257:9652", true}, + {"2001:cdba::3257:9652", true}, + } + + for i, test := range tests { + + errs := validate.Field(test.param, "ip") + + if test.expected == true { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ip failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ip failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "ip" { + t.Fatalf("Index: %d ip failed Error: %s", i, errs) + } + } + } + } } -func PanicMatchesSkip(t *testing.T, skip int, fn func(), matches string) { +func TestIPv6Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.1", false}, + {"172.16.0.1", false}, + {"192.168.0.1", false}, + {"192.168.255.254", false}, + {"192.168.255.256", false}, + {"172.16.255.254", false}, + {"172.16.256.255", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", true}, + {"2001:cdba:0:0:0:0:3257:9652", true}, + {"2001:cdba::3257:9652", true}, + } - _, file, line, _ := runtime.Caller(skip) + for i, test := range tests { - defer func() { - if r := recover(); r != nil { - err := fmt.Sprintf("%s", r) + errs := validate.Field(test.param, "ipv6") - if err != matches { - fmt.Printf("%s:%d Panic... expected [%s] received [%s]", path.Base(file), line, matches, err) - t.FailNow() + if test.expected == true { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "ipv6" { + t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) + } } } - }() + } +} - fn() +func TestIPv4Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.1", true}, + {"172.16.0.1", true}, + {"192.168.0.1", true}, + {"192.168.255.254", true}, + {"192.168.255.256", false}, + {"172.16.255.254", true}, + {"172.16.256.255", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, + {"2001:cdba:0:0:0:0:3257:9652", false}, + {"2001:cdba::3257:9652", false}, + } + + for i, test := range tests { + + errs := validate.Field(test.param, "ipv4") + + if test.expected == true { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "ipv4" { + t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) + } + } + } + } } -func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) { +func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) { - val, ok := errs[key] - EqualSkip(t, 2, ok, true) - NotEqualSkip(t, 2, val, nil) - EqualSkip(t, 2, val.Field, field) - EqualSkip(t, 2, val.Tag, expectedTag) + var m map[string]string + + errs := validate.Field(m, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + m = map[string]string{} + errs = validate.Field(m, "required") + Equal(t, errs, nil) + + var arr [5]string + errs = validate.Field(arr, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + arr[0] = "ok" + errs = validate.Field(arr, "required") + Equal(t, errs, nil) + + var s []string + errs = validate.Field(s, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + s = []string{} + errs = validate.Field(s, "required") + Equal(t, errs, nil) + + var c chan string + errs = validate.Field(c, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + c = make(chan string) + errs = validate.Field(c, "required") + Equal(t, errs, nil) + + var tst *int + errs = validate.Field(tst, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + one := 1 + tst = &one + errs = validate.Field(tst, "required") + Equal(t, errs, nil) + + var iface interface{} + + errs = validate.Field(iface, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + errs = validate.Field(iface, "omitempty,required") + Equal(t, errs, nil) + + errs = validate.Field(iface, "") + Equal(t, errs, nil) + + var f func(string) + + errs = validate.Field(f, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + f = func(name string) {} + + errs = validate.Field(f, "required") + Equal(t, errs, nil) } func TestDatePtrValidationIssueValidation(t *testing.T) { @@ -396,9 +725,9 @@ func TestInterfaceErrValidation(t *testing.T) { var errStructPtr2Array [][]*Inner - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, nil}) tmsp2 := &TestMultiDimensionalStructsPtr2{ Errs: errStructPtr2Array, @@ -478,7 +807,7 @@ func TestMapDiveValidation(t *testing.T) { Errs map[int]Inner `validate:"gt=0,dive"` } - mi := map[int]Inner{0: Inner{"ok"}, 3: Inner{""}, 4: Inner{"ok"}} + mi := map[int]Inner{0: {"ok"}, 3: {""}, 4: {"ok"}} ms := &TestMapStruct{ Errs: mi, @@ -514,7 +843,7 @@ func TestMapDiveValidation(t *testing.T) { Errs map[int]*Inner `validate:"gt=0,dive,required"` } - mip := map[int]*Inner{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}} + mip := map[int]*Inner{0: {"ok"}, 3: nil, 4: {"ok"}} msp := &TestMapStructPtr{ Errs: mip, @@ -529,7 +858,7 @@ func TestMapDiveValidation(t *testing.T) { Errs map[int]*Inner `validate:"gt=0,dive,omitempty,required"` } - mip2 := map[int]*Inner{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}} + mip2 := map[int]*Inner{0: {"ok"}, 3: nil, 4: {"ok"}} msp2 := &TestMapStructPtr2{ Errs: mip2, @@ -616,8 +945,8 @@ func TestArrayDiveValidation(t *testing.T) { var errStructArray [][]Inner - errStructArray = append(errStructArray, []Inner{Inner{"ok"}, Inner{""}, Inner{""}}) - errStructArray = append(errStructArray, []Inner{Inner{"ok"}, Inner{""}, Inner{""}}) + errStructArray = append(errStructArray, []Inner{{"ok"}, {""}, {""}}) + errStructArray = append(errStructArray, []Inner{{"ok"}, {""}, {""}}) tms := &TestMultiDimensionalStructs{ Errs: errStructArray, @@ -637,9 +966,9 @@ func TestArrayDiveValidation(t *testing.T) { var errStructPtrArray [][]*Inner - errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) + errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, {""}}) + errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, {""}}) + errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, nil}) tmsp := &TestMultiDimensionalStructsPtr{ Errs: errStructPtrArray, @@ -662,9 +991,9 @@ func TestArrayDiveValidation(t *testing.T) { var errStructPtr2Array [][]*Inner - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, nil}) tmsp2 := &TestMultiDimensionalStructsPtr2{ Errs: errStructPtr2Array, @@ -686,9 +1015,9 @@ func TestArrayDiveValidation(t *testing.T) { var errStructPtr3Array [][]*Inner - errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) + errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, nil}) tmsp3 := &TestMultiDimensionalStructsPtr3{ Errs: errStructPtr3Array, @@ -864,11 +1193,11 @@ func TestSSNValidation(t *testing.T) { errs := validate.Field(test.param, "ssn") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d SSN failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d SSN failed Error: %s", i, errs) } else { val := errs[""] @@ -898,11 +1227,11 @@ func TestLongitudeValidation(t *testing.T) { errs := validate.Field(test.param, "longitude") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) } else { val := errs[""] @@ -932,11 +1261,11 @@ func TestLatitudeValidation(t *testing.T) { errs := validate.Field(test.param, "latitude") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } else { val := errs[""] @@ -972,11 +1301,11 @@ func TestDataURIValidation(t *testing.T) { errs := validate.Field(test.param, "datauri") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) } else { val := errs[""] @@ -1010,11 +1339,11 @@ func TestMultibyteValidation(t *testing.T) { errs := validate.Field(test.param, "multibyte") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) } else { val := errs[""] @@ -1049,11 +1378,11 @@ func TestPrintableASCIIValidation(t *testing.T) { errs := validate.Field(test.param, "printascii") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) } else { val := errs[""] @@ -1087,11 +1416,11 @@ func TestASCIIValidation(t *testing.T) { errs := validate.Field(test.param, "ascii") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) } else { val := errs[""] @@ -1122,11 +1451,11 @@ func TestUUID5Validation(t *testing.T) { errs := validate.Field(test.param, "uuid5") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) } else { val := errs[""] @@ -1156,11 +1485,11 @@ func TestUUID4Validation(t *testing.T) { errs := validate.Field(test.param, "uuid4") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) } else { val := errs[""] @@ -1189,11 +1518,11 @@ func TestUUID3Validation(t *testing.T) { errs := validate.Field(test.param, "uuid3") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) } else { val := errs[""] @@ -1225,11 +1554,11 @@ func TestUUIDValidation(t *testing.T) { errs := validate.Field(test.param, "uuid") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d UUID failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID failed Error: %s", i, errs) } else { val := errs[""] @@ -1263,11 +1592,11 @@ func TestISBNValidation(t *testing.T) { errs := validate.Field(test.param, "isbn") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) } else { val := errs[""] @@ -1300,11 +1629,11 @@ func TestISBN13Validation(t *testing.T) { errs := validate.Field(test.param, "isbn13") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) } else { val := errs[""] @@ -1338,11 +1667,11 @@ func TestISBN10Validation(t *testing.T) { errs := validate.Field(test.param, "isbn10") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) } else { val := errs[""] @@ -2702,11 +3031,11 @@ func TestUrl(t *testing.T) { errs := validate.Field(test.param, "url") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d URL failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d URL failed Error: %s", i, errs) } else { val := errs[""] @@ -2766,11 +3095,11 @@ func TestUri(t *testing.T) { errs := validate.Field(test.param, "uri") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d URI failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d URI failed Error: %s", i, errs) } else { val := errs[""] @@ -2858,7 +3187,9 @@ func TestHsla(t *testing.T) { AssertError(t, errs, "", "", "hsla") i := 1 - PanicMatches(t, func() { validate.Field(i, "hsla") }, "interface conversion: interface is int, not string") + validate.Field(i, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsla") } func TestHsl(t *testing.T) { @@ -2892,7 +3223,9 @@ func TestHsl(t *testing.T) { AssertError(t, errs, "", "", "hsl") i := 1 - PanicMatches(t, func() { validate.Field(i, "hsl") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsl") } func TestRgba(t *testing.T) { @@ -2934,7 +3267,9 @@ func TestRgba(t *testing.T) { AssertError(t, errs, "", "", "rgba") i := 1 - PanicMatches(t, func() { validate.Field(i, "rgba") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgba") } func TestRgb(t *testing.T) { @@ -2972,7 +3307,9 @@ func TestRgb(t *testing.T) { AssertError(t, errs, "", "", "rgb") i := 1 - PanicMatches(t, func() { validate.Field(i, "rgb") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgb") } func TestEmail(t *testing.T) { @@ -3002,7 +3339,9 @@ func TestEmail(t *testing.T) { AssertError(t, errs, "", "", "email") i := true - PanicMatches(t, func() { validate.Field(i, "email") }, "interface conversion: interface is bool, not string") + errs = validate.Field(i, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "email") } func TestHexColor(t *testing.T) { @@ -3026,7 +3365,9 @@ func TestHexColor(t *testing.T) { AssertError(t, errs, "", "", "hexcolor") i := true - PanicMatches(t, func() { validate.Field(i, "hexcolor") }, "interface conversion: interface is bool, not string") + errs = validate.Field(i, "hexcolor") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hexcolor") } func TestHexadecimal(t *testing.T) { @@ -3041,7 +3382,9 @@ func TestHexadecimal(t *testing.T) { AssertError(t, errs, "", "", "hexadecimal") i := true - PanicMatches(t, func() { validate.Field(i, "hexadecimal") }, "interface conversion: interface is bool, not string") + errs = validate.Field(i, "hexadecimal") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hexadecimal") } func TestNumber(t *testing.T) { @@ -3086,7 +3429,9 @@ func TestNumber(t *testing.T) { AssertError(t, errs, "", "", "number") i := 1 - PanicMatches(t, func() { validate.Field(i, "number") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "number") } func TestNumeric(t *testing.T) { @@ -3126,7 +3471,9 @@ func TestNumeric(t *testing.T) { AssertError(t, errs, "", "", "numeric") i := 1 - PanicMatches(t, func() { validate.Field(i, "numeric") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "numeric") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "numeric") } func TestAlphaNumeric(t *testing.T) { @@ -3140,7 +3487,9 @@ func TestAlphaNumeric(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "alphanum") - PanicMatches(t, func() { validate.Field(1, "alphanum") }, "interface conversion: interface is int, not string") + errs = validate.Field(1, "alphanum") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "alphanum") } func TestAlpha(t *testing.T) { @@ -3152,10 +3501,11 @@ func TestAlpha(t *testing.T) { s = "abc1" errs = validate.Field(s, "alpha") NotEqual(t, errs, nil) - AssertError(t, errs, "", "", "alpha") - PanicMatches(t, func() { validate.Field(1, "alpha") }, "interface conversion: interface is int, not string") + errs = validate.Field(1, "alpha") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "alpha") } func TestStructStringValidation(t *testing.T) { @@ -3372,14 +3722,14 @@ func TestStructSliceValidation(t *testing.T) { Min: []int{1, 2}, Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, MinMax: []int{1, 2, 3, 4, 5}, - OmitEmpty: []int{}, + OmitEmpty: nil, } errs := validate.Struct(tSuccess) Equal(t, errs, nil) tFail := &TestSlice{ - Required: []int{}, + Required: nil, Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, Min: []int{}, Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, @@ -3408,22 +3758,6 @@ func TestInvalidStruct(t *testing.T) { PanicMatches(t, func() { validate.Struct(s.Test) }, "value passed for validation is not a struct") } -func TestInvalidField(t *testing.T) { - s := &SubTest{ - Test: "1", - } - - PanicMatches(t, func() { validate.Field(s, "required") }, "Invalid field passed to traverseField") -} - -func TestInvalidTagField(t *testing.T) { - s := &SubTest{ - Test: "1", - } - - PanicMatches(t, func() { validate.Field(s.Test, "") }, fmt.Sprintf("Invalid validation tag on field %s", "")) -} - func TestInvalidValidatorFunction(t *testing.T) { s := &SubTest{ Test: "1",