diff --git a/cache.go b/cache.go index 289226e..655b522 100644 --- a/cache.go +++ b/cache.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) } -func (s *structCacheMap) Get(key reflect.Type) (*cachedStruct, bool) { - s.lock.RLock() - value, ok := s.m[key] - s.lock.RUnlock() - return value, ok +type tagCache struct { + lock sync.Mutex + m atomic.Value // map[string]*cTag } -func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) { - s.lock.Lock() - s.m[key] = value - s.lock.Unlock() +func (tc *tagCache) Get(key string) (c *cTag, found bool) { + c, found = tc.m.Load().(map[string]*cTag)[key] + return } -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 + 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. -type tagCacheMap struct { - lock sync.RWMutex - m map[string]*cachedTag -} +func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct { -func (s *tagCacheMap) Get(key string) (*cachedTag, bool) { - s.lock.RLock() - value, ok := s.m[key] - s.lock.RUnlock() + v.structCache.lock.Lock() + defer v.structCache.lock.Unlock() - 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) { - s.lock.Lock() - s.m[key] = value - s.lock.Unlock() +// 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) + + 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 } diff --git a/util.go b/util.go index bdb5cda..07d59a0 100644 --- a/util.go +++ b/util.go @@ -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 +// } diff --git a/validator.go b/validator.go index 85bcf97..73ba8f2 100644 --- a/validator.go +++ b/validator.go @@ -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,9 +148,11 @@ type Validate struct { hasCustomFuncs bool hasAliasValidators bool hasStructLevelFuncs bool - tagCache *tagCacheMap - structCache *structCacheMap - errsPool *sync.Pool + // oldTagCache *tagCacheMap + // oldStructCache *structCacheMap + tagCache *tagCache + structCache *structCache + errsPool *sync.Pool } func (v *Validate) initCheck() { @@ -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,140 +527,162 @@ 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 + + // 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 { - 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 { + if partial { - _, ok = includeExclude[errPrefix+f.Name] + _, ok = includeExclude[errPrefix+f.Name] - if (ok && exclude) || (!ok && !exclude) { - continue - } + 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) } + // 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. - 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) { + 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 } - } - 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") - } - } -} + if ct.isOrVal { + + 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, + } + } -// 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) { + continue OUTER + // break + // goto VAL + } - 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) - } -} + // valFunc, ok = v.validationFuncs[val[0]] + // if !ok { + // panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) + // } -// 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.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) { - 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) - } -} + // drain rest of the 'or' values, then continue or leave + 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 -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 { + ct = ct.next - var valFunc Func - var ok bool + if ct == nil { + 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]] - if !ok { - panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) - } + ns := errPrefix + cf.Name - if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) { - return false - } + 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 + } - 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{ 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.tag, 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, + Param: ct.param, + Type: typ, + Kind: kind, } - } - 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 + 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, + } + // 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 } - 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 +// } diff --git a/validator_test.go b/validator_test.go index 248ae3f..6d4cadd 100644 --- a/validator_test.go +++ b/validator_test.go @@ -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")