diff --git a/README.md b/README.md index 21693f6..32e042c 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ Package validator ================ [![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) [![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) ![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 ```go -go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkFieldSuccess-4 10000000 167 ns/op 0 B/op 0 allocs/op -BenchmarkFieldFailure-4 2000000 701 ns/op 432 B/op 4 allocs/op -BenchmarkFieldDiveSuccess-4 500000 2937 ns/op 480 B/op 27 allocs/op -BenchmarkFieldDiveFailure-4 500000 3536 ns/op 912 B/op 31 allocs/op -BenchmarkFieldCustomTypeSuccess-4 5000000 341 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 2000000 679 ns/op 432 B/op 4 allocs/op -BenchmarkFieldOrTagSuccess-4 1000000 1157 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1109 ns/op 464 B/op 6 allocs/op -BenchmarkStructLevelValidationSuccess-4 2000000 694 ns/op 176 B/op 6 allocs/op -BenchmarkStructLevelValidationFailure-4 1000000 1311 ns/op 640 B/op 11 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 2000000 894 ns/op 80 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1496 ns/op 688 B/op 11 allocs/op -BenchmarkStructPartialSuccess-4 1000000 1229 ns/op 384 B/op 10 allocs/op -BenchmarkStructPartialFailure-4 1000000 1838 ns/op 832 B/op 15 allocs/op -BenchmarkStructExceptSuccess-4 2000000 961 ns/op 336 B/op 7 allocs/op -BenchmarkStructExceptFailure-4 1000000 1218 ns/op 384 B/op 10 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-4 2000000 954 ns/op 128 B/op 6 allocs/op -BenchmarkStructSimpleCrossFieldFailure-4 1000000 1569 ns/op 592 B/op 11 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1588 ns/op 192 B/op 10 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 2217 ns/op 656 B/op 15 allocs/op -BenchmarkStructSimpleSuccess-4 2000000 925 ns/op 48 B/op 3 allocs/op -BenchmarkStructSimpleFailure-4 1000000 1650 ns/op 688 B/op 11 allocs/op -BenchmarkStructSimpleSuccessParallel-4 5000000 261 ns/op 48 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 758 ns/op 688 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 300000 5868 ns/op 544 B/op 32 allocs/op -BenchmarkStructComplexFailure-4 200000 10767 ns/op 3912 B/op 77 allocs/op -BenchmarkStructComplexSuccessParallel-4 1000000 1559 ns/op 544 B/op 32 allocs/op -BenchmarkStructComplexFailureParallel-4 500000 3747 ns/op 3912 B/op 77 allocs +BenchmarkFieldSuccess-8 20000000 118 ns/op 0 B/op 0 allocs/op +BenchmarkFieldFailure-8 2000000 758 ns/op 432 B/op 4 allocs/op +BenchmarkFieldDiveSuccess-8 500000 2471 ns/op 464 B/op 28 allocs/op +BenchmarkFieldDiveFailure-8 500000 3172 ns/op 896 B/op 32 allocs/op +BenchmarkFieldCustomTypeSuccess-8 5000000 300 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-8 2000000 775 ns/op 432 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-8 1000000 1122 ns/op 4 B/op 1 allocs/op +BenchmarkFieldOrTagFailure-8 1000000 1167 ns/op 448 B/op 6 allocs/op +BenchmarkStructLevelValidationSuccess-8 3000000 548 ns/op 160 B/op 5 allocs/op +BenchmarkStructLevelValidationFailure-8 3000000 558 ns/op 160 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-8 2000000 623 ns/op 36 B/op 3 allocs/op +BenchmarkStructSimpleCustomTypeFailure-8 1000000 1381 ns/op 640 B/op 9 allocs/op +BenchmarkStructPartialSuccess-8 1000000 1036 ns/op 272 B/op 9 allocs/op +BenchmarkStructPartialFailure-8 1000000 1734 ns/op 730 B/op 14 allocs/op +BenchmarkStructExceptSuccess-8 2000000 888 ns/op 250 B/op 7 allocs/op +BenchmarkStructExceptFailure-8 1000000 1036 ns/op 272 B/op 9 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-8 2000000 773 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossFieldFailure-8 1000000 1487 ns/op 536 B/op 9 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 1000000 1261 ns/op 112 B/op 7 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 2055 ns/op 576 B/op 12 allocs/op +BenchmarkStructSimpleSuccess-8 3000000 519 ns/op 4 B/op 1 allocs/op +BenchmarkStructSimpleFailure-8 1000000 1429 ns/op 640 B/op 9 allocs/op +BenchmarkStructSimpleSuccessParallel-8 10000000 146 ns/op 4 B/op 1 allocs/op +BenchmarkStructSimpleFailureParallel-8 2000000 551 ns/op 640 B/op 9 allocs/op +BenchmarkStructComplexSuccess-8 500000 3269 ns/op 244 B/op 15 allocs/op +BenchmarkStructComplexFailure-8 200000 8436 ns/op 3609 B/op 60 allocs/op +BenchmarkStructComplexSuccessParallel-8 1000000 1024 ns/op 244 B/op 15 allocs/op +BenchmarkStructComplexFailureParallel-8 500000 3536 ns/op 3609 B/op 60 allocs/op ``` Complimentary Software diff --git a/baked_in.go b/baked_in.go index 3dcb0de..cd922d2 100644 --- a/baked_in.go +++ b/baked_in.go @@ -271,11 +271,7 @@ func IsISBN13(v *Validate, topStruct reflect.Value, currentStructOrField reflect checksum += factor[i%2] * int32(s[i]-'0') } - if (int32(s[12]-'0'))-((10-(checksum%10))%10) == 0 { - return true - } - - return false + return (int32(s[12]-'0'))-((10-(checksum%10))%10) == 0 } // 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') } - if checksum%11 == 0 { - return true - } - - return false + return checksum%11 == 0 } // 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 case reflect.Struct: - if field.Type() == timeType || field.Type() == timePtrType { + if fieldType == timeType || fieldType == timePtrType { 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: - if field.Type() == timeType || field.Type() == timePtrType { + if fieldType == timeType || fieldType == timePtrType { return field.Interface().(time.Time).Before(time.Now().UTC()) } diff --git a/cache.go b/cache.go index 289226e..76670c1 100644 --- a/cache.go +++ b/cache.go @@ -1,71 +1,263 @@ package validator import ( + "fmt" "reflect" + "strings" "sync" + "sync/atomic" ) -type cachedField struct { - Idx int - Name string - AltName string - CachedTag *cachedTag +type tagType uint8 + +const ( + typeDefault tagType = iota + typeOmitEmpty + typeNoStructLevel + typeStructOnly + typeDive + typeOr + typeExists +) + +type structCache struct { + lock sync.Mutex + m atomic.Value // map[reflect.Type]*cStruct } -type cachedStruct struct { - Name string - fields map[int]cachedField +func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) { + c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key] + return +} + +func (sc *structCache) Set(key reflect.Type, value *cStruct) { + + 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) } -type structCacheMap struct { - lock sync.RWMutex - m map[reflect.Type]*cachedStruct +type tagCache struct { + lock sync.Mutex + m atomic.Value // map[string]*cTag } -func (s *structCacheMap) Get(key reflect.Type) (*cachedStruct, bool) { - s.lock.RLock() - value, ok := s.m[key] - s.lock.RUnlock() - return value, ok +func (tc *tagCache) Get(key string) (c *cTag, found bool) { + c, found = tc.m.Load().(map[string]*cTag)[key] + return } -func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) { - s.lock.Lock() - s.m[key] = value - s.lock.Unlock() +func (tc *tagCache) Set(key string, value *cTag) { + + m := tc.m.Load().(map[string]*cTag) + + nm := make(map[string]*cTag, len(m)+1) + for k, v := range m { + nm[k] = v + } + nm[key] = value + tc.m.Store(nm) } -type cachedTag struct { - tag string - isOmitEmpty bool - isNoStructLevel bool - isStructOnly bool - diveTag string - tags []*tagVals +type cStruct struct { + Name string + fields map[int]*cField + fn StructLevelFunc } -type tagVals struct { - tagVals [][]string - isOrVal bool - isAlias bool - tag string +type cField struct { + Idx int + Name string + AltName string + cTags *cTag } -type tagCacheMap struct { - lock sync.RWMutex - m map[string]*cachedTag +type cTag struct { + tag string + aliasTag string + actualAliasTag string + param string + hasAlias bool + typeof tagType + hasTag bool + fn Func + next *cTag } -func (s *tagCacheMap) Get(key string) (*cachedTag, bool) { - s.lock.RLock() - value, ok := s.m[key] - s.lock.RUnlock() +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 { + + 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) + } - return value, ok + 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) { - s.lock.Lock() - s.m[key] = value - s.lock.Unlock() +func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) { + + var t string + 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 } diff --git a/util.go b/util.go index 417a9fe..a7357e7 100644 --- a/util.go +++ b/util.go @@ -1,14 +1,12 @@ package validator import ( - "fmt" "reflect" "strconv" "strings" ) const ( - dash = "-" blank = "" namespaceSeparator = "." leftBracket = "[" @@ -19,15 +17,15 @@ const ( ) var ( - restrictedTags = map[string]*struct{}{ - diveTag: emptyStructPtr, - existsTag: emptyStructPtr, - structOnlyTag: emptyStructPtr, - omitempty: emptyStructPtr, - skipValidationTag: emptyStructPtr, - utf8HexComma: emptyStructPtr, - utf8Pipe: emptyStructPtr, - noStructLevelTag: emptyStructPtr, + restrictedTags = map[string]struct{}{ + diveTag: {}, + existsTag: {}, + structOnlyTag: {}, + omitempty: {}, + skipValidationTag: {}, + utf8HexComma: {}, + utf8Pipe: {}, + noStructLevelTag: {}, } ) @@ -118,7 +116,6 @@ func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (re ns = namespace[idx+1:] } else { ns = blank - idx = len(namespace) } bracketIdx := strings.Index(fld, leftBracket) @@ -253,136 +250,3 @@ func panicIf(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 -} diff --git a/validator.go b/validator.go index 2df68f0..2b1d716 100644 --- a/validator.go +++ b/validator.go @@ -41,9 +41,9 @@ const ( ) var ( - timeType = reflect.TypeOf(time.Time{}) - timePtrType = reflect.TypeOf(&time.Time{}) - emptyStructPtr = new(struct{}) + timeType = reflect.TypeOf(time.Time{}) + timePtrType = reflect.TypeOf(&time.Time{}) + defaultCField = new(cField) ) // StructLevel contains all of the information and helper methods @@ -147,8 +147,8 @@ type Validate struct { hasCustomFuncs bool hasAliasValidators bool hasStructLevelFuncs bool - tagCache *tagCacheMap - structCache *structCacheMap + tagCache *tagCache + structCache *structCache errsPool *sync.Pool } @@ -220,11 +220,17 @@ type FieldError struct { // New creates a new Validate instance for use. 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{ tagName: config.TagName, fieldNameTag: config.FieldNameTag, - tagCache: &tagCacheMap{m: map[string]*cachedTag{}}, - structCache: &structCacheMap{m: map[reflect.Type]*cachedStruct{}}, + tagCache: tc, + structCache: sc, errsPool: &sync.Pool{New: func() interface{} { return ValidationErrors{} }}} @@ -332,10 +338,28 @@ func (v *Validate) RegisterAliasValidation(alias, tags string) { func (v *Validate) Field(field interface{}, tag string) error { v.initCheck() + if len(tag) == 0 || tag == skipValidationTag { + return nil + } + errs := v.errsPool.Get().(ValidationErrors) 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 { 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 { v.initCheck() + if len(tag) == 0 || tag == skipValidationTag { + return nil + } + errs := v.errsPool.Get().(ValidationErrors) 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 { v.errsPool.Put(errs) @@ -374,7 +416,7 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) error { sv, _ := v.ExtractType(reflect.ValueOf(current)) name := sv.Type().Name() - m := map[string]*struct{}{} + m := map[string]struct{}{} if fields != nil { for _, k := range fields { @@ -390,19 +432,19 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) error { if idx != -1 { for idx != -1 { key += s[:idx] - m[key] = emptyStructPtr + m[key] = struct{}{} idx2 := strings.Index(s, rightBracket) idx2++ key += s[idx:idx2] - m[key] = emptyStructPtr + m[key] = struct{}{} s = s[idx2:] idx = strings.Index(s, leftBracket) } } else { key += s - m[key] = emptyStructPtr + m[key] = struct{}{} } key += namespaceSeparator @@ -413,7 +455,7 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) error { 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 { v.errsPool.Put(errs) @@ -432,15 +474,15 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) error { sv, _ := v.ExtractType(reflect.ValueOf(current)) name := sv.Type().Name() - m := map[string]*struct{}{} + m := map[string]struct{}{} for _, key := range fields { - m[name+namespaceSeparator+key] = emptyStructPtr + m[name+namespaceSeparator+key] = struct{}{} } 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 { v.errsPool.Put(errs) @@ -459,7 +501,7 @@ func (v *Validate) Struct(current interface{}) error { errs := v.errsPool.Get().(ValidationErrors) 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 { v.errsPool.Put(errs) @@ -469,8 +511,7 @@ func (v *Validate) Struct(current interface{}) error { return errs } -// 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{}, isStructOnly bool) { +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) { if current.Kind() == reflect.Ptr && !current.IsNil() { current = current.Elem() @@ -480,134 +521,85 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec 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() - sName := typ.Name() + cs, ok = v.structCache.Get(typ) + if !ok { + cs = v.extractStructCache(current, typ.Name()) + } if useStructName { - errPrefix += sName + namespaceSeparator + errPrefix += cs.Name + namespaceSeparator - if v.fieldNameTag != blank { - nsPrefix += sName + namespaceSeparator + if len(v.fieldNameTag) != 0 { + nsPrefix += cs.Name + namespaceSeparator } } // structonly tag present don't tranverseFields // but must still check and run below struct level validation // if present - if !isStructOnly { + if first || ct == nil || ct.typeof != typeStructOnly { - var fld reflect.StructField + for _, f := range cs.fields { - // is anonymous struct, cannot parse or cache as - // it has no name to index by - if sName == blank { + if partial { - var customName string - var ok bool - numFields := current.NumField() + _, ok = includeExclude[errPrefix+f.Name] - for i := 0; i < numFields; i++ { - - fld = typ.Field(i) - - if fld.PkgPath != blank && !fld.Anonymous { + if (ok && exclude) || (!ok && !exclude) { 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 { - - _, ok = includeExclude[errPrefix+f.Name] - - if (ok && exclude) || (!ok && !exclude) { - 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. - if v.hasStructLevelFuncs { - if fn, ok := v.structLevelFuncs[current.Type()]; ok { - fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs}) - } + if cs.fn != nil { + cs.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 -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) { - - if tag == skipValidationTag { - return - } - - if cTag == nil { - var isCached bool - cTag, isCached = v.tagCache.Get(tag) - - if !isCached { - cTag = v.parseTags(tag, name) - } - } +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) { current, kind, nullable := v.extractTypeInternal(current, false) var typ reflect.Type switch kind { case reflect.Ptr, reflect.Interface, reflect.Invalid: - if cTag.isOmitEmpty { + + if ct == nil { return } - if tag != blank { + if ct.typeof == typeOmitEmpty { + return + } - ns := errPrefix + name + if ct.hasTag { + + ns := errPrefix + cf.Name if kind == reflect.Invalid { errs[ns] = &FieldError{ FieldNamespace: ns, - NameNamespace: nsPrefix + customName, - Name: customName, - Field: name, - Tag: cTag.tags[0].tag, - ActualTag: cTag.tags[0].tagVals[0][0], - Param: cTag.tags[0].tagVals[0][1], + NameNamespace: nsPrefix + cf.AltName, + Name: cf.AltName, + Field: cf.Name, + Tag: ct.aliasTag, + ActualTag: ct.tag, + Param: ct.param, Kind: kind, } return @@ -615,12 +607,12 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. errs[ns] = &FieldError{ FieldNamespace: ns, - NameNamespace: nsPrefix + customName, - Name: customName, - Field: name, - Tag: cTag.tags[0].tag, - ActualTag: cTag.tags[0].tagVals[0][0], - Param: cTag.tags[0].tagVals[0][1], + NameNamespace: nsPrefix + cf.AltName, + Name: cf.AltName, + Field: cf.Name, + Tag: ct.aliasTag, + ActualTag: ct.tag, + Param: ct.param, Value: current.Interface(), Kind: kind, Type: current.Type(), @@ -629,170 +621,162 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - // if we get here tag length is zero and we can leave - if kind == reflect.Invalid { - return - } - case reflect.Struct: typ = current.Type() if typ != timeType { - if cTag.isNoStructLevel { + if ct != nil { + ct = ct.next + } + + if ct != nil && ct.typeof == typeNoStructLevel { 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 } } - if tag == blank { + if !ct.hasTag { return } typ = current.Type() - var dive bool - var diveSubTag string +OUTER: + 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 - } - - 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) { return } + ct = ct.next continue - } - if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, nsPrefix, errs, valTag, name, customName) { - return - } - } + case typeDive: - if dive { - // traverse slice or map here - // or panic ;) - switch kind { - case reflect.Slice, reflect.Array: - v.traverseSlice(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil) - case reflect.Map: - v.traverseMap(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil) - default: - // throw error, if not a slice or map then should not have gotten here - // bad dive tag - panic("dive error! can't dive on a non slice or map") - } - } -} + ct = ct.next -// 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) { + // traverse slice or map here + // or panic ;) + switch kind { + case reflect.Slice, reflect.Array: - 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) - } -} + 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) + } -// traverseMap traverses a map's elements and passes them to traverseField for validation -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) { + case reflect.Map: + 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) + } - for _, key := range current.MapKeys() { - 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) - } -} + default: + // throw error, if not a slice or map then should not have gotten here + // bad dive tag + panic("dive error! can't dive on a non slice or map") + } -// 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 -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 { + return - var valFunc Func - var ok bool + case typeOr: - if valTag.isOrVal { + errTag := blank - errTag := blank + for { - for _, val := range valTag.tagVals { + if ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) { - valFunc, ok = v.validationFuncs[val[0]] - if !ok { - panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) - } + // drain rest of the 'or' values, then continue or leave + for { - if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) { - return false - } + ct = ct.next - errTag += orSeparator + val[0] - } + if ct == nil { + return + } - ns := errPrefix + name + if ct.typeof != typeOr { + continue OUTER + } + } + } - if valTag.isAlias { - errs[ns] = &FieldError{ - FieldNamespace: ns, - NameNamespace: nsPrefix + customName, - Name: customName, - Field: name, - Tag: valTag.tag, - ActualTag: errTag[1:], - Value: current.Interface(), - Type: currentType, - Kind: currentKind, - } - } else { - errs[errPrefix+name] = &FieldError{ - FieldNamespace: ns, - NameNamespace: nsPrefix + customName, - Name: customName, - Field: name, - Tag: errTag[1:], - ActualTag: errTag[1:], - Value: current.Interface(), - Type: currentType, - Kind: currentKind, + errTag += orSeparator + ct.tag + + if ct.next == nil { + // if we get here, no valid 'or' value and no more tags + + ns := errPrefix + cf.Name + + if ct.hasAlias { + errs[ns] = &FieldError{ + FieldNamespace: ns, + NameNamespace: nsPrefix + cf.AltName, + Name: cf.AltName, + Field: cf.Name, + Tag: ct.aliasTag, + ActualTag: ct.actualAliasTag, + Value: current.Interface(), + Type: typ, + Kind: kind, + } + } else { + errs[errPrefix+cf.Name] = &FieldError{ + FieldNamespace: ns, + NameNamespace: nsPrefix + cf.AltName, + Name: cf.AltName, + Field: cf.Name, + Tag: errTag[1:], + ActualTag: errTag[1:], + Value: current.Interface(), + Type: typ, + Kind: kind, + } + } + + return + } + + ct = ct.next } - } - return true - } + default: + if !ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) { - valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]] - if !ok { - panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) - } + ns := errPrefix + cf.Name - if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, valTag.tagVals[0][1]) { - return false - } + errs[ns] = &FieldError{ + FieldNamespace: ns, + NameNamespace: nsPrefix + cf.AltName, + Name: cf.AltName, + Field: cf.Name, + Tag: ct.aliasTag, + ActualTag: ct.tag, + Value: current.Interface(), + Param: ct.param, + Type: typ, + Kind: kind, + } - ns := errPrefix + name + return - errs[ns] = &FieldError{ - FieldNamespace: ns, - NameNamespace: nsPrefix + customName, - Name: customName, - Field: name, - Tag: valTag.tag, - ActualTag: valTag.tagVals[0][0], - Value: current.Interface(), - Param: valTag.tagVals[0][1], - Type: currentType, - Kind: currentKind, - } + } - return true + ct = ct.next + } + } } diff --git a/validator_test.go b/validator_test.go index c3e746d..6af996d 100644 --- a/validator_test.go +++ b/validator_test.go @@ -365,7 +365,7 @@ func TestAnonymous(t *testing.T) { B string `validate:"required" json:"BEE"` } anonymousC struct { - c string `validate:"required" json:"SEE"` + c string `validate:"required"` } } @@ -381,7 +381,7 @@ func TestAnonymous(t *testing.T) { B: "", }, anonymousC: struct { - c string `validate:"required" json:"SEE"` + c string `validate:"required"` }{ c: "", }, @@ -398,7 +398,7 @@ func TestAnonymous(t *testing.T) { Equal(t, errs["Test.AnonymousB.B"].Name, "BEE") s := struct { - c string `validate:"required" json:"SEE"` + c string `validate:"required"` }{ c: "", } @@ -407,6 +407,42 @@ func TestAnonymous(t *testing.T) { 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) { config := &Config{ TagName: "validate", @@ -576,6 +612,7 @@ func TestAliasTags(t *testing.T) { validate.RegisterAliasValidation("req", "required,dive,iscolor") arr := []string{"val1", "#fff", "#000"} + errs = validate.Field(arr, "req") NotEqual(t, errs, nil) AssertError(t, errs, "[0]", "[0]", "iscolor") @@ -1733,7 +1770,7 @@ func TestMACValidation(t *testing.T) { errs := validate.Field(test.param, "mac") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { t.Fatalf("Index: %d cidrv4 failed Error: %s", i, errs) } @@ -2003,7 +2040,7 @@ func TestTCPAddrValidation(t *testing.T) { for i, test := range tests { errs := validate.Field(test.param, "tcp_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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 { errs := validate.Field(test.param, "tcp6_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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 { errs := validate.Field(test.param, "tcp4_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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 { errs := validate.Field(test.param, "udp_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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 { errs := validate.Field(test.param, "udp6_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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 { errs := validate.Field(test.param, "udp4_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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 { errs := validate.Field(test.param, "ip_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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 { errs := validate.Field(test.param, "ip6_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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 { errs := validate.Field(test.param, "ip4_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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 { errs := validate.Field(test.param, "unix_addr") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { t.Fatalf("Index: %d unix_addr failed Error: %s", i, errs) } @@ -2369,6 +2406,9 @@ func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) { errs = validate.Field(iface, "") Equal(t, errs, nil) + errs = validate.FieldWithValue(nil, iface, "") + Equal(t, errs, nil) + var f func(string) errs = validate.Field(f, "required") @@ -3018,7 +3058,7 @@ func TestSSNValidation(t *testing.T) { errs := validate.Field(test.param, "ssn") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { 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") - if test.expected == true { + if test.expected { if !IsEqual(errs, nil) { t.Fatalf("Index: %d URI failed Error: %s", i, errs) } @@ -5481,6 +5521,16 @@ func TestAlpha(t *testing.T) { errs := validate.Field(s, "alpha") 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" errs = validate.Field(s, "alpha") NotEqual(t, errs, nil) @@ -5489,6 +5539,7 @@ func TestAlpha(t *testing.T) { errs = validate.Field(1, "alpha") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "alpha") + } func TestStructStringValidation(t *testing.T) { @@ -5776,6 +5827,33 @@ func TestCustomFieldName(t *testing.T) { 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 func TestPointerAndOmitEmpty(t *testing.T) {