14 typeDefault tagType = iota
23 type structCache struct {
25 m atomic.Value // map[reflect.Type]*cStruct
28 func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
29 c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
33 func (sc *structCache) Set(key reflect.Type, value *cStruct) {
35 m := sc.m.Load().(map[reflect.Type]*cStruct)
37 nm := make(map[reflect.Type]*cStruct, len(m)+1)
45 type tagCache struct {
47 m atomic.Value // map[string]*cTag
50 func (tc *tagCache) Get(key string) (c *cTag, found bool) {
51 c, found = tc.m.Load().(map[string]*cTag)[key]
55 func (tc *tagCache) Set(key string, value *cTag) {
57 m := tc.m.Load().(map[string]*cTag)
59 nm := make(map[string]*cTag, len(m)+1)
69 fields map[int]*cField
92 func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
94 v.structCache.lock.Lock()
95 defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
99 // could have been multiple trying to access, but once first is done this ensures struct
100 // isn't parsed again.
101 cs, ok := v.structCache.Get(typ)
106 cs = &cStruct{Name: sName, fields: make(map[int]*cField), fn: v.structLevelFuncs[typ]}
108 numFields := current.NumField()
111 var fld reflect.StructField
113 var customName string
115 for i := 0; i < numFields; i++ {
119 if !fld.Anonymous && fld.PkgPath != blank {
123 tag = fld.Tag.Get(v.tagName)
125 if tag == skipValidationTag {
129 customName = fld.Name
131 if v.fieldNameTag != blank {
133 name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
135 // dash check is for json "-" (aka skipValidationTag) means don't output in json
136 if name != "" && name != skipValidationTag {
141 // NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different
142 // and so only struct level caching can be used instead of combined with Field tag caching
145 ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, blank, false)
147 // even if field doesn't have validations need cTag for traversing to potential inner/nested
148 // elements of the field.
152 cs.fields[i] = &cField{Idx: i, Name: fld.Name, AltName: customName, cTags: ctag}
155 v.structCache.Set(typ, cs)
160 func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
164 noAlias := len(alias) == 0
165 tags := strings.Split(tag, tagSeparator)
167 for i := 0; i < len(tags); i++ {
175 if v.hasAliasValidators {
176 // check map for alias and process new tags, otherwise process as usual
177 if tagsVal, found := v.aliasValidators[t]; found {
180 firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
182 next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
183 current.next, current = next, curr
192 current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
195 current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
196 current = current.next
202 current.typeof = typeDive
206 current.typeof = typeOmitEmpty
210 current.typeof = typeStructOnly
213 case noStructLevelTag:
214 current.typeof = typeNoStructLevel
218 current.typeof = typeExists
223 // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
224 orVals := strings.Split(t, orSeparator)
226 for j := 0; j < len(orVals); j++ {
228 vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
232 current.aliasTag = alias
234 current.actualAliasTag = t
238 current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
239 current = current.next
242 current.tag = vals[0]
243 if len(current.tag) == 0 {
244 panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
247 if current.fn, ok = v.validationFuncs[current.tag]; !ok {
248 panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, fieldName)))
252 current.typeof = typeOr
256 current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)