podman
332 строки · 7.6 Кб
1package validator
2
3import (
4"fmt"
5"reflect"
6"strings"
7"sync"
8"sync/atomic"
9)
10
11type tagType uint8
12
13const (
14typeDefault tagType = iota
15typeOmitEmpty
16typeIsDefault
17typeNoStructLevel
18typeStructOnly
19typeDive
20typeOr
21typeKeys
22typeEndKeys
23typeOmitNil
24)
25
26const (
27invalidValidation = "Invalid validation tag on field '%s'"
28undefinedValidation = "Undefined validation function '%s' on field '%s'"
29keysTagNotDefined = "'" + endKeysTag + "' tag encountered without a corresponding '" + keysTag + "' tag"
30)
31
32type structCache struct {
33lock sync.Mutex
34m atomic.Value // map[reflect.Type]*cStruct
35}
36
37func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
38c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
39return
40}
41
42func (sc *structCache) Set(key reflect.Type, value *cStruct) {
43m := sc.m.Load().(map[reflect.Type]*cStruct)
44nm := make(map[reflect.Type]*cStruct, len(m)+1)
45for k, v := range m {
46nm[k] = v
47}
48nm[key] = value
49sc.m.Store(nm)
50}
51
52type tagCache struct {
53lock sync.Mutex
54m atomic.Value // map[string]*cTag
55}
56
57func (tc *tagCache) Get(key string) (c *cTag, found bool) {
58c, found = tc.m.Load().(map[string]*cTag)[key]
59return
60}
61
62func (tc *tagCache) Set(key string, value *cTag) {
63m := tc.m.Load().(map[string]*cTag)
64nm := make(map[string]*cTag, len(m)+1)
65for k, v := range m {
66nm[k] = v
67}
68nm[key] = value
69tc.m.Store(nm)
70}
71
72type cStruct struct {
73name string
74fields []*cField
75fn StructLevelFuncCtx
76}
77
78type cField struct {
79idx int
80name string
81altName string
82namesEqual bool
83cTags *cTag
84}
85
86type cTag struct {
87tag string
88aliasTag string
89actualAliasTag string
90param string
91keys *cTag // only populated when using tag's 'keys' and 'endkeys' for map key validation
92next *cTag
93fn FuncCtx
94typeof tagType
95hasTag bool
96hasAlias bool
97hasParam bool // true if parameter used eg. eq= where the equal sign has been set
98isBlockEnd bool // indicates the current tag represents the last validation in the block
99runValidationWhenNil bool
100}
101
102func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
103v.structCache.lock.Lock()
104defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
105
106typ := current.Type()
107
108// could have been multiple trying to access, but once first is done this ensures struct
109// isn't parsed again.
110cs, ok := v.structCache.Get(typ)
111if ok {
112return cs
113}
114
115cs = &cStruct{name: sName, fields: make([]*cField, 0), fn: v.structLevelFuncs[typ]}
116
117numFields := current.NumField()
118rules := v.rules[typ]
119
120var ctag *cTag
121var fld reflect.StructField
122var tag string
123var customName string
124
125for i := 0; i < numFields; i++ {
126
127fld = typ.Field(i)
128
129if !fld.Anonymous && len(fld.PkgPath) > 0 {
130continue
131}
132
133if rtag, ok := rules[fld.Name]; ok {
134tag = rtag
135} else {
136tag = fld.Tag.Get(v.tagName)
137}
138
139if tag == skipValidationTag {
140continue
141}
142
143customName = fld.Name
144
145if v.hasTagNameFunc {
146name := v.tagNameFunc(fld)
147if len(name) > 0 {
148customName = name
149}
150}
151
152// NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different
153// and so only struct level caching can be used instead of combined with Field tag caching
154
155if len(tag) > 0 {
156ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, "", false)
157} else {
158// even if field doesn't have validations need cTag for traversing to potential inner/nested
159// elements of the field.
160ctag = new(cTag)
161}
162
163cs.fields = append(cs.fields, &cField{
164idx: i,
165name: fld.Name,
166altName: customName,
167cTags: ctag,
168namesEqual: fld.Name == customName,
169})
170}
171v.structCache.Set(typ, cs)
172return cs
173}
174
175func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
176var t string
177noAlias := len(alias) == 0
178tags := strings.Split(tag, tagSeparator)
179
180for i := 0; i < len(tags); i++ {
181t = tags[i]
182if noAlias {
183alias = t
184}
185
186// check map for alias and process new tags, otherwise process as usual
187if tagsVal, found := v.aliases[t]; found {
188if i == 0 {
189firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
190} else {
191next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
192current.next, current = next, curr
193
194}
195continue
196}
197
198var prevTag tagType
199
200if i == 0 {
201current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true, typeof: typeDefault}
202firstCtag = current
203} else {
204prevTag = current.typeof
205current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
206current = current.next
207}
208
209switch t {
210case diveTag:
211current.typeof = typeDive
212continue
213
214case keysTag:
215current.typeof = typeKeys
216
217if i == 0 || prevTag != typeDive {
218panic(fmt.Sprintf("'%s' tag must be immediately preceded by the '%s' tag", keysTag, diveTag))
219}
220
221current.typeof = typeKeys
222
223// need to pass along only keys tag
224// need to increment i to skip over the keys tags
225b := make([]byte, 0, 64)
226
227i++
228
229for ; i < len(tags); i++ {
230
231b = append(b, tags[i]...)
232b = append(b, ',')
233
234if tags[i] == endKeysTag {
235break
236}
237}
238
239current.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), fieldName, "", false)
240continue
241
242case endKeysTag:
243current.typeof = typeEndKeys
244
245// if there are more in tags then there was no keysTag defined
246// and an error should be thrown
247if i != len(tags)-1 {
248panic(keysTagNotDefined)
249}
250return
251
252case omitempty:
253current.typeof = typeOmitEmpty
254continue
255
256case omitnil:
257current.typeof = typeOmitNil
258continue
259
260case structOnlyTag:
261current.typeof = typeStructOnly
262continue
263
264case noStructLevelTag:
265current.typeof = typeNoStructLevel
266continue
267
268default:
269if t == isdefault {
270current.typeof = typeIsDefault
271}
272// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
273orVals := strings.Split(t, orSeparator)
274
275for j := 0; j < len(orVals); j++ {
276vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
277if noAlias {
278alias = vals[0]
279current.aliasTag = alias
280} else {
281current.actualAliasTag = t
282}
283
284if j > 0 {
285current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
286current = current.next
287}
288current.hasParam = len(vals) > 1
289
290current.tag = vals[0]
291if len(current.tag) == 0 {
292panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
293}
294
295if wrapper, ok := v.validations[current.tag]; ok {
296current.fn = wrapper.fn
297current.runValidationWhenNil = wrapper.runValidatinOnNil
298} else {
299panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName)))
300}
301
302if len(orVals) > 1 {
303current.typeof = typeOr
304}
305
306if len(vals) > 1 {
307current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
308}
309}
310current.isBlockEnd = true
311}
312}
313return
314}
315
316func (v *Validate) fetchCacheTag(tag string) *cTag {
317// find cached tag
318ctag, found := v.tagCache.Get(tag)
319if !found {
320v.tagCache.lock.Lock()
321defer v.tagCache.lock.Unlock()
322
323// could have been multiple trying to access, but once first is done this ensures tag
324// isn't parsed again.
325ctag, found = v.tagCache.Get(tag)
326if !found {
327ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false)
328v.tagCache.Set(tag, ctag)
329}
330}
331return ctag
332}
333