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