Merge branch 'minor-perf-1' into v8

pull/252/head v8.18.0
joeybloggs 8 years ago
commit 1d3a3d281b
  1. 61
      README.md
  2. 16
      baked_in.go
  3. 280
      cache.go
  4. 154
      util.go
  5. 380
      validator.go
  6. 150
      validator_test.go

@ -2,10 +2,10 @@ Package validator
================ ================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v8/logo.png"> <img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v8/logo.png">
[![Join the chat at https://gitter.im/bluesuncorp/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/bluesuncorp/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-8.17.3-green.svg) ![Project status](https://img.shields.io/badge/version-8.18.0-green.svg)
[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/530054/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/530054/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v8&service=github)](https://coveralls.io/github/go-playground/validator?branch=v8) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v8&service=github)](https://coveralls.io/github/go-playground/validator?branch=v8)
[![Go Report Card](http://goreportcard.com/badge/go-playground/validator)](http://goreportcard.com/report/go-playground/validator) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
[![GoDoc](https://godoc.org/gopkg.in/go-playground/validator.v8?status.svg)](https://godoc.org/gopkg.in/go-playground/validator.v8) [![GoDoc](https://godoc.org/gopkg.in/go-playground/validator.v8?status.svg)](https://godoc.org/gopkg.in/go-playground/validator.v8)
![License](https://img.shields.io/dub/l/vibe-d.svg) ![License](https://img.shields.io/dub/l/vibe-d.svg)
@ -310,36 +310,35 @@ Benchmarks
------ ------
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.5.3 darwin/amd64 ###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.5.3 darwin/amd64
```go ```go
go test -cpu=4 -bench=. -benchmem=true
PASS PASS
BenchmarkFieldSuccess-4 10000000 167 ns/op 0 B/op 0 allocs/op BenchmarkFieldSuccess-8 20000000 118 ns/op 0 B/op 0 allocs/op
BenchmarkFieldFailure-4 2000000 701 ns/op 432 B/op 4 allocs/op BenchmarkFieldFailure-8 2000000 758 ns/op 432 B/op 4 allocs/op
BenchmarkFieldDiveSuccess-4 500000 2937 ns/op 480 B/op 27 allocs/op BenchmarkFieldDiveSuccess-8 500000 2471 ns/op 464 B/op 28 allocs/op
BenchmarkFieldDiveFailure-4 500000 3536 ns/op 912 B/op 31 allocs/op BenchmarkFieldDiveFailure-8 500000 3172 ns/op 896 B/op 32 allocs/op
BenchmarkFieldCustomTypeSuccess-4 5000000 341 ns/op 32 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccess-8 5000000 300 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-4 2000000 679 ns/op 432 B/op 4 allocs/op BenchmarkFieldCustomTypeFailure-8 2000000 775 ns/op 432 B/op 4 allocs/op
BenchmarkFieldOrTagSuccess-4 1000000 1157 ns/op 16 B/op 1 allocs/op BenchmarkFieldOrTagSuccess-8 1000000 1122 ns/op 4 B/op 1 allocs/op
BenchmarkFieldOrTagFailure-4 1000000 1109 ns/op 464 B/op 6 allocs/op BenchmarkFieldOrTagFailure-8 1000000 1167 ns/op 448 B/op 6 allocs/op
BenchmarkStructLevelValidationSuccess-4 2000000 694 ns/op 176 B/op 6 allocs/op BenchmarkStructLevelValidationSuccess-8 3000000 548 ns/op 160 B/op 5 allocs/op
BenchmarkStructLevelValidationFailure-4 1000000 1311 ns/op 640 B/op 11 allocs/op BenchmarkStructLevelValidationFailure-8 3000000 558 ns/op 160 B/op 5 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-4 2000000 894 ns/op 80 B/op 5 allocs/op BenchmarkStructSimpleCustomTypeSuccess-8 2000000 623 ns/op 36 B/op 3 allocs/op
BenchmarkStructSimpleCustomTypeFailure-4 1000000 1496 ns/op 688 B/op 11 allocs/op BenchmarkStructSimpleCustomTypeFailure-8 1000000 1381 ns/op 640 B/op 9 allocs/op
BenchmarkStructPartialSuccess-4 1000000 1229 ns/op 384 B/op 10 allocs/op BenchmarkStructPartialSuccess-8 1000000 1036 ns/op 272 B/op 9 allocs/op
BenchmarkStructPartialFailure-4 1000000 1838 ns/op 832 B/op 15 allocs/op BenchmarkStructPartialFailure-8 1000000 1734 ns/op 730 B/op 14 allocs/op
BenchmarkStructExceptSuccess-4 2000000 961 ns/op 336 B/op 7 allocs/op BenchmarkStructExceptSuccess-8 2000000 888 ns/op 250 B/op 7 allocs/op
BenchmarkStructExceptFailure-4 1000000 1218 ns/op 384 B/op 10 allocs/op BenchmarkStructExceptFailure-8 1000000 1036 ns/op 272 B/op 9 allocs/op
BenchmarkStructSimpleCrossFieldSuccess-4 2000000 954 ns/op 128 B/op 6 allocs/op BenchmarkStructSimpleCrossFieldSuccess-8 2000000 773 ns/op 80 B/op 4 allocs/op
BenchmarkStructSimpleCrossFieldFailure-4 1000000 1569 ns/op 592 B/op 11 allocs/op BenchmarkStructSimpleCrossFieldFailure-8 1000000 1487 ns/op 536 B/op 9 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1588 ns/op 192 B/op 10 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 1000000 1261 ns/op 112 B/op 7 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 2217 ns/op 656 B/op 15 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 2055 ns/op 576 B/op 12 allocs/op
BenchmarkStructSimpleSuccess-4 2000000 925 ns/op 48 B/op 3 allocs/op BenchmarkStructSimpleSuccess-8 3000000 519 ns/op 4 B/op 1 allocs/op
BenchmarkStructSimpleFailure-4 1000000 1650 ns/op 688 B/op 11 allocs/op BenchmarkStructSimpleFailure-8 1000000 1429 ns/op 640 B/op 9 allocs/op
BenchmarkStructSimpleSuccessParallel-4 5000000 261 ns/op 48 B/op 3 allocs/op BenchmarkStructSimpleSuccessParallel-8 10000000 146 ns/op 4 B/op 1 allocs/op
BenchmarkStructSimpleFailureParallel-4 2000000 758 ns/op 688 B/op 11 allocs/op BenchmarkStructSimpleFailureParallel-8 2000000 551 ns/op 640 B/op 9 allocs/op
BenchmarkStructComplexSuccess-4 300000 5868 ns/op 544 B/op 32 allocs/op BenchmarkStructComplexSuccess-8 500000 3269 ns/op 244 B/op 15 allocs/op
BenchmarkStructComplexFailure-4 200000 10767 ns/op 3912 B/op 77 allocs/op BenchmarkStructComplexFailure-8 200000 8436 ns/op 3609 B/op 60 allocs/op
BenchmarkStructComplexSuccessParallel-4 1000000 1559 ns/op 544 B/op 32 allocs/op BenchmarkStructComplexSuccessParallel-8 1000000 1024 ns/op 244 B/op 15 allocs/op
BenchmarkStructComplexFailureParallel-4 500000 3747 ns/op 3912 B/op 77 allocs BenchmarkStructComplexFailureParallel-8 500000 3536 ns/op 3609 B/op 60 allocs/op
``` ```
Complimentary Software Complimentary Software

@ -271,11 +271,7 @@ func IsISBN13(v *Validate, topStruct reflect.Value, currentStructOrField reflect
checksum += factor[i%2] * int32(s[i]-'0') checksum += factor[i%2] * int32(s[i]-'0')
} }
if (int32(s[12]-'0'))-((10-(checksum%10))%10) == 0 { return (int32(s[12]-'0'))-((10-(checksum%10))%10) == 0
return true
}
return false
} }
// IsISBN10 is the validation function for validating if the field's value is a valid v10 ISBN. // IsISBN10 is the validation function for validating if the field's value is a valid v10 ISBN.
@ -301,11 +297,7 @@ func IsISBN10(v *Validate, topStruct reflect.Value, currentStructOrField reflect
checksum += 10 * int32(s[9]-'0') checksum += 10 * int32(s[9]-'0')
} }
if checksum%11 == 0 { return checksum%11 == 0
return true
}
return false
} }
// ExcludesRune is the validation function for validating that the field's value does not contain the rune specified withing the param. // ExcludesRune is the validation function for validating that the field's value does not contain the rune specified withing the param.
@ -1040,7 +1032,7 @@ func IsGt(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Val
return field.Float() > p return field.Float() > p
case reflect.Struct: case reflect.Struct:
if field.Type() == timeType || field.Type() == timePtrType { if fieldType == timeType || fieldType == timePtrType {
return field.Interface().(time.Time).After(time.Now().UTC()) return field.Interface().(time.Time).After(time.Now().UTC())
} }
@ -1255,7 +1247,7 @@ func IsLt(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Val
case reflect.Struct: case reflect.Struct:
if field.Type() == timeType || field.Type() == timePtrType { if fieldType == timeType || fieldType == timePtrType {
return field.Interface().(time.Time).Before(time.Now().UTC()) return field.Interface().(time.Time).Before(time.Now().UTC())
} }

@ -1,71 +1,263 @@
package validator package validator
import ( import (
"fmt"
"reflect" "reflect"
"strings"
"sync" "sync"
"sync/atomic"
) )
type cachedField struct { type tagType uint8
Idx int
Name string const (
AltName string typeDefault tagType = iota
CachedTag *cachedTag typeOmitEmpty
typeNoStructLevel
typeStructOnly
typeDive
typeOr
typeExists
)
type structCache struct {
lock sync.Mutex
m atomic.Value // map[reflect.Type]*cStruct
} }
type cachedStruct struct { func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
Name string c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
fields map[int]cachedField return
} }
type structCacheMap struct { func (sc *structCache) Set(key reflect.Type, value *cStruct) {
lock sync.RWMutex
m map[reflect.Type]*cachedStruct m := sc.m.Load().(map[reflect.Type]*cStruct)
nm := make(map[reflect.Type]*cStruct, len(m)+1)
for k, v := range m {
nm[k] = v
}
nm[key] = value
sc.m.Store(nm)
} }
func (s *structCacheMap) Get(key reflect.Type) (*cachedStruct, bool) { type tagCache struct {
s.lock.RLock() lock sync.Mutex
value, ok := s.m[key] m atomic.Value // map[string]*cTag
s.lock.RUnlock()
return value, ok
} }
func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) { func (tc *tagCache) Get(key string) (c *cTag, found bool) {
s.lock.Lock() c, found = tc.m.Load().(map[string]*cTag)[key]
s.m[key] = value return
s.lock.Unlock()
} }
type cachedTag struct { func (tc *tagCache) Set(key string, value *cTag) {
tag string
isOmitEmpty bool m := tc.m.Load().(map[string]*cTag)
isNoStructLevel bool
isStructOnly bool nm := make(map[string]*cTag, len(m)+1)
diveTag string for k, v := range m {
tags []*tagVals nm[k] = v
}
nm[key] = value
tc.m.Store(nm)
} }
type tagVals struct { type cStruct struct {
tagVals [][]string Name string
isOrVal bool fields map[int]*cField
isAlias bool fn StructLevelFunc
tag string
} }
type tagCacheMap struct { type cField struct {
lock sync.RWMutex Idx int
m map[string]*cachedTag Name string
AltName string
cTags *cTag
} }
func (s *tagCacheMap) Get(key string) (*cachedTag, bool) { type cTag struct {
s.lock.RLock() tag string
value, ok := s.m[key] aliasTag string
s.lock.RUnlock() actualAliasTag string
param string
hasAlias bool
typeof tagType
hasTag bool
fn Func
next *cTag
}
func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
v.structCache.lock.Lock()
defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
typ := current.Type()
// could have been multiple trying to access, but once first is done this ensures struct
// isn't parsed again.
cs, ok := v.structCache.Get(typ)
if ok {
return cs
}
cs = &cStruct{Name: sName, fields: make(map[int]*cField), fn: v.structLevelFuncs[typ]}
numFields := current.NumField()
var ctag *cTag
var fld reflect.StructField
var tag string
var customName string
for i := 0; i < numFields; i++ {
fld = typ.Field(i)
if !fld.Anonymous && fld.PkgPath != blank {
continue
}
tag = fld.Tag.Get(v.tagName)
if tag == skipValidationTag {
continue
}
customName = fld.Name
if v.fieldNameTag != blank {
return value, ok name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
// dash check is for json "-" (aka skipValidationTag) means don't output in json
if name != "" && name != skipValidationTag {
customName = name
}
}
// NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different
// and so only struct level caching can be used instead of combined with Field tag caching
if len(tag) > 0 {
ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, blank, false)
} else {
// even if field doesn't have validations need cTag for traversing to potential inner/nested
// elements of the field.
ctag = new(cTag)
}
cs.fields[i] = &cField{Idx: i, Name: fld.Name, AltName: customName, cTags: ctag}
}
v.structCache.Set(typ, cs)
return cs
} }
func (s *tagCacheMap) Set(key string, value *cachedTag) { func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
s.lock.Lock()
s.m[key] = value var t string
s.lock.Unlock() var ok bool
noAlias := len(alias) == 0
tags := strings.Split(tag, tagSeparator)
for i := 0; i < len(tags); i++ {
t = tags[i]
if noAlias {
alias = t
}
if v.hasAliasValidators {
// check map for alias and process new tags, otherwise process as usual
if tagsVal, found := v.aliasValidators[t]; found {
if i == 0 {
firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
} else {
next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
current.next, current = next, curr
}
continue
}
}
if i == 0 {
current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
firstCtag = current
} else {
current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
current = current.next
}
switch t {
case diveTag:
current.typeof = typeDive
continue
case omitempty:
current.typeof = typeOmitEmpty
continue
case structOnlyTag:
current.typeof = typeStructOnly
continue
case noStructLevelTag:
current.typeof = typeNoStructLevel
continue
case existsTag:
current.typeof = typeExists
continue
default:
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator)
for j := 0; j < len(orVals); j++ {
vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
if noAlias {
alias = vals[0]
current.aliasTag = alias
} else {
current.actualAliasTag = t
}
if j > 0 {
current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
current = current.next
}
current.tag = vals[0]
if len(current.tag) == 0 {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
}
if current.fn, ok = v.validationFuncs[current.tag]; !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, fieldName)))
}
if len(orVals) > 1 {
current.typeof = typeOr
}
if len(vals) > 1 {
current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
}
}
}
}
return
} }

@ -1,14 +1,12 @@
package validator package validator
import ( import (
"fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
) )
const ( const (
dash = "-"
blank = "" blank = ""
namespaceSeparator = "." namespaceSeparator = "."
leftBracket = "[" leftBracket = "["
@ -19,15 +17,15 @@ const (
) )
var ( var (
restrictedTags = map[string]*struct{}{ restrictedTags = map[string]struct{}{
diveTag: emptyStructPtr, diveTag: {},
existsTag: emptyStructPtr, existsTag: {},
structOnlyTag: emptyStructPtr, structOnlyTag: {},
omitempty: emptyStructPtr, omitempty: {},
skipValidationTag: emptyStructPtr, skipValidationTag: {},
utf8HexComma: emptyStructPtr, utf8HexComma: {},
utf8Pipe: emptyStructPtr, utf8Pipe: {},
noStructLevelTag: emptyStructPtr, noStructLevelTag: {},
} }
) )
@ -118,7 +116,6 @@ func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (re
ns = namespace[idx+1:] ns = namespace[idx+1:]
} else { } else {
ns = blank ns = blank
idx = len(namespace)
} }
bracketIdx := strings.Index(fld, leftBracket) bracketIdx := strings.Index(fld, leftBracket)
@ -253,136 +250,3 @@ func panicIf(err error) {
panic(err.Error()) panic(err.Error())
} }
} }
func (v *Validate) parseStruct(current reflect.Value, sName string) *cachedStruct {
typ := current.Type()
s := &cachedStruct{Name: sName, fields: map[int]cachedField{}}
numFields := current.NumField()
var fld reflect.StructField
var tag string
var customName string
for i := 0; i < numFields; i++ {
fld = typ.Field(i)
if fld.PkgPath != blank {
continue
}
tag = fld.Tag.Get(v.tagName)
if tag == skipValidationTag {
continue
}
customName = fld.Name
if v.fieldNameTag != blank {
name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
// dash check is for json "-" (aka skipValidationTag) means don't output in json
if name != "" && name != skipValidationTag {
customName = name
}
}
cTag, ok := v.tagCache.Get(tag)
if !ok {
cTag = v.parseTags(tag, fld.Name)
}
s.fields[i] = cachedField{Idx: i, Name: fld.Name, AltName: customName, CachedTag: cTag}
}
v.structCache.Set(typ, s)
return s
}
func (v *Validate) parseTags(tag, fieldName string) *cachedTag {
cTag := &cachedTag{tag: tag}
v.parseTagsRecursive(cTag, tag, fieldName, blank, false)
v.tagCache.Set(tag, cTag)
return cTag
}
func (v *Validate) parseTagsRecursive(cTag *cachedTag, tag, fieldName, alias string, isAlias bool) bool {
if tag == blank {
return true
}
for _, t := range strings.Split(tag, tagSeparator) {
if v.hasAliasValidators {
// check map for alias and process new tags, otherwise process as usual
if tagsVal, ok := v.aliasValidators[t]; ok {
leave := v.parseTagsRecursive(cTag, tagsVal, fieldName, t, true)
if leave {
return leave
}
continue
}
}
switch t {
case diveTag:
cTag.diveTag = tag
tVals := &tagVals{tagVals: [][]string{{t}}}
cTag.tags = append(cTag.tags, tVals)
return true
case omitempty:
cTag.isOmitEmpty = true
case structOnlyTag:
cTag.isStructOnly = true
case noStructLevelTag:
cTag.isNoStructLevel = true
}
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator)
tagVal := &tagVals{isAlias: isAlias, isOrVal: len(orVals) > 1, tagVals: make([][]string, len(orVals))}
cTag.tags = append(cTag.tags, tagVal)
var key string
var param string
for i, val := range orVals {
vals := strings.SplitN(val, tagKeySeparator, 2)
key = vals[0]
tagVal.tag = key
if isAlias {
tagVal.tag = alias
}
if key == blank {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
}
if len(vals) > 1 {
param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
}
tagVal.tagVals[i] = []string{key, param}
}
}
return false
}

@ -43,7 +43,7 @@ const (
var ( var (
timeType = reflect.TypeOf(time.Time{}) timeType = reflect.TypeOf(time.Time{})
timePtrType = reflect.TypeOf(&time.Time{}) timePtrType = reflect.TypeOf(&time.Time{})
emptyStructPtr = new(struct{}) defaultCField = new(cField)
) )
// StructLevel contains all of the information and helper methods // StructLevel contains all of the information and helper methods
@ -147,8 +147,8 @@ type Validate struct {
hasCustomFuncs bool hasCustomFuncs bool
hasAliasValidators bool hasAliasValidators bool
hasStructLevelFuncs bool hasStructLevelFuncs bool
tagCache *tagCacheMap tagCache *tagCache
structCache *structCacheMap structCache *structCache
errsPool *sync.Pool errsPool *sync.Pool
} }
@ -220,11 +220,17 @@ 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 {
tc := new(tagCache)
tc.m.Store(make(map[string]*cTag))
sc := new(structCache)
sc.m.Store(make(map[reflect.Type]*cStruct))
v := &Validate{ v := &Validate{
tagName: config.TagName, tagName: config.TagName,
fieldNameTag: config.FieldNameTag, fieldNameTag: config.FieldNameTag,
tagCache: &tagCacheMap{m: map[string]*cachedTag{}}, tagCache: tc,
structCache: &structCacheMap{m: map[reflect.Type]*cachedStruct{}}, structCache: sc,
errsPool: &sync.Pool{New: func() interface{} { errsPool: &sync.Pool{New: func() interface{} {
return ValidationErrors{} return ValidationErrors{}
}}} }}}
@ -332,10 +338,28 @@ func (v *Validate) RegisterAliasValidation(alias, tags string) {
func (v *Validate) Field(field interface{}, tag string) error { func (v *Validate) Field(field interface{}, tag string) error {
v.initCheck() v.initCheck()
if len(tag) == 0 || tag == skipValidationTag {
return nil
}
errs := v.errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
fieldVal := reflect.ValueOf(field) fieldVal := reflect.ValueOf(field)
v.traverseField(fieldVal, fieldVal, fieldVal, blank, blank, errs, false, tag, blank, blank, false, false, nil, nil) ctag, ok := v.tagCache.Get(tag)
if !ok {
v.tagCache.lock.Lock()
defer v.tagCache.lock.Unlock()
// could have been multiple trying to access, but once first is done this ensures tag
// isn't parsed again.
ctag, ok = v.tagCache.Get(tag)
if !ok {
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
v.tagCache.Set(tag, ctag)
}
}
v.traverseField(fieldVal, fieldVal, fieldVal, blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
if len(errs) == 0 { if len(errs) == 0 {
v.errsPool.Put(errs) v.errsPool.Put(errs)
@ -352,10 +376,28 @@ func (v *Validate) Field(field interface{}, tag string) error {
func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error { func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error {
v.initCheck() v.initCheck()
if len(tag) == 0 || tag == skipValidationTag {
return nil
}
errs := v.errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
topVal := reflect.ValueOf(val) topVal := reflect.ValueOf(val)
v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, blank, errs, false, tag, blank, blank, false, false, nil, nil) ctag, ok := v.tagCache.Get(tag)
if !ok {
v.tagCache.lock.Lock()
defer v.tagCache.lock.Unlock()
// could have been multiple trying to access, but once first is done this ensures tag
// isn't parsed again.
ctag, ok = v.tagCache.Get(tag)
if !ok {
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
v.tagCache.Set(tag, ctag)
}
}
v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
if len(errs) == 0 { if len(errs) == 0 {
v.errsPool.Put(errs) v.errsPool.Put(errs)
@ -374,7 +416,7 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) error {
sv, _ := v.ExtractType(reflect.ValueOf(current)) sv, _ := v.ExtractType(reflect.ValueOf(current))
name := sv.Type().Name() name := sv.Type().Name()
m := map[string]*struct{}{} m := map[string]struct{}{}
if fields != nil { if fields != nil {
for _, k := range fields { for _, k := range fields {
@ -390,19 +432,19 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) error {
if idx != -1 { if idx != -1 {
for idx != -1 { for idx != -1 {
key += s[:idx] key += s[:idx]
m[key] = emptyStructPtr m[key] = struct{}{}
idx2 := strings.Index(s, rightBracket) idx2 := strings.Index(s, rightBracket)
idx2++ idx2++
key += s[idx:idx2] key += s[idx:idx2]
m[key] = emptyStructPtr m[key] = struct{}{}
s = s[idx2:] s = s[idx2:]
idx = strings.Index(s, leftBracket) idx = strings.Index(s, leftBracket)
} }
} else { } else {
key += s key += s
m[key] = emptyStructPtr m[key] = struct{}{}
} }
key += namespaceSeparator key += namespaceSeparator
@ -413,7 +455,7 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) error {
errs := v.errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
v.tranverseStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, false, m, false) v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, false, m, false)
if len(errs) == 0 { if len(errs) == 0 {
v.errsPool.Put(errs) v.errsPool.Put(errs)
@ -432,15 +474,15 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) error {
sv, _ := v.ExtractType(reflect.ValueOf(current)) sv, _ := v.ExtractType(reflect.ValueOf(current))
name := sv.Type().Name() name := sv.Type().Name()
m := map[string]*struct{}{} m := map[string]struct{}{}
for _, key := range fields { for _, key := range fields {
m[name+namespaceSeparator+key] = emptyStructPtr m[name+namespaceSeparator+key] = struct{}{}
} }
errs := v.errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
v.tranverseStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, true, m, false) v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, true, m, false)
if len(errs) == 0 { if len(errs) == 0 {
v.errsPool.Put(errs) v.errsPool.Put(errs)
@ -459,7 +501,7 @@ func (v *Validate) Struct(current interface{}) error {
errs := v.errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
sv := reflect.ValueOf(current) sv := reflect.ValueOf(current)
v.tranverseStruct(sv, sv, sv, blank, blank, errs, true, false, false, nil, false) v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, false, false, nil, false)
if len(errs) == 0 { if len(errs) == 0 {
v.errsPool.Put(errs) v.errsPool.Put(errs)
@ -469,8 +511,7 @@ func (v *Validate) Struct(current interface{}) error {
return errs return errs
} }
// tranverseStruct traverses a structs fields and then passes them to be validated by traverseField func (v *Validate) ensureValidStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, isStructOnly bool) {
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]*struct{}, isStructOnly bool) {
if current.Kind() == reflect.Ptr && !current.IsNil() { if current.Kind() == reflect.Ptr && !current.IsNil() {
current = current.Elem() current = current.Elem()
@ -480,72 +521,35 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
panic("value passed for validation is not a struct") panic("value passed for validation is not a struct")
} }
// var ok bool v.tranverseStruct(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, useStructName, partial, exclude, includeExclude, nil, nil)
}
// tranverseStruct traverses a structs fields and then passes them to be validated by traverseField
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, ct *cTag) {
var ok bool
first := len(nsPrefix) == 0
typ := current.Type() typ := current.Type()
sName := typ.Name() cs, ok = v.structCache.Get(typ)
if !ok {
cs = v.extractStructCache(current, typ.Name())
}
if useStructName { if useStructName {
errPrefix += sName + namespaceSeparator errPrefix += cs.Name + namespaceSeparator
if v.fieldNameTag != blank { if len(v.fieldNameTag) != 0 {
nsPrefix += sName + namespaceSeparator nsPrefix += cs.Name + namespaceSeparator
} }
} }
// structonly tag present don't tranverseFields // structonly tag present don't tranverseFields
// but must still check and run below struct level validation // but must still check and run below struct level validation
// if present // if present
if !isStructOnly { if first || ct == nil || ct.typeof != typeStructOnly {
var fld reflect.StructField
// is anonymous struct, cannot parse or cache as
// it has no name to index by
if sName == blank {
var customName string
var ok bool
numFields := current.NumField()
for i := 0; i < numFields; i++ {
fld = typ.Field(i) for _, f := range cs.fields {
if fld.PkgPath != blank && !fld.Anonymous {
continue
}
if partial {
_, ok = includeExclude[errPrefix+fld.Name]
if (ok && exclude) || (!ok && !exclude) {
continue
}
}
customName = fld.Name
if v.fieldNameTag != blank {
name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
// dash check is for json "-" means don't output in json
if name != blank && name != dash {
customName = name
}
}
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, nsPrefix, errs, true, fld.Tag.Get(v.tagName), fld.Name, customName, partial, exclude, includeExclude, nil)
}
} else {
s, ok := v.structCache.Get(typ)
if !ok {
s = v.parseStruct(current, sName)
}
for i, f := range s.fields {
if partial { if partial {
@ -555,59 +559,47 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
continue continue
} }
} }
fld = typ.Field(i)
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, nsPrefix, errs, true, f.CachedTag.tag, fld.Name, f.AltName, partial, exclude, includeExclude, f.CachedTag) v.traverseField(topStruct, currentStruct, current.Field(f.Idx), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, f, f.cTags)
}
} }
} }
// check if any struct level validations, after all field validations already checked. // check if any struct level validations, after all field validations already checked.
if v.hasStructLevelFuncs { if cs.fn != nil {
if fn, ok := v.structLevelFuncs[current.Type()]; ok { cs.fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs})
fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs})
}
} }
} }
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options // traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, isStructField bool, tag, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) { func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, cf *cField, ct *cTag) {
if tag == skipValidationTag {
return
}
if cTag == nil {
var isCached bool
cTag, isCached = v.tagCache.Get(tag)
if !isCached {
cTag = v.parseTags(tag, name)
}
}
current, kind, nullable := v.extractTypeInternal(current, false) current, kind, nullable := v.extractTypeInternal(current, false)
var typ reflect.Type var typ reflect.Type
switch kind { switch kind {
case reflect.Ptr, reflect.Interface, reflect.Invalid: case reflect.Ptr, reflect.Interface, reflect.Invalid:
if cTag.isOmitEmpty {
if ct == nil {
return return
} }
if tag != blank { if ct.typeof == typeOmitEmpty {
return
}
if ct.hasTag {
ns := errPrefix + name ns := errPrefix + cf.Name
if kind == reflect.Invalid { if kind == reflect.Invalid {
errs[ns] = &FieldError{ errs[ns] = &FieldError{
FieldNamespace: ns, FieldNamespace: ns,
NameNamespace: nsPrefix + customName, NameNamespace: nsPrefix + cf.AltName,
Name: customName, Name: cf.AltName,
Field: name, Field: cf.Name,
Tag: cTag.tags[0].tag, Tag: ct.aliasTag,
ActualTag: cTag.tags[0].tagVals[0][0], ActualTag: ct.tag,
Param: cTag.tags[0].tagVals[0][1], Param: ct.param,
Kind: kind, Kind: kind,
} }
return return
@ -615,12 +607,12 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
errs[ns] = &FieldError{ errs[ns] = &FieldError{
FieldNamespace: ns, FieldNamespace: ns,
NameNamespace: nsPrefix + customName, NameNamespace: nsPrefix + cf.AltName,
Name: customName, Name: cf.AltName,
Field: name, Field: cf.Name,
Tag: cTag.tags[0].tag, Tag: ct.aliasTag,
ActualTag: cTag.tags[0].tagVals[0][0], ActualTag: ct.tag,
Param: cTag.tags[0].tagVals[0][1], Param: ct.param,
Value: current.Interface(), Value: current.Interface(),
Kind: kind, Kind: kind,
Type: current.Type(), Type: current.Type(),
@ -629,170 +621,162 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
return return
} }
// if we get here tag length is zero and we can leave
if kind == reflect.Invalid {
return
}
case reflect.Struct: case reflect.Struct:
typ = current.Type() typ = current.Type()
if typ != timeType { if typ != timeType {
if cTag.isNoStructLevel { if ct != nil {
ct = ct.next
}
if ct != nil && ct.typeof == typeNoStructLevel {
return return
} }
v.tranverseStruct(topStruct, current, current, errPrefix+name+namespaceSeparator, nsPrefix+customName+namespaceSeparator, errs, false, partial, exclude, includeExclude, cTag.isStructOnly) v.tranverseStruct(topStruct, current, current, errPrefix+cf.Name+namespaceSeparator, nsPrefix+cf.AltName+namespaceSeparator, errs, false, partial, exclude, includeExclude, cs, ct)
return return
} }
} }
if tag == blank { if !ct.hasTag {
return return
} }
typ = current.Type() typ = current.Type()
var dive bool OUTER:
var diveSubTag string for {
if ct == nil {
return
}
for _, valTag := range cTag.tags { switch ct.typeof {
if valTag.tagVals[0][0] == existsTag { case typeExists:
ct = ct.next
continue continue
}
if valTag.tagVals[0][0] == diveTag {
dive = true
diveSubTag = strings.TrimLeft(strings.SplitN(cTag.diveTag, diveTag, 2)[1], ",")
break
}
if valTag.tagVals[0][0] == omitempty { case typeOmitEmpty:
if !nullable && !HasValue(v, topStruct, currentStruct, current, typ, kind, blank) { if !nullable && !HasValue(v, topStruct, currentStruct, current, typ, kind, blank) {
return return
} }
ct = ct.next
continue continue
}
if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, nsPrefix, errs, valTag, name, customName) { case typeDive:
return
} ct = ct.next
}
if dive {
// traverse slice or map here // traverse slice or map here
// or panic ;) // or panic ;)
switch kind { switch kind {
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
v.traverseSlice(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil)
for i := 0; i < current.Len(); i++ {
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(arrayIndexFieldName, cf.Name, i), AltName: fmt.Sprintf(arrayIndexFieldName, cf.AltName, i)}, ct)
}
case reflect.Map: case reflect.Map:
v.traverseMap(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil) for _, key := range current.MapKeys() {
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(mapIndexFieldName, cf.Name, key.Interface()), AltName: fmt.Sprintf(mapIndexFieldName, cf.AltName, key.Interface())}, ct)
}
default: default:
// throw error, if not a slice or map then should not have gotten here // throw error, if not a slice or map then should not have gotten here
// bad dive tag // bad dive tag
panic("dive error! can't dive on a non slice or map") panic("dive error! can't dive on a non slice or map")
} }
}
}
// traverseSlice traverses a Slice or Array's elements and passes them to traverseField for validation
func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, tag, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) {
for i := 0; i < current.Len(); i++ {
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, nsPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i), fmt.Sprintf(arrayIndexFieldName, customName, i), partial, exclude, includeExclude, cTag)
}
}
// traverseMap traverses a map's elements and passes them to traverseField for validation return
func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, tag, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) {
for _, key := range current.MapKeys() { case typeOr:
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, nsPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface()), fmt.Sprintf(mapIndexFieldName, customName, key.Interface()), partial, exclude, includeExclude, cTag)
}
}
// validateField validates a field based on the provided tag's key and param values and returns true if there is an error or false if all ok errTag := blank
func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, nsPrefix string, errs ValidationErrors, valTag *tagVals, name, customName string) bool {
var valFunc Func for {
var ok bool
if valTag.isOrVal { if ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
errTag := blank // drain rest of the 'or' values, then continue or leave
for {
for _, val := range valTag.tagVals { ct = ct.next
valFunc, ok = v.validationFuncs[val[0]] if ct == nil {
if !ok { return
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
} }
if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) { if ct.typeof != typeOr {
return false continue OUTER
} }
errTag += orSeparator + val[0]
} }
}
errTag += orSeparator + ct.tag
ns := errPrefix + name if ct.next == nil {
// if we get here, no valid 'or' value and no more tags
if valTag.isAlias { ns := errPrefix + cf.Name
if ct.hasAlias {
errs[ns] = &FieldError{ errs[ns] = &FieldError{
FieldNamespace: ns, FieldNamespace: ns,
NameNamespace: nsPrefix + customName, NameNamespace: nsPrefix + cf.AltName,
Name: customName, Name: cf.AltName,
Field: name, Field: cf.Name,
Tag: valTag.tag, Tag: ct.aliasTag,
ActualTag: errTag[1:], ActualTag: ct.actualAliasTag,
Value: current.Interface(), Value: current.Interface(),
Type: currentType, Type: typ,
Kind: currentKind, Kind: kind,
} }
} else { } else {
errs[errPrefix+name] = &FieldError{ errs[errPrefix+cf.Name] = &FieldError{
FieldNamespace: ns, FieldNamespace: ns,
NameNamespace: nsPrefix + customName, NameNamespace: nsPrefix + cf.AltName,
Name: customName, Name: cf.AltName,
Field: name, Field: cf.Name,
Tag: errTag[1:], Tag: errTag[1:],
ActualTag: errTag[1:], ActualTag: errTag[1:],
Value: current.Interface(), Value: current.Interface(),
Type: currentType, Type: typ,
Kind: currentKind, Kind: kind,
} }
} }
return true return
} }
valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]] ct = ct.next
if !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
} }
if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, valTag.tagVals[0][1]) { default:
return false if !ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
}
ns := errPrefix + name ns := errPrefix + cf.Name
errs[ns] = &FieldError{ errs[ns] = &FieldError{
FieldNamespace: ns, FieldNamespace: ns,
NameNamespace: nsPrefix + customName, NameNamespace: nsPrefix + cf.AltName,
Name: customName, Name: cf.AltName,
Field: name, Field: cf.Name,
Tag: valTag.tag, Tag: ct.aliasTag,
ActualTag: valTag.tagVals[0][0], ActualTag: ct.tag,
Value: current.Interface(), Value: current.Interface(),
Param: valTag.tagVals[0][1], Param: ct.param,
Type: currentType, Type: typ,
Kind: currentKind, Kind: kind,
}
return
} }
return true ct = ct.next
}
}
} }

@ -365,7 +365,7 @@ func TestAnonymous(t *testing.T) {
B string `validate:"required" json:"BEE"` B string `validate:"required" json:"BEE"`
} }
anonymousC struct { anonymousC struct {
c string `validate:"required" json:"SEE"` c string `validate:"required"`
} }
} }
@ -381,7 +381,7 @@ func TestAnonymous(t *testing.T) {
B: "", B: "",
}, },
anonymousC: struct { anonymousC: struct {
c string `validate:"required" json:"SEE"` c string `validate:"required"`
}{ }{
c: "", c: "",
}, },
@ -398,7 +398,7 @@ func TestAnonymous(t *testing.T) {
Equal(t, errs["Test.AnonymousB.B"].Name, "BEE") Equal(t, errs["Test.AnonymousB.B"].Name, "BEE")
s := struct { s := struct {
c string `validate:"required" json:"SEE"` c string `validate:"required"`
}{ }{
c: "", c: "",
} }
@ -407,6 +407,42 @@ func TestAnonymous(t *testing.T) {
Equal(t, err, nil) Equal(t, err, nil)
} }
func TestAnonymousSameStructDifferentTags(t *testing.T) {
v2 := New(&Config{TagName: "validate", FieldNameTag: "json"})
type Test struct {
A interface{}
}
tst := &Test{
A: struct {
A string `validate:"required"`
}{
A: "",
},
}
err := v2.Struct(tst)
NotEqual(t, err, nil)
errs := err.(ValidationErrors)
Equal(t, len(errs), 1)
AssertError(t, errs, "Test.A.A", "A", "required")
tst = &Test{
A: struct {
A string `validate:"omitempty,required"`
}{
A: "",
},
}
err = v2.Struct(tst)
Equal(t, err, nil)
}
func TestStructLevelReturnValidationErrors(t *testing.T) { func TestStructLevelReturnValidationErrors(t *testing.T) {
config := &Config{ config := &Config{
TagName: "validate", TagName: "validate",
@ -576,6 +612,7 @@ func TestAliasTags(t *testing.T) {
validate.RegisterAliasValidation("req", "required,dive,iscolor") validate.RegisterAliasValidation("req", "required,dive,iscolor")
arr := []string{"val1", "#fff", "#000"} arr := []string{"val1", "#fff", "#000"}
errs = validate.Field(arr, "req") errs = validate.Field(arr, "req")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "[0]", "[0]", "iscolor") AssertError(t, errs, "[0]", "[0]", "iscolor")
@ -1733,7 +1770,7 @@ func TestMACValidation(t *testing.T) {
errs := validate.Field(test.param, "mac") errs := validate.Field(test.param, "mac")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d mac failed Error: %s", i, errs) t.Fatalf("Index: %d mac failed Error: %s", i, errs)
} }
@ -1772,7 +1809,7 @@ func TestIPValidation(t *testing.T) {
errs := validate.Field(test.param, "ip") errs := validate.Field(test.param, "ip")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ip failed Error: %s", i, errs) t.Fatalf("Index: %d ip failed Error: %s", i, errs)
} }
@ -1810,7 +1847,7 @@ func TestIPv6Validation(t *testing.T) {
errs := validate.Field(test.param, "ipv6") errs := validate.Field(test.param, "ipv6")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs)
} }
@ -1848,7 +1885,7 @@ func TestIPv4Validation(t *testing.T) {
errs := validate.Field(test.param, "ipv4") errs := validate.Field(test.param, "ipv4")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs)
} }
@ -1889,7 +1926,7 @@ func TestCIDRValidation(t *testing.T) {
errs := validate.Field(test.param, "cidr") errs := validate.Field(test.param, "cidr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d cidr failed Error: %s", i, errs) t.Fatalf("Index: %d cidr failed Error: %s", i, errs)
} }
@ -1930,7 +1967,7 @@ func TestCIDRv6Validation(t *testing.T) {
errs := validate.Field(test.param, "cidrv6") errs := validate.Field(test.param, "cidrv6")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d cidrv6 failed Error: %s", i, errs) t.Fatalf("Index: %d cidrv6 failed Error: %s", i, errs)
} }
@ -1971,7 +2008,7 @@ func TestCIDRv4Validation(t *testing.T) {
errs := validate.Field(test.param, "cidrv4") errs := validate.Field(test.param, "cidrv4")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d cidrv4 failed Error: %s", i, errs) t.Fatalf("Index: %d cidrv4 failed Error: %s", i, errs)
} }
@ -2003,7 +2040,7 @@ func TestTCPAddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "tcp_addr") errs := validate.Field(test.param, "tcp_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d tcp_addr failed Error: %s", i, errs) t.Fatalf("Index: %d tcp_addr failed Error: %s", i, errs)
} }
@ -2035,7 +2072,7 @@ func TestTCP6AddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "tcp6_addr") errs := validate.Field(test.param, "tcp6_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d tcp6_addr failed Error: %s", i, errs) t.Fatalf("Index: %d tcp6_addr failed Error: %s", i, errs)
} }
@ -2067,7 +2104,7 @@ func TestTCP4AddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "tcp4_addr") errs := validate.Field(test.param, "tcp4_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d tcp4_addr failed Error: %s", i, errs) t.Fatalf("Index: %d tcp4_addr failed Error: %s", i, errs)
} }
@ -2100,7 +2137,7 @@ func TestUDPAddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "udp_addr") errs := validate.Field(test.param, "udp_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d udp_addr failed Error: %s", i, errs) t.Fatalf("Index: %d udp_addr failed Error: %s", i, errs)
} }
@ -2132,7 +2169,7 @@ func TestUDP6AddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "udp6_addr") errs := validate.Field(test.param, "udp6_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d udp6_addr failed Error: %s", i, errs) t.Fatalf("Index: %d udp6_addr failed Error: %s", i, errs)
} }
@ -2164,7 +2201,7 @@ func TestUDP4AddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "udp4_addr") errs := validate.Field(test.param, "udp4_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d udp4_addr failed Error: %s", i, errs) t.Fatalf("Index: %d udp4_addr failed Error: %s", i, errs)
} }
@ -2197,7 +2234,7 @@ func TestIPAddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "ip_addr") errs := validate.Field(test.param, "ip_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ip_addr failed Error: %s", i, errs) t.Fatalf("Index: %d ip_addr failed Error: %s", i, errs)
} }
@ -2229,7 +2266,7 @@ func TestIP6AddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "ip6_addr") errs := validate.Field(test.param, "ip6_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ip6_addr failed Error: %s", i, errs) t.Fatalf("Index: %d ip6_addr failed Error: %s", i, errs)
} }
@ -2261,7 +2298,7 @@ func TestIP4AddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "ip4_addr") errs := validate.Field(test.param, "ip4_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d ip4_addr failed Error: %s", i, errs) t.Fatalf("Index: %d ip4_addr failed Error: %s", i, errs)
} }
@ -2290,7 +2327,7 @@ func TestUnixAddrValidation(t *testing.T) {
for i, test := range tests { for i, test := range tests {
errs := validate.Field(test.param, "unix_addr") errs := validate.Field(test.param, "unix_addr")
if test.expected == true { if test.expected {
if !IsEqual(errs, nil) { if !IsEqual(errs, nil) {
t.Fatalf("Index: %d unix_addr failed Error: %s", i, errs) t.Fatalf("Index: %d unix_addr failed Error: %s", i, errs)
} }
@ -2369,6 +2406,9 @@ func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) {
errs = validate.Field(iface, "") errs = validate.Field(iface, "")
Equal(t, errs, nil) Equal(t, errs, nil)
errs = validate.FieldWithValue(nil, iface, "")
Equal(t, errs, nil)
var f func(string) var f func(string)
errs = validate.Field(f, "required") errs = validate.Field(f, "required")
@ -3018,7 +3058,7 @@ func TestSSNValidation(t *testing.T) {
errs := validate.Field(test.param, "ssn") errs := validate.Field(test.param, "ssn")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3052,7 +3092,7 @@ func TestLongitudeValidation(t *testing.T) {
errs := validate.Field(test.param, "longitude") errs := validate.Field(test.param, "longitude")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3086,7 +3126,7 @@ func TestLatitudeValidation(t *testing.T) {
errs := validate.Field(test.param, "latitude") errs := validate.Field(test.param, "latitude")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3126,7 +3166,7 @@ func TestDataURIValidation(t *testing.T) {
errs := validate.Field(test.param, "datauri") errs := validate.Field(test.param, "datauri")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3164,7 +3204,7 @@ func TestMultibyteValidation(t *testing.T) {
errs := validate.Field(test.param, "multibyte") errs := validate.Field(test.param, "multibyte")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3203,7 +3243,7 @@ func TestPrintableASCIIValidation(t *testing.T) {
errs := validate.Field(test.param, "printascii") errs := validate.Field(test.param, "printascii")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3241,7 +3281,7 @@ func TestASCIIValidation(t *testing.T) {
errs := validate.Field(test.param, "ascii") errs := validate.Field(test.param, "ascii")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3276,7 +3316,7 @@ func TestUUID5Validation(t *testing.T) {
errs := validate.Field(test.param, "uuid5") errs := validate.Field(test.param, "uuid5")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3310,7 +3350,7 @@ func TestUUID4Validation(t *testing.T) {
errs := validate.Field(test.param, "uuid4") errs := validate.Field(test.param, "uuid4")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3343,7 +3383,7 @@ func TestUUID3Validation(t *testing.T) {
errs := validate.Field(test.param, "uuid3") errs := validate.Field(test.param, "uuid3")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3379,7 +3419,7 @@ func TestUUIDValidation(t *testing.T) {
errs := validate.Field(test.param, "uuid") errs := validate.Field(test.param, "uuid")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3417,7 +3457,7 @@ func TestISBNValidation(t *testing.T) {
errs := validate.Field(test.param, "isbn") errs := validate.Field(test.param, "isbn")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3454,7 +3494,7 @@ func TestISBN13Validation(t *testing.T) {
errs := validate.Field(test.param, "isbn13") errs := validate.Field(test.param, "isbn13")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -3492,7 +3532,7 @@ func TestISBN10Validation(t *testing.T) {
errs := validate.Field(test.param, "isbn10") errs := validate.Field(test.param, "isbn10")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -4993,7 +5033,7 @@ func TestUrl(t *testing.T) {
errs := validate.Field(test.param, "url") errs := validate.Field(test.param, "url")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -5057,7 +5097,7 @@ func TestUri(t *testing.T) {
errs := validate.Field(test.param, "uri") errs := validate.Field(test.param, "uri")
if test.expected == true { if test.expected {
if !IsEqual(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)
} }
@ -5481,6 +5521,16 @@ func TestAlpha(t *testing.T) {
errs := validate.Field(s, "alpha") errs := validate.Field(s, "alpha")
Equal(t, errs, nil) Equal(t, errs, nil)
s = "abc®"
errs = validate.Field(s, "alpha")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "alpha")
s = "abc÷"
errs = validate.Field(s, "alpha")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "alpha")
s = "abc1" s = "abc1"
errs = validate.Field(s, "alpha") errs = validate.Field(s, "alpha")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
@ -5489,6 +5539,7 @@ func TestAlpha(t *testing.T) {
errs = validate.Field(1, "alpha") errs = validate.Field(1, "alpha")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "alpha") AssertError(t, errs, "", "", "alpha")
} }
func TestStructStringValidation(t *testing.T) { func TestStructStringValidation(t *testing.T) {
@ -5776,6 +5827,33 @@ func TestCustomFieldName(t *testing.T) {
Equal(t, errs["A.E"].Name, "E") Equal(t, errs["A.E"].Name, "E")
} }
func TestMutipleRecursiveExtractStructCache(t *testing.T) {
type Recursive struct {
Field *string `validate:"exists,required,len=5,ne=string"`
}
var test Recursive
current := reflect.ValueOf(test)
name := "Recursive"
proceed := make(chan struct{})
sc := validate.extractStructCache(current, name)
ptr := fmt.Sprintf("%p", sc)
for i := 0; i < 100; i++ {
go func() {
<-proceed
sc := validate.extractStructCache(current, name)
Equal(t, ptr, fmt.Sprintf("%p", sc))
}()
}
close(proceed)
}
// Thanks @robbrockbank, see https://github.com/go-playground/validator/issues/249 // Thanks @robbrockbank, see https://github.com/go-playground/validator/issues/249
func TestPointerAndOmitEmpty(t *testing.T) { func TestPointerAndOmitEmpty(t *testing.T) {

Loading…
Cancel
Save