podman
486 строк · 11.8 Кб
1package validator
2
3import (
4"context"
5"fmt"
6"reflect"
7"strconv"
8)
9
10// per validate construct
11type validate struct {
12v *Validate
13top reflect.Value
14ns []byte
15actualNs []byte
16errs ValidationErrors
17includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise
18ffn FilterFunc
19slflParent reflect.Value // StructLevel & FieldLevel
20slCurrent reflect.Value // StructLevel & FieldLevel
21flField reflect.Value // StructLevel & FieldLevel
22cf *cField // StructLevel & FieldLevel
23ct *cTag // StructLevel & FieldLevel
24misc []byte // misc reusable
25str1 string // misc reusable
26str2 string // misc reusable
27fldIsPointer bool // StructLevel & FieldLevel
28isPartial bool
29hasExcludes bool
30}
31
32// parent and current will be the same the first run of validateStruct
33func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) {
34
35cs, ok := v.v.structCache.Get(typ)
36if !ok {
37cs = v.v.extractStructCache(current, typ.Name())
38}
39
40if len(ns) == 0 && len(cs.name) != 0 {
41
42ns = append(ns, cs.name...)
43ns = append(ns, '.')
44
45structNs = append(structNs, cs.name...)
46structNs = append(structNs, '.')
47}
48
49// ct is nil on top level struct, and structs as fields that have no tag info
50// so if nil or if not nil and the structonly tag isn't present
51if ct == nil || ct.typeof != typeStructOnly {
52
53var f *cField
54
55for i := 0; i < len(cs.fields); i++ {
56
57f = cs.fields[i]
58
59if v.isPartial {
60
61if v.ffn != nil {
62// used with StructFiltered
63if v.ffn(append(structNs, f.name...)) {
64continue
65}
66
67} else {
68// used with StructPartial & StructExcept
69_, ok = v.includeExclude[string(append(structNs, f.name...))]
70
71if (ok && v.hasExcludes) || (!ok && !v.hasExcludes) {
72continue
73}
74}
75}
76
77v.traverseField(ctx, current, current.Field(f.idx), ns, structNs, f, f.cTags)
78}
79}
80
81// check if any struct level validations, after all field validations already checked.
82// first iteration will have no info about nostructlevel tag, and is checked prior to
83// calling the next iteration of validateStruct called from traverseField.
84if cs.fn != nil {
85
86v.slflParent = parent
87v.slCurrent = current
88v.ns = ns
89v.actualNs = structNs
90
91cs.fn(ctx, v)
92}
93}
94
95// 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
96func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) {
97var typ reflect.Type
98var kind reflect.Kind
99
100current, kind, v.fldIsPointer = v.extractTypeInternal(current, false)
101
102var isNestedStruct bool
103
104switch kind {
105case reflect.Ptr, reflect.Interface, reflect.Invalid:
106
107if ct == nil {
108return
109}
110
111if ct.typeof == typeOmitEmpty || ct.typeof == typeIsDefault {
112return
113}
114
115if ct.typeof == typeOmitNil && (kind != reflect.Invalid && current.IsNil()) {
116return
117}
118
119if ct.hasTag {
120if kind == reflect.Invalid {
121v.str1 = string(append(ns, cf.altName...))
122if v.v.hasTagNameFunc {
123v.str2 = string(append(structNs, cf.name...))
124} else {
125v.str2 = v.str1
126}
127v.errs = append(v.errs,
128&fieldError{
129v: v.v,
130tag: ct.aliasTag,
131actualTag: ct.tag,
132ns: v.str1,
133structNs: v.str2,
134fieldLen: uint8(len(cf.altName)),
135structfieldLen: uint8(len(cf.name)),
136param: ct.param,
137kind: kind,
138},
139)
140return
141}
142
143v.str1 = string(append(ns, cf.altName...))
144if v.v.hasTagNameFunc {
145v.str2 = string(append(structNs, cf.name...))
146} else {
147v.str2 = v.str1
148}
149if !ct.runValidationWhenNil {
150v.errs = append(v.errs,
151&fieldError{
152v: v.v,
153tag: ct.aliasTag,
154actualTag: ct.tag,
155ns: v.str1,
156structNs: v.str2,
157fieldLen: uint8(len(cf.altName)),
158structfieldLen: uint8(len(cf.name)),
159value: current.Interface(),
160param: ct.param,
161kind: kind,
162typ: current.Type(),
163},
164)
165return
166}
167}
168
169if kind == reflect.Invalid {
170return
171}
172
173case reflect.Struct:
174isNestedStruct = !current.Type().ConvertibleTo(timeType)
175// For backward compatibility before struct level validation tags were supported
176// as there were a number of projects relying on `required` not failing on non-pointer
177// structs. Since it's basically nonsensical to use `required` with a non-pointer struct
178// are explicitly skipping the required validation for it. This WILL be removed in the
179// next major version.
180if isNestedStruct && !v.v.requiredStructEnabled && ct != nil && ct.tag == requiredTag {
181ct = ct.next
182}
183}
184
185typ = current.Type()
186
187OUTER:
188for {
189if ct == nil || !ct.hasTag || (isNestedStruct && len(cf.name) == 0) {
190// isNestedStruct check here
191if isNestedStruct {
192// if len == 0 then validating using 'Var' or 'VarWithValue'
193// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
194// VarWithField - this allows for validating against each field within the struct against a specific value
195// pretty handy in certain situations
196if len(cf.name) > 0 {
197ns = append(append(ns, cf.altName...), '.')
198structNs = append(append(structNs, cf.name...), '.')
199}
200
201v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
202}
203return
204}
205
206switch ct.typeof {
207case typeNoStructLevel:
208return
209
210case typeStructOnly:
211if isNestedStruct {
212// if len == 0 then validating using 'Var' or 'VarWithValue'
213// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
214// VarWithField - this allows for validating against each field within the struct against a specific value
215// pretty handy in certain situations
216if len(cf.name) > 0 {
217ns = append(append(ns, cf.altName...), '.')
218structNs = append(append(structNs, cf.name...), '.')
219}
220
221v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
222}
223return
224
225case typeOmitEmpty:
226
227// set Field Level fields
228v.slflParent = parent
229v.flField = current
230v.cf = cf
231v.ct = ct
232
233if !hasValue(v) {
234return
235}
236
237ct = ct.next
238continue
239
240case typeOmitNil:
241v.slflParent = parent
242v.flField = current
243v.cf = cf
244v.ct = ct
245
246switch field := v.Field(); field.Kind() {
247case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
248if field.IsNil() {
249return
250}
251default:
252if v.fldIsPointer && field.Interface() == nil {
253return
254}
255}
256
257ct = ct.next
258continue
259
260case typeEndKeys:
261return
262
263case typeDive:
264
265ct = ct.next
266
267// traverse slice or map here
268// or panic ;)
269switch kind {
270case reflect.Slice, reflect.Array:
271
272var i64 int64
273reusableCF := &cField{}
274
275for i := 0; i < current.Len(); i++ {
276
277i64 = int64(i)
278
279v.misc = append(v.misc[0:0], cf.name...)
280v.misc = append(v.misc, '[')
281v.misc = strconv.AppendInt(v.misc, i64, 10)
282v.misc = append(v.misc, ']')
283
284reusableCF.name = string(v.misc)
285
286if cf.namesEqual {
287reusableCF.altName = reusableCF.name
288} else {
289
290v.misc = append(v.misc[0:0], cf.altName...)
291v.misc = append(v.misc, '[')
292v.misc = strconv.AppendInt(v.misc, i64, 10)
293v.misc = append(v.misc, ']')
294
295reusableCF.altName = string(v.misc)
296}
297v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct)
298}
299
300case reflect.Map:
301
302var pv string
303reusableCF := &cField{}
304
305for _, key := range current.MapKeys() {
306
307pv = fmt.Sprintf("%v", key.Interface())
308
309v.misc = append(v.misc[0:0], cf.name...)
310v.misc = append(v.misc, '[')
311v.misc = append(v.misc, pv...)
312v.misc = append(v.misc, ']')
313
314reusableCF.name = string(v.misc)
315
316if cf.namesEqual {
317reusableCF.altName = reusableCF.name
318} else {
319v.misc = append(v.misc[0:0], cf.altName...)
320v.misc = append(v.misc, '[')
321v.misc = append(v.misc, pv...)
322v.misc = append(v.misc, ']')
323
324reusableCF.altName = string(v.misc)
325}
326
327if ct != nil && ct.typeof == typeKeys && ct.keys != nil {
328v.traverseField(ctx, parent, key, ns, structNs, reusableCF, ct.keys)
329// can be nil when just keys being validated
330if ct.next != nil {
331v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct.next)
332}
333} else {
334v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct)
335}
336}
337
338default:
339// throw error, if not a slice or map then should not have gotten here
340// bad dive tag
341panic("dive error! can't dive on a non slice or map")
342}
343
344return
345
346case typeOr:
347
348v.misc = v.misc[0:0]
349
350for {
351
352// set Field Level fields
353v.slflParent = parent
354v.flField = current
355v.cf = cf
356v.ct = ct
357
358if ct.fn(ctx, v) {
359if ct.isBlockEnd {
360ct = ct.next
361continue OUTER
362}
363
364// drain rest of the 'or' values, then continue or leave
365for {
366
367ct = ct.next
368
369if ct == nil {
370continue OUTER
371}
372
373if ct.typeof != typeOr {
374continue OUTER
375}
376
377if ct.isBlockEnd {
378ct = ct.next
379continue OUTER
380}
381}
382}
383
384v.misc = append(v.misc, '|')
385v.misc = append(v.misc, ct.tag...)
386
387if ct.hasParam {
388v.misc = append(v.misc, '=')
389v.misc = append(v.misc, ct.param...)
390}
391
392if ct.isBlockEnd || ct.next == nil {
393// if we get here, no valid 'or' value and no more tags
394v.str1 = string(append(ns, cf.altName...))
395
396if v.v.hasTagNameFunc {
397v.str2 = string(append(structNs, cf.name...))
398} else {
399v.str2 = v.str1
400}
401
402if ct.hasAlias {
403
404v.errs = append(v.errs,
405&fieldError{
406v: v.v,
407tag: ct.aliasTag,
408actualTag: ct.actualAliasTag,
409ns: v.str1,
410structNs: v.str2,
411fieldLen: uint8(len(cf.altName)),
412structfieldLen: uint8(len(cf.name)),
413value: current.Interface(),
414param: ct.param,
415kind: kind,
416typ: typ,
417},
418)
419
420} else {
421
422tVal := string(v.misc)[1:]
423
424v.errs = append(v.errs,
425&fieldError{
426v: v.v,
427tag: tVal,
428actualTag: tVal,
429ns: v.str1,
430structNs: v.str2,
431fieldLen: uint8(len(cf.altName)),
432structfieldLen: uint8(len(cf.name)),
433value: current.Interface(),
434param: ct.param,
435kind: kind,
436typ: typ,
437},
438)
439}
440
441return
442}
443
444ct = ct.next
445}
446
447default:
448
449// set Field Level fields
450v.slflParent = parent
451v.flField = current
452v.cf = cf
453v.ct = ct
454
455if !ct.fn(ctx, v) {
456v.str1 = string(append(ns, cf.altName...))
457
458if v.v.hasTagNameFunc {
459v.str2 = string(append(structNs, cf.name...))
460} else {
461v.str2 = v.str1
462}
463
464v.errs = append(v.errs,
465&fieldError{
466v: v.v,
467tag: ct.aliasTag,
468actualTag: ct.tag,
469ns: v.str1,
470structNs: v.str2,
471fieldLen: uint8(len(cf.altName)),
472structfieldLen: uint8(len(cf.name)),
473value: current.Interface(),
474param: ct.param,
475kind: kind,
476typ: typ,
477},
478)
479
480return
481}
482ct = ct.next
483}
484}
485
486}
487