OSDN Git Service

new repo
[bytom/vapor.git] / vendor / gopkg.in / go-playground / validator.v9 / cache.go
1 package validator
2
3 import (
4         "fmt"
5         "reflect"
6         "strings"
7         "sync"
8         "sync/atomic"
9 )
10
11 type tagType uint8
12
13 const (
14         typeDefault tagType = iota
15         typeOmitEmpty
16         typeIsDefault
17         typeNoStructLevel
18         typeStructOnly
19         typeDive
20         typeOr
21 )
22
23 const (
24         invalidValidation   = "Invalid validation tag on field '%s'"
25         undefinedValidation = "Undefined validation function '%s' on field '%s'"
26 )
27
28 type structCache struct {
29         lock sync.Mutex
30         m    atomic.Value // map[reflect.Type]*cStruct
31 }
32
33 func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
34         c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
35         return
36 }
37
38 func (sc *structCache) Set(key reflect.Type, value *cStruct) {
39
40         m := sc.m.Load().(map[reflect.Type]*cStruct)
41
42         nm := make(map[reflect.Type]*cStruct, len(m)+1)
43         for k, v := range m {
44                 nm[k] = v
45         }
46         nm[key] = value
47         sc.m.Store(nm)
48 }
49
50 type tagCache struct {
51         lock sync.Mutex
52         m    atomic.Value // map[string]*cTag
53 }
54
55 func (tc *tagCache) Get(key string) (c *cTag, found bool) {
56         c, found = tc.m.Load().(map[string]*cTag)[key]
57         return
58 }
59
60 func (tc *tagCache) Set(key string, value *cTag) {
61
62         m := tc.m.Load().(map[string]*cTag)
63
64         nm := make(map[string]*cTag, len(m)+1)
65         for k, v := range m {
66                 nm[k] = v
67         }
68         nm[key] = value
69         tc.m.Store(nm)
70 }
71
72 type cStruct struct {
73         name   string
74         fields []*cField
75         fn     StructLevelFuncCtx
76 }
77
78 type cField struct {
79         idx        int
80         name       string
81         altName    string
82         namesEqual bool
83         cTags      *cTag
84 }
85
86 type cTag struct {
87         tag            string
88         aliasTag       string
89         actualAliasTag string
90         param          string
91         hasAlias       bool
92         typeof         tagType
93         hasTag         bool
94         fn             FuncCtx
95         next           *cTag
96 }
97
98 func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
99
100         v.structCache.lock.Lock()
101         defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
102
103         typ := current.Type()
104
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)
108         if ok {
109                 return cs
110         }
111
112         cs = &cStruct{name: sName, fields: make([]*cField, 0), fn: v.structLevelFuncs[typ]}
113
114         numFields := current.NumField()
115
116         var ctag *cTag
117         var fld reflect.StructField
118         var tag string
119         var customName string
120
121         for i := 0; i < numFields; i++ {
122
123                 fld = typ.Field(i)
124
125                 if !fld.Anonymous && len(fld.PkgPath) > 0 {
126                         continue
127                 }
128
129                 tag = fld.Tag.Get(v.tagName)
130
131                 if tag == skipValidationTag {
132                         continue
133                 }
134
135                 customName = fld.Name
136
137                 if v.hasTagNameFunc {
138
139                         name := v.tagNameFunc(fld)
140
141                         if len(name) > 0 {
142                                 customName = name
143                         }
144                 }
145
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
148
149                 if len(tag) > 0 {
150                         ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, "", false)
151                 } else {
152                         // even if field doesn't have validations need cTag for traversing to potential inner/nested
153                         // elements of the field.
154                         ctag = new(cTag)
155                 }
156
157                 cs.fields = append(cs.fields, &cField{
158                         idx:        i,
159                         name:       fld.Name,
160                         altName:    customName,
161                         cTags:      ctag,
162                         namesEqual: fld.Name == customName,
163                 })
164         }
165
166         v.structCache.Set(typ, cs)
167
168         return cs
169 }
170
171 func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
172
173         var t string
174         var ok bool
175         noAlias := len(alias) == 0
176         tags := strings.Split(tag, tagSeparator)
177
178         for i := 0; i < len(tags); i++ {
179
180                 t = tags[i]
181
182                 if noAlias {
183                         alias = t
184                 }
185
186                 // check map for alias and process new tags, otherwise process as usual
187                 if tagsVal, found := v.aliases[t]; found {
188
189                         if i == 0 {
190                                 firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
191                         } else {
192                                 next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
193                                 current.next, current = next, curr
194
195                         }
196
197                         continue
198                 }
199
200                 if i == 0 {
201                         current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
202                         firstCtag = current
203                 } else {
204                         current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
205                         current = current.next
206                 }
207
208                 switch t {
209
210                 case diveTag:
211                         current.typeof = typeDive
212                         continue
213
214                 case omitempty:
215                         current.typeof = typeOmitEmpty
216                         continue
217
218                 case structOnlyTag:
219                         current.typeof = typeStructOnly
220                         continue
221
222                 case noStructLevelTag:
223                         current.typeof = typeNoStructLevel
224                         continue
225
226                 default:
227
228                         if t == isdefault {
229                                 current.typeof = typeIsDefault
230                         }
231
232                         // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
233                         orVals := strings.Split(t, orSeparator)
234
235                         for j := 0; j < len(orVals); j++ {
236
237                                 vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
238
239                                 if noAlias {
240                                         alias = vals[0]
241                                         current.aliasTag = alias
242                                 } else {
243                                         current.actualAliasTag = t
244                                 }
245
246                                 if j > 0 {
247                                         current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
248                                         current = current.next
249                                 }
250
251                                 current.tag = vals[0]
252                                 if len(current.tag) == 0 {
253                                         panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
254                                 }
255
256                                 if current.fn, ok = v.validations[current.tag]; !ok {
257                                         panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName)))
258                                 }
259
260                                 if len(orVals) > 1 {
261                                         current.typeof = typeOr
262                                 }
263
264                                 if len(vals) > 1 {
265                                         current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
266                                 }
267                         }
268                 }
269         }
270
271         return
272 }