Merge pull request #153 from bluesuncorp/v6

Merge latest changes into v7-development
pull/154/head
Dean Karn 9 years ago
commit 305c50fb58
  1. 98
      README.md
  2. 36
      baked_in.go
  3. 200
      benchmarks_test.go
  4. 36
      doc.go
  5. 48
      examples/custom/custom.go
  6. 92
      examples/simple/simple.go
  7. 77
      validator.go
  8. 646
      validator_test.go

@ -13,6 +13,7 @@ It has the following **unique** features:
- Cross Field and Cross Struct validations. - Cross Field and Cross Struct validations.
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated. - 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 Installation
------------ ------------
@ -34,7 +35,9 @@ Usage and documentation
Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs. Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs.
##### Example: ##### Examples:
Struct & Field validation
```go ```go
package main package main
@ -73,6 +76,12 @@ func main() {
validate = validator.New(config) validate = validator.New(config)
validateStruct()
validateField()
}
func validateStruct() {
address := &Address{ address := &Address{
Street: "Eavesdown Docks", Street: "Eavesdown Docks",
Planet: "Persphone", Planet: "Persphone",
@ -109,6 +118,71 @@ func main() {
// save user to database // 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 Benchmarks
@ -120,12 +194,22 @@ hurt parallel performance too much.
```go ```go
$ go test -cpu=4 -bench=. -benchmem=true $ go test -cpu=4 -bench=. -benchmem=true
PASS PASS
BenchmarkField-4 5000000 314 ns/op 16 B/op 1 allocs/op BenchmarkFieldSuccess-4 5000000 318 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTag-4 500000 2425 ns/op 20 B/op 2 allocs/op BenchmarkFieldFailure-4 5000000 316 ns/op 16 B/op 1 allocs/op
BenchmarkStructSimple-4 500000 3117 ns/op 553 B/op 14 allocs/op BenchmarkFieldCustomTypeSuccess-4 3000000 492 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleParallel-4 1000000 1149 ns/op 553 B/op 14 allocs/op BenchmarkFieldCustomTypeFailure-4 2000000 843 ns/op 416 B/op 6 allocs/op
BenchmarkStructComplex-4 100000 19580 ns/op 3230 B/op 102 allocs/op BenchmarkFieldOrTagSuccess-4 500000 2384 ns/op 20 B/op 2 allocs/op
BenchmarkStructComplexParallel-4 200000 6686 ns/op 3232 B/op 102 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 How to Contribute

@ -2,6 +2,7 @@ package validator
import ( import (
"fmt" "fmt"
"net"
"net/url" "net/url"
"reflect" "reflect"
"strconv" "strconv"
@ -64,6 +65,35 @@ var BakedInValidators = map[string]Func{
"latitude": isLatitude, "latitude": isLatitude,
"longitude": isLongitude, "longitude": isLongitude,
"ssn": isSSN, "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 { 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 { func hasValue(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
switch fieldKind { switch fieldKind {
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
case reflect.Slice, reflect.Map, reflect.Array: return !field.IsNil()
return !field.IsNil() && int64(field.Len()) > 0
default: default:
return field.IsValid() && field.Interface() != reflect.Zero(fieldType).Interface() return field.IsValid() && field.Interface() != reflect.Zero(fieldType).Interface()
} }

@ -1,20 +1,68 @@
package validator 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++ { for n := 0; n < b.N; n++ {
validate.Field("1", "len=1") 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++ { for n := 0; n < b.N; n++ {
validate.Field("rgba(0,0,0,1)", "rgb|rgba") 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 { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
@ -22,15 +70,73 @@ func BenchmarkStructSimple(b *testing.B) {
} }
validFoo := &Foo{StringValue: "Foobar", IntValue: 7} validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(validFoo) 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) 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 { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
@ -38,42 +144,32 @@ func BenchmarkStructSimpleParallel(b *testing.B) {
} }
validFoo := &Foo{StringValue: "Foobar", IntValue: 7} validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(validFoo) validate.Struct(validFoo)
validate.Struct(invalidFoo)
} }
}) })
} }
func BenchmarkStructComplex(b *testing.B) { func BenchmarkStructSimpleFailureParallel(b *testing.B) {
tFail := &TestString{ type Foo struct {
Required: "", StringValue string `validate:"min=5,max=10"`
Len: "", IntValue int `validate:"min=5,max=10"`
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",
},
} }
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{ tSuccess := &TestString{
Required: "Required", Required: "Required",
Len: "length==10", Len: "length==10",
@ -103,11 +199,10 @@ func BenchmarkStructComplex(b *testing.B) {
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tSuccess) validate.Struct(tSuccess)
validate.Struct(tFail)
} }
} }
func BenchmarkStructComplexParallel(b *testing.B) { func BenchmarkStructComplexFailure(b *testing.B) {
tFail := &TestString{ tFail := &TestString{
Required: "", 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{ tSuccess := &TestString{
Required: "Required", Required: "Required",
Len: "length==10", Len: "length==10",
@ -163,6 +265,38 @@ func BenchmarkStructComplexParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(tSuccess) 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) validate.Struct(tFail)
} }
}) })

@ -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. 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. 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 omitempty
Allows conditional validation, for example if a field is not set with Allows conditional validation, for example if a field is not set with
a value (Determined by the "required" validator) then other validation 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 [] gt=0 will be applied to []
[]string will be spared validation []string will be spared validation
required will be applied to string 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 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 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) (Usage: required)
len 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. This validates that a string value contains a valid U.S. Social Security Number.
(Usage: ssn) (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: Validator notes:
regex regex

@ -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
}

@ -1,7 +1,11 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"reflect"
sql "database/sql/driver"
"gopkg.in/bluesuncorp/validator.v6" "gopkg.in/bluesuncorp/validator.v6"
) )
@ -35,6 +39,12 @@ func main() {
validate = validator.New(config) validate = validator.New(config)
validateStruct()
validateField()
}
func validateStruct() {
address := &Address{ address := &Address{
Street: "Eavesdown Docks", Street: "Eavesdown Docks",
Planet: "Persphone", Planet: "Persphone",
@ -71,3 +81,85 @@ func main() {
// save user to database // 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
}

@ -29,6 +29,7 @@ const (
omitempty = "omitempty" omitempty = "omitempty"
skipValidationTag = "-" skipValidationTag = "-"
diveTag = "dive" diveTag = "dive"
existsTag = "exists"
fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag"
arrayIndexFieldName = "%s[%d]" arrayIndexFieldName = "%s[%d]"
mapIndexFieldName = "%s[%v]" mapIndexFieldName = "%s[%v]"
@ -45,7 +46,7 @@ var (
// returns new ValidationErrors to the pool // returns new ValidationErrors to the pool
func newValidationErrors() interface{} { func newValidationErrors() interface{} {
return map[string]*FieldError{} return ValidationErrors{}
} }
type tagCache struct { type tagCache struct {
@ -81,8 +82,15 @@ type Validate struct {
type Config struct { type Config struct {
TagName string TagName string
ValidationFuncs map[string]Func 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 // Func accepts all values needed for file and cross field validation
// topStruct = top level struct when validating by struct otherwise nil // topStruct = top level struct when validating by struct otherwise nil
// currentStruct = current level struct when validating by struct otherwise optional comparison value // 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. // New creates a new Validate instance for use.
func New(config Config) *Validate { func New(config Config) *Validate {
if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 {
config.hasCustomFuncs = true
}
return &Validate{config: config} return &Validate{config: config}
} }
@ -145,12 +158,26 @@ func (v *Validate) RegisterValidation(key string, f Func) error {
return nil 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 // 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 // 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 // validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) Field(field interface{}, tag string) ValidationErrors { func (v *Validate) Field(field interface{}, tag string) ValidationErrors {
errs := errsPool.Get().(map[string]*FieldError) errs := errsPool.Get().(ValidationErrors)
fieldVal := reflect.ValueOf(field) fieldVal := reflect.ValueOf(field)
v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "") 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 // validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) ValidationErrors { 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) topVal := reflect.ValueOf(val)
v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "") 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. // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
func (v *Validate) Struct(current interface{}) ValidationErrors { func (v *Validate) Struct(current interface{}) ValidationErrors {
errs := errsPool.Get().(map[string]*FieldError) errs := errsPool.Get().(ValidationErrors)
sv := reflect.ValueOf(current) sv := reflect.ValueOf(current)
v.tranverseStruct(sv, sv, sv, "", errs, true) v.tranverseStruct(sv, sv, sv, "", errs, true)
@ -243,12 +270,10 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
kind = current.Kind() kind = current.Kind()
} }
typ := current.Type()
// this also allows for tags 'required' and 'omitempty' to be used on // 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 // 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 // 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) { if strings.Contains(tag, omitempty) {
return return
@ -264,19 +289,35 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
param = vals[1] 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{ errs[errPrefix+name] = &FieldError{
Field: name, Field: name,
Tag: vals[0], Tag: vals[0],
Param: param, Param: param,
Value: current.Interface(), Value: current.Interface(),
Kind: kind, Kind: kind,
Type: typ, Type: current.Type(),
} }
return return
} }
// if we get here tag length is zero and we can leave
if kind == reflect.Invalid {
return
}
} }
typ := current.Type()
switch kind { switch kind {
case reflect.Struct, reflect.Interface: case reflect.Struct, reflect.Interface:
@ -302,6 +343,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
if kind == reflect.Struct { 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 // required passed validation above so stop here
// if only validating the structs existance. // if only validating the structs existance.
if strings.Contains(tag, structOnlyTag) { 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) tags, isCached := tagsCache.Get(tag)
if !isCached { if !isCached {
@ -329,7 +384,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
for _, t := range strings.Split(tag, tagSeparator) { for _, t := range strings.Split(tag, tagSeparator) {
if t == diveTag { if t == diveTag {
tags = append(tags, &tagCache{tagVals: [][]string{[]string{t}}}) tags = append(tags, &tagCache{tagVals: [][]string{{t}}})
break break
} }
@ -364,6 +419,10 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
for _, cTag := range tags { for _, cTag := range tags {
if cTag.tagVals[0][0] == existsTag {
continue
}
if cTag.tagVals[0][0] == diveTag { if cTag.tagVals[0][0] == diveTag {
dive = true dive = true
diveSubTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",") diveSubTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",")

@ -1,12 +1,15 @@
package validator package validator
import ( import (
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt" "fmt"
"path"
"reflect" "reflect"
"runtime"
"testing" "testing"
"time" "time"
. "gopkg.in/bluesuncorp/assert.v1"
) )
// NOTES: // NOTES:
@ -110,123 +113,449 @@ type TestSlice struct {
var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators}) var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators})
func IsEqual(t *testing.T, val1, val2 interface{}) bool { func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) {
v1 := reflect.ValueOf(val1)
v2 := reflect.ValueOf(val2)
if v1.Kind() == reflect.Ptr { val, ok := errs[key]
v1 = v1.Elem() 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 { if v.Name == "errorme" {
v2 = v2.Elem() panic("SQL Driver Valuer error: some kind of error")
// return nil, errors.New("some kind of error")
} }
if !v1.IsValid() && !v2.IsValid() { if len(v.Name) == 0 {
return true return nil, nil
} }
switch v1.Kind() { return v.Name, nil
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: }
if v1.IsNil() {
v1 = reflect.ValueOf(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() { return ""
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: }
if v2.IsNil() {
v2 = reflect.ValueOf(nil) 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() return ""
v2Underlying := reflect.Zero(reflect.TypeOf(v2)).Interface() }
if v1 == v1Underlying { type CustomMadeUpStruct struct {
if v2 == v2Underlying { MadeUp MadeUpCustomType `validate:"required"`
goto CASE4 OverriddenInt int `validate:"gt=1"`
} else { }
goto CASE3
} func ValidateValuerType(field reflect.Value) interface{} {
} else { if valuer, ok := field.Interface().(driver.Valuer); ok {
if v2 == v2Underlying { val, err := valuer.Value()
goto CASE2 if err != nil {
} else { // handle the error how you want
goto CASE1 return nil
} }
return val
} }
CASE1: return nil
// 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)
} }
func Equal(t *testing.T, val1, val2 interface{}) { func TestExistsValidation(t *testing.T) {
EqualSkip(t, 2, val1, val2)
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) val.Name = "Valid Name"
fmt.Printf("%s:%d %v does not equal %v\n", path.Base(file), line, val1, val2) errs = validate.Field(val, "required")
t.FailNow() 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{}) { func TestSQLValueValidation(t *testing.T) {
NotEqualSkip(t, 2, val1, val2)
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) { if test.expected == true {
_, file, line, _ := runtime.Caller(skip) if !IsEqual(errs, nil) {
fmt.Printf("%s:%d %v should not be equal %v\n", path.Base(file), line, val1, val2) t.Fatalf("Index: %d mac failed Error: %s", i, errs)
t.FailNow() }
} 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) { func TestIPValidation(t *testing.T) {
PanicMatchesSkip(t, 2, fn, matches) 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() { errs := validate.Field(test.param, "ipv6")
if r := recover(); r != nil {
err := fmt.Sprintf("%s", r)
if err != matches { if test.expected == true {
fmt.Printf("%s:%d Panic... expected [%s] received [%s]", path.Base(file), line, matches, err) if !IsEqual(errs, nil) {
t.FailNow() 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] var m map[string]string
EqualSkip(t, 2, ok, true)
NotEqualSkip(t, 2, val, nil) errs := validate.Field(m, "required")
EqualSkip(t, 2, val.Field, field) NotEqual(t, errs, nil)
EqualSkip(t, 2, val.Tag, expectedTag) 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) { func TestDatePtrValidationIssueValidation(t *testing.T) {
@ -396,9 +725,9 @@ func TestInterfaceErrValidation(t *testing.T) {
var errStructPtr2Array [][]*Inner var errStructPtr2Array [][]*Inner
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}})
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}})
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, nil})
tmsp2 := &TestMultiDimensionalStructsPtr2{ tmsp2 := &TestMultiDimensionalStructsPtr2{
Errs: errStructPtr2Array, Errs: errStructPtr2Array,
@ -478,7 +807,7 @@ func TestMapDiveValidation(t *testing.T) {
Errs map[int]Inner `validate:"gt=0,dive"` 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{ ms := &TestMapStruct{
Errs: mi, Errs: mi,
@ -514,7 +843,7 @@ func TestMapDiveValidation(t *testing.T) {
Errs map[int]*Inner `validate:"gt=0,dive,required"` 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{ msp := &TestMapStructPtr{
Errs: mip, Errs: mip,
@ -529,7 +858,7 @@ func TestMapDiveValidation(t *testing.T) {
Errs map[int]*Inner `validate:"gt=0,dive,omitempty,required"` 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{ msp2 := &TestMapStructPtr2{
Errs: mip2, Errs: mip2,
@ -616,8 +945,8 @@ func TestArrayDiveValidation(t *testing.T) {
var errStructArray [][]Inner var errStructArray [][]Inner
errStructArray = append(errStructArray, []Inner{Inner{"ok"}, Inner{""}, Inner{""}}) errStructArray = append(errStructArray, []Inner{{"ok"}, {""}, {""}})
errStructArray = append(errStructArray, []Inner{Inner{"ok"}, Inner{""}, Inner{""}}) errStructArray = append(errStructArray, []Inner{{"ok"}, {""}, {""}})
tms := &TestMultiDimensionalStructs{ tms := &TestMultiDimensionalStructs{
Errs: errStructArray, Errs: errStructArray,
@ -637,9 +966,9 @@ func TestArrayDiveValidation(t *testing.T) {
var errStructPtrArray [][]*Inner var errStructPtrArray [][]*Inner
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, {""}})
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, {""}})
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, nil})
tmsp := &TestMultiDimensionalStructsPtr{ tmsp := &TestMultiDimensionalStructsPtr{
Errs: errStructPtrArray, Errs: errStructPtrArray,
@ -662,9 +991,9 @@ func TestArrayDiveValidation(t *testing.T) {
var errStructPtr2Array [][]*Inner var errStructPtr2Array [][]*Inner
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}})
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}})
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, nil})
tmsp2 := &TestMultiDimensionalStructsPtr2{ tmsp2 := &TestMultiDimensionalStructsPtr2{
Errs: errStructPtr2Array, Errs: errStructPtr2Array,
@ -686,9 +1015,9 @@ func TestArrayDiveValidation(t *testing.T) {
var errStructPtr3Array [][]*Inner var errStructPtr3Array [][]*Inner
errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, {""}})
errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, {""}})
errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, nil})
tmsp3 := &TestMultiDimensionalStructsPtr3{ tmsp3 := &TestMultiDimensionalStructsPtr3{
Errs: errStructPtr3Array, Errs: errStructPtr3Array,
@ -864,11 +1193,11 @@ func TestSSNValidation(t *testing.T) {
errs := validate.Field(test.param, "ssn") errs := validate.Field(test.param, "ssn")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d SSN failed Error: %s", i, errs) t.Fatalf("Index: %d SSN failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d SSN failed Error: %s", i, errs) t.Fatalf("Index: %d SSN failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -898,11 +1227,11 @@ func TestLongitudeValidation(t *testing.T) {
errs := validate.Field(test.param, "longitude") errs := validate.Field(test.param, "longitude")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) t.Fatalf("Index: %d Longitude failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) t.Fatalf("Index: %d Longitude failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -932,11 +1261,11 @@ func TestLatitudeValidation(t *testing.T) {
errs := validate.Field(test.param, "latitude") errs := validate.Field(test.param, "latitude")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) t.Fatalf("Index: %d Latitude failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) t.Fatalf("Index: %d Latitude failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -972,11 +1301,11 @@ func TestDataURIValidation(t *testing.T) {
errs := validate.Field(test.param, "datauri") errs := validate.Field(test.param, "datauri")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) t.Fatalf("Index: %d DataURI failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) t.Fatalf("Index: %d DataURI failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1010,11 +1339,11 @@ func TestMultibyteValidation(t *testing.T) {
errs := validate.Field(test.param, "multibyte") errs := validate.Field(test.param, "multibyte")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1049,11 +1378,11 @@ func TestPrintableASCIIValidation(t *testing.T) {
errs := validate.Field(test.param, "printascii") errs := validate.Field(test.param, "printascii")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1087,11 +1416,11 @@ func TestASCIIValidation(t *testing.T) {
errs := validate.Field(test.param, "ascii") errs := validate.Field(test.param, "ascii")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) t.Fatalf("Index: %d ASCII failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) t.Fatalf("Index: %d ASCII failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1122,11 +1451,11 @@ func TestUUID5Validation(t *testing.T) {
errs := validate.Field(test.param, "uuid5") errs := validate.Field(test.param, "uuid5")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1156,11 +1485,11 @@ func TestUUID4Validation(t *testing.T) {
errs := validate.Field(test.param, "uuid4") errs := validate.Field(test.param, "uuid4")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1189,11 +1518,11 @@ func TestUUID3Validation(t *testing.T) {
errs := validate.Field(test.param, "uuid3") errs := validate.Field(test.param, "uuid3")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1225,11 +1554,11 @@ func TestUUIDValidation(t *testing.T) {
errs := validate.Field(test.param, "uuid") errs := validate.Field(test.param, "uuid")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID failed Error: %s", i, errs) t.Fatalf("Index: %d UUID failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID failed Error: %s", i, errs) t.Fatalf("Index: %d UUID failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1263,11 +1592,11 @@ func TestISBNValidation(t *testing.T) {
errs := validate.Field(test.param, "isbn") errs := validate.Field(test.param, "isbn")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1300,11 +1629,11 @@ func TestISBN13Validation(t *testing.T) {
errs := validate.Field(test.param, "isbn13") errs := validate.Field(test.param, "isbn13")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -1338,11 +1667,11 @@ func TestISBN10Validation(t *testing.T) {
errs := validate.Field(test.param, "isbn10") errs := validate.Field(test.param, "isbn10")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -2702,11 +3031,11 @@ func TestUrl(t *testing.T) {
errs := validate.Field(test.param, "url") errs := validate.Field(test.param, "url")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d URL failed Error: %s", i, errs) t.Fatalf("Index: %d URL failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d URL failed Error: %s", i, errs) t.Fatalf("Index: %d URL failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -2766,11 +3095,11 @@ func TestUri(t *testing.T) {
errs := validate.Field(test.param, "uri") errs := validate.Field(test.param, "uri")
if test.expected == true { if test.expected == true {
if !IsEqual(t, errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d URI failed Error: %s", i, errs) t.Fatalf("Index: %d URI failed Error: %s", i, errs)
} }
} else { } else {
if IsEqual(t, errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d URI failed Error: %s", i, errs) t.Fatalf("Index: %d URI failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs[""]
@ -2858,7 +3187,9 @@ func TestHsla(t *testing.T) {
AssertError(t, errs, "", "", "hsla") AssertError(t, errs, "", "", "hsla")
i := 1 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) { func TestHsl(t *testing.T) {
@ -2892,7 +3223,9 @@ func TestHsl(t *testing.T) {
AssertError(t, errs, "", "", "hsl") AssertError(t, errs, "", "", "hsl")
i := 1 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) { func TestRgba(t *testing.T) {
@ -2934,7 +3267,9 @@ func TestRgba(t *testing.T) {
AssertError(t, errs, "", "", "rgba") AssertError(t, errs, "", "", "rgba")
i := 1 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) { func TestRgb(t *testing.T) {
@ -2972,7 +3307,9 @@ func TestRgb(t *testing.T) {
AssertError(t, errs, "", "", "rgb") AssertError(t, errs, "", "", "rgb")
i := 1 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) { func TestEmail(t *testing.T) {
@ -3002,7 +3339,9 @@ func TestEmail(t *testing.T) {
AssertError(t, errs, "", "", "email") AssertError(t, errs, "", "", "email")
i := true 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) { func TestHexColor(t *testing.T) {
@ -3026,7 +3365,9 @@ func TestHexColor(t *testing.T) {
AssertError(t, errs, "", "", "hexcolor") AssertError(t, errs, "", "", "hexcolor")
i := true 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) { func TestHexadecimal(t *testing.T) {
@ -3041,7 +3382,9 @@ func TestHexadecimal(t *testing.T) {
AssertError(t, errs, "", "", "hexadecimal") AssertError(t, errs, "", "", "hexadecimal")
i := true 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) { func TestNumber(t *testing.T) {
@ -3086,7 +3429,9 @@ func TestNumber(t *testing.T) {
AssertError(t, errs, "", "", "number") AssertError(t, errs, "", "", "number")
i := 1 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) { func TestNumeric(t *testing.T) {
@ -3126,7 +3471,9 @@ func TestNumeric(t *testing.T) {
AssertError(t, errs, "", "", "numeric") AssertError(t, errs, "", "", "numeric")
i := 1 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) { func TestAlphaNumeric(t *testing.T) {
@ -3140,7 +3487,9 @@ func TestAlphaNumeric(t *testing.T) {
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "alphanum") 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) { func TestAlpha(t *testing.T) {
@ -3152,10 +3501,11 @@ func TestAlpha(t *testing.T) {
s = "abc1" s = "abc1"
errs = validate.Field(s, "alpha") errs = validate.Field(s, "alpha")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "alpha") 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) { func TestStructStringValidation(t *testing.T) {
@ -3372,14 +3722,14 @@ func TestStructSliceValidation(t *testing.T) {
Min: []int{1, 2}, Min: []int{1, 2},
Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
MinMax: []int{1, 2, 3, 4, 5}, MinMax: []int{1, 2, 3, 4, 5},
OmitEmpty: []int{}, OmitEmpty: nil,
} }
errs := validate.Struct(tSuccess) errs := validate.Struct(tSuccess)
Equal(t, errs, nil) Equal(t, errs, nil)
tFail := &TestSlice{ tFail := &TestSlice{
Required: []int{}, Required: nil,
Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1},
Min: []int{}, Min: []int{},
Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, 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") 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) { func TestInvalidValidatorFunction(t *testing.T) {
s := &SubTest{ s := &SubTest{
Test: "1", Test: "1",

Loading…
Cancel
Save