initial un-refined new caching system

pull/252/head
joeybloggs 8 years ago
parent 5c8ad6a1a9
commit c0a414dc9e
  1. 521
      cache.go
  2. 185
      util.go
  3. 745
      validator.go
  4. 1
      validator_test.go

@ -1,71 +1,508 @@
package validator package validator
import ( import (
"fmt"
"reflect" "reflect"
"strings"
"sync" "sync"
"sync/atomic"
) )
type cachedField struct { // type cachedField struct {
Idx int // Idx int
Name string // Name string
AltName string // AltName string
CachedTag *cachedTag // CachedTag *cachedTag
// }
// type cachedStruct struct {
// Name string
// fields map[int]cachedField
// }
// type structCacheMap struct {
// lock sync.RWMutex
// m map[reflect.Type]*cachedStruct
// }
// func (s *structCacheMap) Get(key reflect.Type) (*cachedStruct, bool) {
// s.lock.RLock()
// value, ok := s.m[key]
// s.lock.RUnlock()
// return value, ok
// }
// func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) {
// s.lock.Lock()
// s.m[key] = value
// s.lock.Unlock()
// }
// type cachedTag struct {
// tag string
// isOmitEmpty bool
// isNoStructLevel bool
// isStructOnly bool
// diveTag string
// tags []*tagVals
// }
// type tagVals struct {
// tagVals [][]string
// isOrVal bool
// isAlias bool
// tag string
// }
// type tagCacheMap struct {
// lock sync.RWMutex
// m map[string]*cachedTag
// }
// func (s *tagCacheMap) Get(key string) (*cachedTag, bool) {
// s.lock.RLock()
// value, ok := s.m[key]
// s.lock.RUnlock()
// return value, ok
// }
// func (s *tagCacheMap) Set(key string, value *cachedTag) {
// s.lock.Lock()
// s.m[key] = value
// s.lock.Unlock()
// }
// ******* New Cache ***************
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) {
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 cStruct struct {
Name string
fields map[int]*cField
fn StructLevelFunc
}
type cField struct {
Idx int
Name string
AltName string
cTags *cTag
}
// TODO: investigate using enum instead of so many booleans, may be faster
// but let's get the new cache system working first
type cTag struct {
tag string tag string
aliasTag string
actualAliasTag string
param string
hasAlias bool
isOmitEmpty bool isOmitEmpty bool
isNoStructLevel bool isNoStructLevel bool
isStructOnly bool isStructOnly bool
diveTag string isDive bool
tags []*tagVals isOrVal bool
exists bool
hasTag bool
fn Func
next *cTag
} }
type tagVals struct { // TODO: eliminate get and set functions from cache, they are pure overhead for nicer syntax.
tagVals [][]string
isOrVal bool
isAlias bool
tag string
}
type tagCacheMap struct { func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
lock sync.RWMutex
m map[string]*cachedTag
}
func (s *tagCacheMap) Get(key string) (*cachedTag, bool) { v.structCache.lock.Lock()
s.lock.RLock() defer v.structCache.lock.Unlock()
value, ok := s.m[key]
s.lock.RUnlock()
return value, ok 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 {
// v.structCache.lock.Unlock()
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.PkgPath != blank {
if !fld.Anonymous && fld.PkgPath != blank {
continue
}
tag = fld.Tag.Get(v.tagName)
// if len(tag) == 0 || tag == skipValidationTag {
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
}
}
// fmt.Println("Finding Struct Tag", sName, "FLD:", fld.Name)
// ctag, ok := v.tagCache.Get(tag)
// if !ok {
// fmt.Println("Not Found, Lock then check again for parallel operations")
// v.tagCache.lock.Lock()
// defer func() {
// fmt.Println("Ulocking")
// v.tagCache.lock.Unlock()
// fmt.Println("Unlocked")
// }()
// defer v.tagCache.lock.Unlock()
// if ctag, ok = v.tagCache.Get(tag); !ok {
// fmt.Println("parsing tag", tag)
// ctag = v.parseFieldTags(tag, fld.Name)
// NOTE: cannot use tag cache, because tag may be equal, but things like alias may be different
// and so only struct level caching can be used
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)
}
// fmt.Println("Done Parsing")
// v.tagCache.Set(tag, ctag)
// fmt.Println("Tag Cahed")
// }
// fmt.Println("Ulocking")
// v.tagCache.lock.Unlock()
// fmt.Println("Unlocked")
// }
// fmt.Println(tag, ctag)
cs.fields[i] = &cField{Idx: i, Name: fld.Name, AltName: customName, cTags: ctag}
}
// If not anonymous type; they have to be parsed every time because if interface
// a different struct could be used...
// if len(sName) > 0 {
// fmt.Println(typ)
v.structCache.Set(typ, cs)
// }
// v.structCache.lock.Unlock()
return cs
} }
func (s *tagCacheMap) Set(key string, value *cachedTag) { // func (v *Validate) parseFieldTags(tag, fieldName string) (ctag *cTag) {
s.lock.Lock()
s.m[key] = value // // ctag := &cTag{tag: tag}
s.lock.Unlock()
// // fmt.Println(tag)
// ctag, _ = v.parseFieldTagsRecursive(tag, fieldName, blank, false)
// v.tagCache.Set(tag, ctag)
// // fmt.Println(ctag)
// return
// }
// TODO: Optimize for to not Split but ust for over string chunk, by chunk
func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
// func (v *Validate) parseFieldTagsRecursive(ctag *cTag, tag, fieldName, alias string, isAlias bool) bool {
// if tag == blank {
// return
// }
// fmt.Println("Parsing, depath:", depth)
var t string
var ok bool
noAlias := len(alias) == 0
// var tmpCtag *cTag
// var start,end int
// var ctag *cTag
// var lastCtag *cTag
// ctag := &cTag{tag: tag}
tags := strings.Split(tag, tagSeparator)
// fmt.Println(len(tags), tags)
for i := 0; i < len(tags); i++ {
// for i := 0; i < len(tags); i++ {
t = tags[i]
if noAlias {
alias = t
}
// _, found := v.aliasValidators[t]
// fmt.Println(i, t, found)
// if len(t) == 0 {
// continue
// }
// if i == 0 {
// current = &cTag{aliasTag: alias, hasAlias: hasAlias}
// firstCtag = current
// } else {
// current.next = &cTag{aliasTag: alias, hasAlias: hasAlias}
// current = current.next
// }
if v.hasAliasValidators {
// check map for alias and process new tags, otherwise process as usual
if tagsVal, found := v.aliasValidators[t]; found {
// fmt.Println(tagsVal)
if i == 0 {
firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
} else {
// fmt.Println("BEFORE ALIAS:", current)
// diveCurr := current
next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
// fmt.Println("ALIAS:", next, curr)
current.next, current = next, curr
// fmt.Println("AFTER current", diveCurr)
// current.next, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
}
continue
// // tmpCtag, lastCtag = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
// // if ctag == nil {
// // ctag = tmpCtag
// // } else {
// // ctag.next = tmpCtag
// // }
}
}
// if i == 0 {
// firstCtag = current
// }
// type cTag struct {
// tag string
// aliasTag string
// hasAlias bool
// isOmitEmpty bool
// isNoStructLevel bool
// isStructOnly bool
// isDive bool
// isOrVal bool
// fn Func
// next *cTag
// }
// if lastCtag == nil {
// lastCtag = ctag
// }
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.isDive = true
// fmt.Println("DIVE CURRENT", current)
continue
// cTag.diveTag = tag
// tVals := &tagVals{tagVals: [][]string{{t}}}
// cTag.tags = append(cTag.tags, tVals)
// return true
case omitempty:
current.isOmitEmpty = true
continue
case structOnlyTag:
current.isStructOnly = true
continue
case noStructLevelTag:
current.isNoStructLevel = true
continue
case existsTag:
current.exists = true
continue
default:
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator)
// // if no or values
// if len(orVals) == 1 {
// current.fn, ok = v.validationFuncs[t]
// if !ok {
// panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, fieldName)))
// }
// } else {
// 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 j := 0; j < len(orVals); j++ {
// for i, val := range orVals {
// if v.hasAliasValidators {
// // check map for alias and process new tags, otherwise process as usual
// if tagsVal, ok := v.aliasValidators[orVals[j]]; ok {
// if i == 0 {
// firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, orVals[j], true)
// } else {
// current.next, current = v.parseFieldTagsRecursive(tagsVal, fieldName, orVals[j], true)
// }
// continue
// // tmpCtag, lastCtag = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
// // if ctag == nil {
// // ctag = tmpCtag
// // } else {
// // ctag.next = tmpCtag
// // }
// }
// }
vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
if noAlias {
alias = vals[0]
current.aliasTag = alias
} else {
// alias = t
current.actualAliasTag = t
}
if j > 0 {
current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
current = current.next
// current.next=&
}
// else{
current.tag = vals[0]
if len(current.tag) == 0 {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
}
// _, found := v.validationFuncs[current.tag]
// fmt.Println("TAG", current.tag, "FOund:", found)
if current.fn, ok = v.validationFuncs[current.tag]; !ok {
// fmt.Println("I'm panicing!")
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, fieldName)))
}
current.isOrVal = len(orVals) > 1
// }
// tagVal.tag = key
// if isAlias {
// tagVal.tag = alias
// }
// if key == blank {
// panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
// }
if len(vals) > 1 {
current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
}
// tagVal.tagVals[i] = []string{key, param}
}
// }
}
}
// if depth > 0 {
// // _, found := v.aliasValidators[t]
// fmt.Println("WTF", len(tags), tags, firstCtag, current)
// panic("WTF")
// }
return
} }

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

@ -44,6 +44,7 @@ var (
timeType = reflect.TypeOf(time.Time{}) timeType = reflect.TypeOf(time.Time{})
timePtrType = reflect.TypeOf(&time.Time{}) timePtrType = reflect.TypeOf(&time.Time{})
emptyStructPtr = new(struct{}) 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,9 +148,11 @@ type Validate struct {
hasCustomFuncs bool hasCustomFuncs bool
hasAliasValidators bool hasAliasValidators bool
hasStructLevelFuncs bool hasStructLevelFuncs bool
tagCache *tagCacheMap // oldTagCache *tagCacheMap
structCache *structCacheMap // oldStructCache *structCacheMap
errsPool *sync.Pool tagCache *tagCache
structCache *structCache
errsPool *sync.Pool
} }
func (v *Validate) initCheck() { func (v *Validate) initCheck() {
@ -220,11 +223,19 @@ 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,
// oldTagCache: &tagCacheMap{m: map[string]*cachedTag{}},
// oldStructCache: &structCacheMap{m: map[reflect.Type]*cachedStruct{}},
errsPool: &sync.Pool{New: func() interface{} { errsPool: &sync.Pool{New: func() interface{} {
return ValidationErrors{} return ValidationErrors{}
}}} }}}
@ -332,10 +343,30 @@ 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)
}
}
// fmt.Println("CTAG:", 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 +383,27 @@ 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.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)
@ -479,140 +527,162 @@ func (v *Validate) ensureValidStruct(topStruct reflect.Value, currentStruct refl
panic("value passed for validation is not a struct") panic("value passed for validation is not a struct")
} }
v.tranverseStruct(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, useStructName, partial, exclude, includeExclude, isStructOnly) 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 // 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) 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 // var ok bool
first := len(nsPrefix) == 0
typ := current.Type() typ := current.Type()
var ok bool
// var sName string
cs, ok = v.structCache.Get(typ)
if !ok {
cs = v.extractStructCache(current, typ.Name())
}
sName := typ.Name() // sName := 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.isStructOnly {
var fld reflect.StructField // var fld reflect.StructField
// is anonymous struct, cannot parse or cache as // is anonymous struct, cannot parse or cache as
// it has no name to index by // it has no name to index by
if sName == blank { // if sName == blank {
var customName string // var customName string
var ok bool // var ok bool
numFields := current.NumField() // numFields := current.NumField()
for i := 0; i < numFields; i++ { // for i := 0; i < numFields; i++ {
fld = typ.Field(i) // fld = typ.Field(i)
if !fld.Anonymous && fld.PkgPath != blank { // if !fld.Anonymous && fld.PkgPath != blank {
continue // continue
} // }
if partial { // if partial {
_, ok = includeExclude[errPrefix+fld.Name] // _, ok = includeExclude[errPrefix+fld.Name]
if (ok && exclude) || (!ok && !exclude) { // if (ok && exclude) || (!ok && !exclude) {
continue // continue
} // }
} // }
customName = fld.Name // customName = fld.Name
if v.fieldNameTag != blank { // if v.fieldNameTag != blank {
name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0] // name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
// dash check is for json "-" means don't output in json // // dash check is for json "-" means don't output in json
if name != blank && name != dash { // if name != blank && name != dash {
customName = name // 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) // v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, nsPrefix, errs, true, fld.Tag.Get(v.tagName), fld.Name, customName, partial, exclude, includeExclude, nil)
} // }
} else { // } else {
s, ok := v.structCache.Get(typ) // s, ok := v.oldStructCache.Get(typ)
if !ok { // if !ok {
s = v.parseStruct(current, sName) // s = v.parseStruct(current, sName)
} // ccs := v.extractStructCache(current, sName)
// fmt.Println(ccs, ccs.fields[0], s.fields[0].CachedTag)
// }
for i, f := range s.fields { for _, f := range cs.fields {
if partial { if partial {
_, ok = includeExclude[errPrefix+f.Name] _, ok = includeExclude[errPrefix+f.Name]
if (ok && exclude) || (!ok && !exclude) { if (ok && exclude) || (!ok && !exclude) {
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)
} }
// fld = typ.Field(f.Idx)
// fmt.Println(errPrefix+f.Name, f.cTags)
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 { // if fsn, ok := v.structLevelFuncs[current.Type()]; ok {
fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs}) 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 // 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 { // if tag == skipValidationTag {
return // return
} // }
if cTag == nil { // if cTag == nil {
var isCached bool // var isCached bool
cTag, isCached = v.tagCache.Get(tag) // cTag, isCached = v.oldTagCache.Get(tag)
if !isCached { // if !isCached {
cTag = v.parseTags(tag, name) // cTag = v.parseTags(tag, name)
} // }
} // }
current, kind := v.ExtractType(current) current, kind := v.ExtractType(current)
var typ reflect.Type var typ reflect.Type
// fmt.Println("TS:", ct, kind)
switch kind { switch kind {
case reflect.Ptr, reflect.Interface, reflect.Invalid: case reflect.Ptr, reflect.Interface, reflect.Invalid:
if cTag.isOmitEmpty {
// return
if ct == nil {
return
}
if ct.isOmitEmpty {
return return
} }
if tag != blank { // if !ct.hasTag {
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
@ -620,12 +690,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(),
@ -634,6 +704,8 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
return return
} }
// return
// if we get here tag length is zero and we can leave // if we get here tag length is zero and we can leave
if kind == reflect.Invalid { if kind == reflect.Invalid {
return return
@ -644,159 +716,452 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
if typ != timeType { if typ != timeType {
if cTag.isNoStructLevel { if ct != nil {
ct = ct.next
// fmt.Println("CHECKING NO STRUCT LEVEL", ct.isNoStructLevel, ct.next)
}
// if ct != nil && (ct.isNoStructLevel || (ct.next != nil && ct.next.isNoStructLevel)) {
if ct != nil && ct.isNoStructLevel {
return return
} }
v.tranverseStruct(topStruct, current, current, errPrefix+name+namespaceSeparator, nsPrefix+customName+namespaceSeparator, errs, false, partial, exclude, includeExclude, cTag.isStructOnly) // func (v *Validate) tranverseStruct(topStruct reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]*struct{}, cs *cStruct, ct *cTag)
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 {
for _, valTag := range cTag.tags { return
if valTag.tagVals[0][0] == existsTag {
continue
} }
if valTag.tagVals[0][0] == diveTag { if ct.exists {
dive = true ct = ct.next
diveSubTag = strings.TrimLeft(strings.SplitN(cTag.diveTag, diveTag, 2)[1], ",") continue
break
} }
if valTag.tagVals[0][0] == omitempty { if ct.isOmitEmpty {
if !HasValue(v, topStruct, currentStruct, current, typ, kind, blank) { if !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) { if ct.isDive {
// fmt.Println("IN DIVE:", ct)
ct = ct.next
// fmt.Println("NEXT:", ct)
// 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, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(arrayIndexFieldName, cf.Name, i), AltName: fmt.Sprintf(arrayIndexFieldName, cf.AltName, i)}, ct)
}
// v.traverseSlice(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil)
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)
// 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)
}
// 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")
}
return return
} }
}
if dive { if ct.isOrVal {
// traverse slice or map here
// or panic ;) errTag := blank
switch kind {
case reflect.Slice, reflect.Array: // for _, val := range valTag.tagVals {
v.traverseSlice(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil) for {
case reflect.Map:
v.traverseMap(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil) // fmt.Println(ct)
default: // if ct == nil {
// throw error, if not a slice or map then should not have gotten here // // if we get here, no valid 'or' value and no more tags
// bad dive tag
panic("dive error! can't dive on a non slice or map") // 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.tag,
// 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
// }
if !ct.isOrVal {
// if we get here, no valid 'or' value, but 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,
}
}
// traverseSlice traverses a Slice or Array's elements and passes them to traverseField for validation continue OUTER
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) { // break
// goto VAL
}
for i := 0; i < current.Len(); i++ { // valFunc, ok = v.validationFuncs[val[0]]
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) // if !ok {
} // panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
} // }
// traverseMap traverses a map's elements and passes them to traverseField for validation if ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
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() { // drain rest of the 'or' values, then continue or leave
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) for {
}
}
// 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 ct = ct.next
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 if ct == nil {
var ok bool return
}
if valTag.isOrVal { if !ct.isOrVal {
continue OUTER
// break
// goto VAL
}
}
}
errTag := blank errTag += orSeparator + ct.tag
for _, val := range valTag.tagVals { if ct.next == nil {
// if we get here, no valid 'or' value and no more tags
valFunc, ok = v.validationFuncs[val[0]] ns := errPrefix + cf.Name
if !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
}
if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) { if ct.hasAlias {
return false 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
}
errTag += orSeparator + val[0] ct = ct.next
}
// if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) {
// return false
// }
// errTag += orSeparator + val[0]
// }
// ns := errPrefix + name
// 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,
// }
// NOT SURE ABOUT THE BELOW LINE BEING COMMENT, LINT SAYS IT's Unreachable but....
// return
} }
ns := errPrefix + name // fmt.Println(ct)
if !ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
ns := errPrefix + cf.Name
if valTag.isAlias {
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.tag,
Value: current.Interface(), Value: current.Interface(),
Type: currentType, Param: ct.param,
Kind: currentKind, Type: typ,
} Kind: kind,
} 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,
} }
}
return true return
}
valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]]
if !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
}
if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, valTag.tagVals[0][1]) {
return false
}
ns := errPrefix + name
errs[ns] = &FieldError{ }
FieldNamespace: ns, // VAL:
NameNamespace: nsPrefix + customName, // valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]]
Name: customName, // if !ok {
Field: name, // panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
Tag: valTag.tag, // }
ActualTag: valTag.tagVals[0][0],
Value: current.Interface(), // if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, valTag.tagVals[0][1]) {
Param: valTag.tagVals[0][1], // return false
Type: currentType, // }
Kind: currentKind,
// ns := errPrefix + name
// 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,
// }
// if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, nsPrefix, errs, valTag, name, customName) {
// return
// }
ct = ct.next
} }
return true // var dive bool
// var diveSubTag string
// for _, valTag := range cTag.tags {
// // if valTag.tagVals[0][0] == existsTag {
// // 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 {
// // if !HasValue(v, topStruct, currentStruct, current, typ, kind, blank) {
// // return
// // }
// // continue
// // }
// if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, nsPrefix, errs, valTag, name, customName) {
// return
// }
// }
// 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")
// }
// }
} }
// // 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
// 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() {
// 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
// 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
// var ok bool
// if valTag.isOrVal {
// errTag := blank
// for _, val := range valTag.tagVals {
// valFunc, ok = v.validationFuncs[val[0]]
// if !ok {
// panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
// }
// if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) {
// return false
// }
// errTag += orSeparator + val[0]
// }
// ns := errPrefix + name
// 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,
// }
// }
// return true
// }
// valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]]
// if !ok {
// panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
// }
// if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, valTag.tagVals[0][1]) {
// return false
// }
// ns := errPrefix + name
// 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
// }

@ -576,6 +576,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")

Loading…
Cancel
Save