14 typeDefault tagType = iota
24 invalidValidation = "Invalid validation tag on field '%s'"
25 undefinedValidation = "Undefined validation function '%s' on field '%s'"
28 type structCache struct {
30 m atomic.Value // map[reflect.Type]*cStruct
33 func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
34 c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
38 func (sc *structCache) Set(key reflect.Type, value *cStruct) {
40 m := sc.m.Load().(map[reflect.Type]*cStruct)
42 nm := make(map[reflect.Type]*cStruct, len(m)+1)
50 type tagCache struct {
52 m atomic.Value // map[string]*cTag
55 func (tc *tagCache) Get(key string) (c *cTag, found bool) {
56 c, found = tc.m.Load().(map[string]*cTag)[key]
60 func (tc *tagCache) Set(key string, value *cTag) {
62 m := tc.m.Load().(map[string]*cTag)
64 nm := make(map[string]*cTag, len(m)+1)
98 func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
100 v.structCache.lock.Lock()
101 defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
103 typ := current.Type()
105 // could have been multiple trying to access, but once first is done this ensures struct
106 // isn't parsed again.
107 cs, ok := v.structCache.Get(typ)
112 cs = &cStruct{name: sName, fields: make([]*cField, 0), fn: v.structLevelFuncs[typ]}
114 numFields := current.NumField()
117 var fld reflect.StructField
119 var customName string
121 for i := 0; i < numFields; i++ {
125 if !fld.Anonymous && len(fld.PkgPath) > 0 {
129 tag = fld.Tag.Get(v.tagName)
131 if tag == skipValidationTag {
135 customName = fld.Name
137 if v.hasTagNameFunc {
139 name := v.tagNameFunc(fld)
146 // NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different
147 // and so only struct level caching can be used instead of combined with Field tag caching
150 ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, "", false)
152 // even if field doesn't have validations need cTag for traversing to potential inner/nested
153 // elements of the field.
157 cs.fields = append(cs.fields, &cField{
162 namesEqual: fld.Name == customName,
166 v.structCache.Set(typ, cs)
171 func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
175 noAlias := len(alias) == 0
176 tags := strings.Split(tag, tagSeparator)
178 for i := 0; i < len(tags); i++ {
186 // check map for alias and process new tags, otherwise process as usual
187 if tagsVal, found := v.aliases[t]; found {
190 firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
192 next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
193 current.next, current = next, curr
201 current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
204 current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
205 current = current.next
211 current.typeof = typeDive
215 current.typeof = typeOmitEmpty
219 current.typeof = typeStructOnly
222 case noStructLevelTag:
223 current.typeof = typeNoStructLevel
229 current.typeof = typeIsDefault
232 // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
233 orVals := strings.Split(t, orSeparator)
235 for j := 0; j < len(orVals); j++ {
237 vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
241 current.aliasTag = alias
243 current.actualAliasTag = t
247 current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
248 current = current.next
251 current.tag = vals[0]
252 if len(current.tag) == 0 {
253 panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
256 if current.fn, ok = v.validations[current.tag]; !ok {
257 panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName)))
261 current.typeof = typeOr
265 current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)