initial un-refined new caching system

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

@ -1,71 +1,508 @@
package validator
import (
"fmt"
"reflect"
"strings"
"sync"
"sync/atomic"
)
type cachedField struct {
Idx int
Name string
AltName string
CachedTag *cachedTag
// type cachedField struct {
// Idx int
// Name string
// AltName string
// 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 {
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
}
type structCacheMap struct {
lock sync.RWMutex
m map[reflect.Type]*cachedStruct
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 tagCache struct {
lock sync.Mutex
m atomic.Value // map[string]*cTag
}
func (tc *tagCache) Get(key string) (c *cTag, found bool) {
c, found = tc.m.Load().(map[string]*cTag)[key]
return
}
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)
}
func (s *structCacheMap) Get(key reflect.Type) (*cachedStruct, bool) {
s.lock.RLock()
value, ok := s.m[key]
s.lock.RUnlock()
return value, ok
type cStruct struct {
Name string
fields map[int]*cField
fn StructLevelFunc
}
func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) {
s.lock.Lock()
s.m[key] = value
s.lock.Unlock()
type cField struct {
Idx int
Name string
AltName string
cTags *cTag
}
type cachedTag struct {
// 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
aliasTag string
actualAliasTag string
param string
hasAlias bool
isOmitEmpty bool
isNoStructLevel bool
isStructOnly bool
diveTag string
tags []*tagVals
isDive bool
isOrVal bool
exists bool
hasTag bool
fn Func
next *cTag
}
type tagVals struct {
tagVals [][]string
isOrVal bool
isAlias bool
tag string
// TODO: eliminate get and set functions from cache, they are pure overhead for nicer syntax.
func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
v.structCache.lock.Lock()
defer v.structCache.lock.Unlock()
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
}
type tagCacheMap struct {
lock sync.RWMutex
m map[string]*cachedTag
// func (v *Validate) parseFieldTags(tag, fieldName string) (ctag *cTag) {
// // ctag := &cTag{tag: tag}
// // 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)
func (s *tagCacheMap) Get(key string) (*cachedTag, bool) {
s.lock.RLock()
value, ok := s.m[key]
s.lock.RUnlock()
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
return value, ok
// fmt.Println("AFTER current", diveCurr)
// current.next, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
}
func (s *tagCacheMap) Set(key string, value *cachedTag) {
s.lock.Lock()
s.m[key] = value
s.lock.Unlock()
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
import (
"fmt"
"reflect"
"strconv"
"strings"
)
const (
dash = "-"
// dash = "-"
blank = ""
namespaceSeparator = "."
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()
s := &cachedStruct{Name: sName, fields: map[int]cachedField{}}
// typ := current.Type()
// s := &cachedStruct{Name: sName, fields: map[int]cachedField{}}
numFields := current.NumField()
// numFields := current.NumField()
var fld reflect.StructField
var tag string
var customName string
// var fld reflect.StructField
// var tag 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 {
continue
}
// if fld.PkgPath != blank {
// continue
// }
tag = fld.Tag.Get(v.tagName)
// tag = fld.Tag.Get(v.tagName)
if tag == skipValidationTag {
continue
}
// if tag == skipValidationTag {
// continue
// }
customName = fld.Name
if v.fieldNameTag != blank {
// customName = fld.Name
// 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
if name != "" && name != skipValidationTag {
customName = name
}
}
// // 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)
}
// cTag, ok := v.oldTagCache.Get(tag)
// if !ok {
// 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 {
return true
}
// if tag == blank {
// return true
// }
for _, t := range strings.Split(tag, tagSeparator) {
// 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 {
// 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)
// leave := v.parseTagsRecursive(cTag, tagsVal, fieldName, t, true)
if leave {
return leave
}
// if leave {
// return leave
// }
continue
}
}
// continue
// }
// }
switch t {
// switch t {
case diveTag:
cTag.diveTag = tag
tVals := &tagVals{tagVals: [][]string{{t}}}
cTag.tags = append(cTag.tags, tVals)
return true
// case diveTag:
// cTag.diveTag = tag
// tVals := &tagVals{tagVals: [][]string{{t}}}
// cTag.tags = append(cTag.tags, tVals)
// return true
case omitempty:
cTag.isOmitEmpty = true
// case omitempty:
// cTag.isOmitEmpty = true
case structOnlyTag:
cTag.isStructOnly = true
// case structOnlyTag:
// cTag.isStructOnly = true
case noStructLevelTag:
cTag.isNoStructLevel = 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)
// // 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
// var key string
// var param string
for i, val := range orVals {
vals := strings.SplitN(val, tagKeySeparator, 2)
key = vals[0]
// for i, val := range orVals {
// vals := strings.SplitN(val, tagKeySeparator, 2)
// key = vals[0]
tagVal.tag = key
// tagVal.tag = key
if isAlias {
tagVal.tag = alias
}
// if isAlias {
// tagVal.tag = alias
// }
if key == blank {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
}
// 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)
}
// if len(vals) > 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{})
timePtrType = reflect.TypeOf(&time.Time{})
emptyStructPtr = new(struct{})
defaultCField = new(cField)
)
// StructLevel contains all of the information and helper methods
@ -147,8 +148,10 @@ type Validate struct {
hasCustomFuncs bool
hasAliasValidators bool
hasStructLevelFuncs bool
tagCache *tagCacheMap
structCache *structCacheMap
// oldTagCache *tagCacheMap
// oldStructCache *structCacheMap
tagCache *tagCache
structCache *structCache
errsPool *sync.Pool
}
@ -220,11 +223,19 @@ 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,
// oldTagCache: &tagCacheMap{m: map[string]*cachedTag{}},
// oldStructCache: &structCacheMap{m: map[reflect.Type]*cachedStruct{}},
errsPool: &sync.Pool{New: func() interface{} {
return ValidationErrors{}
}}}
@ -332,10 +343,30 @@ 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)
}
}
// fmt.Println("CTAG:", 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 +383,27 @@ 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.traverseField(topVal, topVal, reflect.ValueOf(field), blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
if len(errs) == 0 {
v.errsPool.Put(errs)
@ -479,78 +527,89 @@ func (v *Validate) ensureValidStruct(topStruct reflect.Value, currentStruct refl
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
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
first := len(nsPrefix) == 0
typ := current.Type()
var ok bool
sName := typ.Name()
// var sName string
cs, ok = v.structCache.Get(typ)
if !ok {
cs = v.extractStructCache(current, typ.Name())
}
// sName := 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.isStructOnly {
var fld reflect.StructField
// var fld reflect.StructField
// is anonymous struct, cannot parse or cache as
// it has no name to index by
if sName == blank {
// if sName == blank {
var customName string
var ok bool
numFields := current.NumField()
// var customName string
// var ok bool
// 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 {
continue
}
// if !fld.Anonymous && fld.PkgPath != blank {
// continue
// }
if partial {
// if partial {
_, ok = includeExclude[errPrefix+fld.Name]
// _, ok = includeExclude[errPrefix+fld.Name]
if (ok && exclude) || (!ok && !exclude) {
continue
}
}
// if (ok && exclude) || (!ok && !exclude) {
// 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
if name != blank && name != dash {
customName = name
}
}
// // 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)
}
// 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.oldStructCache.Get(typ)
// if !ok {
// 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 {
@ -560,59 +619,70 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
continue
}
}
fld = typ.Field(i)
// fld = typ.Field(f.Idx)
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, nsPrefix, errs, true, f.CachedTag.tag, fld.Name, f.AltName, partial, exclude, includeExclude, f.CachedTag)
}
// 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.
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 {
// if fsn, ok := v.structLevelFuncs[current.Type()]; ok {
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) {
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, partial bool, exclude bool, includeExclude map[string]*struct{}, cs *cStruct, cf *cField, ct *cTag) {
if tag == skipValidationTag {
return
}
// if tag == skipValidationTag {
// return
// }
if cTag == nil {
var isCached bool
cTag, isCached = v.tagCache.Get(tag)
// if cTag == nil {
// var isCached bool
// cTag, isCached = v.oldTagCache.Get(tag)
if !isCached {
cTag = v.parseTags(tag, name)
}
}
// if !isCached {
// cTag = v.parseTags(tag, name)
// }
// }
current, kind := v.ExtractType(current)
var typ reflect.Type
// fmt.Println("TS:", ct, kind)
switch kind {
case reflect.Ptr, reflect.Interface, reflect.Invalid:
if cTag.isOmitEmpty {
// return
if ct == nil {
return
}
if ct.isOmitEmpty {
return
}
if tag != blank {
// if !ct.hasTag {
if ct.hasTag {
ns := errPrefix + name
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
@ -620,12 +690,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(),
@ -634,6 +704,8 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
return
}
// return
// if we get here tag length is zero and we can leave
if kind == reflect.Invalid {
return
@ -644,159 +716,452 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
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
}
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
}
}
if tag == blank {
if !ct.hasTag {
return
}
typ = current.Type()
var dive bool
var diveSubTag string
for _, valTag := range cTag.tags {
if valTag.tagVals[0][0] == existsTag {
continue
OUTER:
for {
if ct == nil {
return
}
if valTag.tagVals[0][0] == diveTag {
dive = true
diveSubTag = strings.TrimLeft(strings.SplitN(cTag.diveTag, diveTag, 2)[1], ",")
break
if ct.exists {
ct = ct.next
continue
}
if valTag.tagVals[0][0] == omitempty {
if ct.isOmitEmpty {
if !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
}
}
if ct.isDive {
// fmt.Println("IN DIVE:", ct)
ct = ct.next
// fmt.Println("NEXT:", ct)
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)
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:
v.traverseMap(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil)
for _, key := range current.MapKeys() {
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(mapIndexFieldName, cf.Name, key.Interface()), AltName: fmt.Sprintf(mapIndexFieldName, cf.AltName, key.Interface())}, ct)
// 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")
}
}
}
// 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)
}
return
}
// 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) {
if ct.isOrVal {
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)
errTag := blank
// for _, val := range valTag.tagVals {
for {
// fmt.Println(ct)
// if ct == 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.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,
}
}
// 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 {
continue OUTER
// break
// goto VAL
}
var valFunc Func
var ok bool
// valFunc, ok = v.validationFuncs[val[0]]
// if !ok {
// panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
// }
if valTag.isOrVal {
if ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
errTag := blank
// drain rest of the 'or' values, then continue or leave
for {
for _, val := range valTag.tagVals {
ct = ct.next
valFunc, ok = v.validationFuncs[val[0]]
if !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
if ct == nil {
return
}
if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) {
return false
if !ct.isOrVal {
continue OUTER
// break
// goto VAL
}
errTag += orSeparator + val[0]
}
}
errTag += orSeparator + ct.tag
ns := errPrefix + name
if ct.next == nil {
// if we get here, no valid 'or' value and no more tags
if valTag.isAlias {
ns := errPrefix + cf.Name
if ct.hasAlias {
errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: nsPrefix + customName,
Name: customName,
Field: name,
Tag: valTag.tag,
ActualTag: errTag[1:],
NameNamespace: nsPrefix + cf.AltName,
Name: cf.AltName,
Field: cf.Name,
Tag: ct.aliasTag,
ActualTag: ct.actualAliasTag,
Value: current.Interface(),
Type: currentType,
Kind: currentKind,
Type: typ,
Kind: kind,
}
} else {
errs[errPrefix+name] = &FieldError{
errs[errPrefix+cf.Name] = &FieldError{
FieldNamespace: ns,
NameNamespace: nsPrefix + customName,
Name: customName,
Field: name,
NameNamespace: nsPrefix + cf.AltName,
Name: cf.AltName,
Field: cf.Name,
Tag: errTag[1:],
ActualTag: errTag[1:],
Value: current.Interface(),
Type: currentType,
Kind: currentKind,
Type: typ,
Kind: kind,
}
}
return true
return
}
valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]]
if !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
ct = ct.next
}
// if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) {
// return false
// }
if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, valTag.tagVals[0][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
errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: nsPrefix + customName,
Name: customName,
Field: name,
Tag: valTag.tag,
ActualTag: valTag.tagVals[0][0],
NameNamespace: nsPrefix + cf.AltName,
Name: cf.AltName,
Field: cf.Name,
Tag: ct.aliasTag,
ActualTag: ct.tag,
Value: current.Interface(),
Param: valTag.tagVals[0][1],
Type: currentType,
Kind: currentKind,
Param: ct.param,
Type: typ,
Kind: kind,
}
return true
return
}
// VAL:
// 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,
// }
// if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, nsPrefix, errs, valTag, name, customName) {
// return
// }
ct = ct.next
}
// 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")
arr := []string{"val1", "#fff", "#000"}
errs = validate.Field(arr, "req")
NotEqual(t, errs, nil)
AssertError(t, errs, "[0]", "[0]", "iscolor")

Loading…
Cancel
Save