• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

go-playground / validator / 13299705569

13 Feb 2025 03:16AM UTC coverage: 74.328% (+0.009%) from 74.319%
13299705569

Pull #1374

github

thenicolau
feat: add option early return in case of error
Pull Request #1374: feat: add option early return in case of error

7 of 7 new or added lines in 2 files covered. (100.0%)

5 existing lines in 1 file now uncovered.

12699 of 17085 relevant lines covered (74.33%)

84.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.74
/validator.go
1
package validator
2

3
import (
4
        "context"
5
        "fmt"
6
        "reflect"
7
        "strconv"
8
        "unsafe"
9
)
10

11
// per validate construct
12
type validate struct {
13
        v              *Validate
14
        top            reflect.Value
15
        ns             []byte
16
        actualNs       []byte
17
        errs           ValidationErrors
18
        includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise
19
        ffn            FilterFunc
20
        slflParent     reflect.Value // StructLevel & FieldLevel
21
        slCurrent      reflect.Value // StructLevel & FieldLevel
22
        flField        reflect.Value // StructLevel & FieldLevel
23
        cf             *cField       // StructLevel & FieldLevel
24
        ct             *cTag         // StructLevel & FieldLevel
25
        misc           []byte        // misc reusable
26
        str1           string        // misc reusable
27
        str2           string        // misc reusable
28
        fldIsPointer   bool          // StructLevel & FieldLevel
29
        isPartial      bool
30
        hasExcludes    bool
31
}
32

33
// parent and current will be the same the first run of validateStruct
34
func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) {
907✔
35

907✔
36
        cs, ok := v.v.structCache.Get(typ)
907✔
37
        if !ok {
1,262✔
38
                cs = v.v.extractStructCache(current, typ.Name())
355✔
39
        }
355✔
40

41
        if len(ns) == 0 && len(cs.name) != 0 {
1,303✔
42

398✔
43
                ns = append(ns, cs.name...)
398✔
44
                ns = append(ns, '.')
398✔
45

398✔
46
                structNs = append(structNs, cs.name...)
398✔
47
                structNs = append(structNs, '.')
398✔
48
        }
398✔
49

50
        // ct is nil on top level struct, and structs as fields that have no tag info
51
        // so if nil or if not nil and the structonly tag isn't present
52
        if ct == nil || ct.typeof != typeStructOnly {
1,806✔
53

901✔
54
                var f *cField
901✔
55

901✔
56
                for i := 0; i < len(cs.fields); i++ {
2,930✔
57
                        if v.v.earlyExit && len(v.errs) > 0 {
2,035✔
58
                                return
6✔
59
                        }
6✔
60
                        f = cs.fields[i]
2,023✔
61

2,023✔
62
                        if v.isPartial {
2,539✔
63

516✔
64
                                if v.ffn != nil {
643✔
65
                                        // used with StructFiltered
127✔
66
                                        if v.ffn(append(structNs, f.name...)) {
197✔
67
                                                continue
70✔
68
                                        }
69

70
                                } else {
389✔
71
                                        // used with StructPartial & StructExcept
389✔
72
                                        _, ok = v.includeExclude[string(append(structNs, f.name...))]
389✔
73

389✔
74
                                        if (ok && v.hasExcludes) || (!ok && !v.hasExcludes) {
503✔
75
                                                continue
114✔
76
                                        }
77
                                }
78
                        }
79

80
                        v.traverseField(ctx, current, current.Field(f.idx), ns, structNs, f, f.cTags)
1,839✔
81
                }
82
        }
83

84
        // check if any struct level validations, after all field validations already checked.
85
        // first iteration will have no info about nostructlevel tag, and is checked prior to
86
        // calling the next iteration of validateStruct called from traverseField.
87
        if cs.fn != nil {
903✔
88

11✔
89
                v.slflParent = parent
11✔
90
                v.slCurrent = current
11✔
91
                v.ns = ns
11✔
92
                v.actualNs = structNs
11✔
93

11✔
94
                cs.fn(ctx, v)
11✔
95
        }
11✔
96
}
97

98
// 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
99
func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) {
3,877✔
100
        var typ reflect.Type
3,877✔
101
        var kind reflect.Kind
3,877✔
102

3,877✔
103
        current, kind, v.fldIsPointer = v.extractTypeInternal(current, false)
3,877✔
104

3,877✔
105
        var isNestedStruct bool
3,877✔
106

3,877✔
107
        switch kind {
3,877✔
108
        case reflect.Ptr, reflect.Interface, reflect.Invalid:
145✔
109

145✔
110
                if ct == nil {
147✔
111
                        return
2✔
112
                }
2✔
113

114
                if ct.typeof == typeOmitEmpty || ct.typeof == typeIsDefault {
153✔
115
                        return
10✔
116
                }
10✔
117

118
                if ct.typeof == typeOmitNil && (kind != reflect.Invalid && current.IsNil()) {
136✔
119
                        return
3✔
120
                }
3✔
121

122
                if ct.hasTag {
213✔
123
                        if kind == reflect.Invalid {
96✔
124
                                v.str1 = string(append(ns, cf.altName...))
13✔
125
                                if v.v.hasTagNameFunc {
13✔
UNCOV
126
                                        v.str2 = string(append(structNs, cf.name...))
×
127
                                } else {
13✔
128
                                        v.str2 = v.str1
13✔
129
                                }
13✔
130
                                v.errs = append(v.errs,
13✔
131
                                        &fieldError{
13✔
132
                                                v:              v.v,
13✔
133
                                                tag:            ct.aliasTag,
13✔
134
                                                actualTag:      ct.tag,
13✔
135
                                                ns:             v.str1,
13✔
136
                                                structNs:       v.str2,
13✔
137
                                                fieldLen:       uint8(len(cf.altName)),
13✔
138
                                                structfieldLen: uint8(len(cf.name)),
13✔
139
                                                param:          ct.param,
13✔
140
                                                kind:           kind,
13✔
141
                                        },
13✔
142
                                )
13✔
143
                                return
13✔
144
                        }
145

146
                        v.str1 = string(append(ns, cf.altName...))
70✔
147
                        if v.v.hasTagNameFunc {
71✔
148
                                v.str2 = string(append(structNs, cf.name...))
1✔
149
                        } else {
70✔
150
                                v.str2 = v.str1
69✔
151
                        }
69✔
152
                        if !ct.runValidationWhenNil {
98✔
153
                                v.errs = append(v.errs,
28✔
154
                                        &fieldError{
28✔
155
                                                v:              v.v,
28✔
156
                                                tag:            ct.aliasTag,
28✔
157
                                                actualTag:      ct.tag,
28✔
158
                                                ns:             v.str1,
28✔
159
                                                structNs:       v.str2,
28✔
160
                                                fieldLen:       uint8(len(cf.altName)),
28✔
161
                                                structfieldLen: uint8(len(cf.name)),
28✔
162
                                                value:          getValue(current),
28✔
163
                                                param:          ct.param,
28✔
164
                                                kind:           kind,
28✔
165
                                                typ:            current.Type(),
28✔
166
                                        },
28✔
167
                                )
28✔
168
                                return
28✔
169
                        }
28✔
170
                }
171

172
                if kind == reflect.Invalid {
90✔
173
                        return
1✔
174
                }
1✔
175

176
        case reflect.Struct:
572✔
177
                isNestedStruct = !current.Type().ConvertibleTo(timeType)
572✔
178
                // For backward compatibility before struct level validation tags were supported
572✔
179
                // as there were a number of projects relying on `required` not failing on non-pointer
572✔
180
                // structs. Since it's basically nonsensical to use `required` with a non-pointer struct
572✔
181
                // are explicitly skipping the required validation for it. This WILL be removed in the
572✔
182
                // next major version.
572✔
183
                if isNestedStruct && !v.v.requiredStructEnabled && ct != nil && ct.tag == requiredTag {
598✔
184
                        ct = ct.next
26✔
185
                }
26✔
186
        }
187

188
        typ = current.Type()
3,818✔
189

3,818✔
190
OUTER:
3,818✔
191
        for {
9,378✔
192
                if ct == nil || !ct.hasTag || (isNestedStruct && len(cf.name) == 0) {
7,796✔
193
                        // isNestedStruct check here
2,236✔
194
                        if isNestedStruct {
2,632✔
195
                                // if len == 0 then validating using 'Var' or 'VarWithValue'
396✔
196
                                // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
396✔
197
                                // VarWithField - this allows for validating against each field within the struct against a specific value
396✔
198
                                //                pretty handy in certain situations
396✔
199
                                if len(cf.name) > 0 {
786✔
200
                                        ns = append(append(ns, cf.altName...), '.')
390✔
201
                                        structNs = append(append(structNs, cf.name...), '.')
390✔
202
                                }
390✔
203

204
                                v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
396✔
205
                        }
206
                        return
2,236✔
207
                }
208

209
                switch ct.typeof {
3,324✔
210
                case typeNoStructLevel:
5✔
211
                        return
5✔
212

213
                case typeStructOnly:
4✔
214
                        if isNestedStruct {
8✔
215
                                // if len == 0 then validating using 'Var' or 'VarWithValue'
4✔
216
                                // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
4✔
217
                                // VarWithField - this allows for validating against each field within the struct against a specific value
4✔
218
                                //                pretty handy in certain situations
4✔
219
                                if len(cf.name) > 0 {
8✔
220
                                        ns = append(append(ns, cf.altName...), '.')
4✔
221
                                        structNs = append(append(structNs, cf.name...), '.')
4✔
222
                                }
4✔
223

224
                                v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
4✔
225
                        }
226
                        return
4✔
227

228
                case typeOmitEmpty:
153✔
229

153✔
230
                        // set Field Level fields
153✔
231
                        v.slflParent = parent
153✔
232
                        v.flField = current
153✔
233
                        v.cf = cf
153✔
234
                        v.ct = ct
153✔
235

153✔
236
                        if !hasValue(v) {
244✔
237
                                return
91✔
238
                        }
91✔
239

240
                        ct = ct.next
62✔
241
                        continue
62✔
242

243
                case typeOmitNil:
7✔
244
                        v.slflParent = parent
7✔
245
                        v.flField = current
7✔
246
                        v.cf = cf
7✔
247
                        v.ct = ct
7✔
248

7✔
249
                        switch field := v.Field(); field.Kind() {
7✔
UNCOV
250
                        case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
×
251
                                if field.IsNil() {
×
252
                                        return
×
253
                                }
×
254
                        default:
7✔
255
                                if v.fldIsPointer && field.Interface() == nil {
7✔
UNCOV
256
                                        return
×
257
                                }
×
258
                        }
259

260
                        ct = ct.next
7✔
261
                        continue
7✔
262

263
                case typeEndKeys:
18✔
264
                        return
18✔
265

266
                case typeDive:
136✔
267

136✔
268
                        ct = ct.next
136✔
269

136✔
270
                        // traverse slice or map here
136✔
271
                        // or panic ;)
136✔
272
                        switch kind {
136✔
273
                        case reflect.Slice, reflect.Array:
108✔
274

108✔
275
                                var i64 int64
108✔
276
                                reusableCF := &cField{}
108✔
277

108✔
278
                                for i := 0; i < current.Len(); i++ {
357✔
279

249✔
280
                                        i64 = int64(i)
249✔
281

249✔
282
                                        v.misc = append(v.misc[0:0], cf.name...)
249✔
283
                                        v.misc = append(v.misc, '[')
249✔
284
                                        v.misc = strconv.AppendInt(v.misc, i64, 10)
249✔
285
                                        v.misc = append(v.misc, ']')
249✔
286

249✔
287
                                        reusableCF.name = string(v.misc)
249✔
288

249✔
289
                                        if cf.namesEqual {
414✔
290
                                                reusableCF.altName = reusableCF.name
165✔
291
                                        } else {
249✔
292

84✔
293
                                                v.misc = append(v.misc[0:0], cf.altName...)
84✔
294
                                                v.misc = append(v.misc, '[')
84✔
295
                                                v.misc = strconv.AppendInt(v.misc, i64, 10)
84✔
296
                                                v.misc = append(v.misc, ']')
84✔
297

84✔
298
                                                reusableCF.altName = string(v.misc)
84✔
299
                                        }
84✔
300
                                        v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct)
249✔
301
                                }
302

303
                        case reflect.Map:
27✔
304

27✔
305
                                var pv string
27✔
306
                                reusableCF := &cField{}
27✔
307

27✔
308
                                for _, key := range current.MapKeys() {
85✔
309

58✔
310
                                        pv = fmt.Sprintf("%v", key.Interface())
58✔
311

58✔
312
                                        v.misc = append(v.misc[0:0], cf.name...)
58✔
313
                                        v.misc = append(v.misc, '[')
58✔
314
                                        v.misc = append(v.misc, pv...)
58✔
315
                                        v.misc = append(v.misc, ']')
58✔
316

58✔
317
                                        reusableCF.name = string(v.misc)
58✔
318

58✔
319
                                        if cf.namesEqual {
106✔
320
                                                reusableCF.altName = reusableCF.name
48✔
321
                                        } else {
58✔
322
                                                v.misc = append(v.misc[0:0], cf.altName...)
10✔
323
                                                v.misc = append(v.misc, '[')
10✔
324
                                                v.misc = append(v.misc, pv...)
10✔
325
                                                v.misc = append(v.misc, ']')
10✔
326

10✔
327
                                                reusableCF.altName = string(v.misc)
10✔
328
                                        }
10✔
329

330
                                        if ct != nil && ct.typeof == typeKeys && ct.keys != nil {
85✔
331
                                                v.traverseField(ctx, parent, key, ns, structNs, reusableCF, ct.keys)
27✔
332
                                                // can be nil when just keys being validated
27✔
333
                                                if ct.next != nil {
51✔
334
                                                        v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct.next)
24✔
335
                                                }
24✔
336
                                        } else {
31✔
337
                                                v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct)
31✔
338
                                        }
31✔
339
                                }
340

341
                        default:
1✔
342
                                // throw error, if not a slice or map then should not have gotten here
1✔
343
                                // bad dive tag
1✔
344
                                panic("dive error! can't dive on a non slice or map")
1✔
345
                        }
346

347
                        return
135✔
348

349
                case typeOr:
46✔
350

46✔
351
                        v.misc = v.misc[0:0]
46✔
352

46✔
353
                        for {
148✔
354

102✔
355
                                // set Field Level fields
102✔
356
                                v.slflParent = parent
102✔
357
                                v.flField = current
102✔
358
                                v.cf = cf
102✔
359
                                v.ct = ct
102✔
360

102✔
361
                                if ct.fn(ctx, v) {
129✔
362
                                        if ct.isBlockEnd {
38✔
363
                                                ct = ct.next
11✔
364
                                                continue OUTER
11✔
365
                                        }
366

367
                                        // drain rest of the 'or' values, then continue or leave
368
                                        for {
47✔
369

31✔
370
                                                ct = ct.next
31✔
371

31✔
372
                                                if ct == nil {
31✔
UNCOV
373
                                                        continue OUTER
×
374
                                                }
375

376
                                                if ct.typeof != typeOr {
31✔
UNCOV
377
                                                        continue OUTER
×
378
                                                }
379

380
                                                if ct.isBlockEnd {
47✔
381
                                                        ct = ct.next
16✔
382
                                                        continue OUTER
16✔
383
                                                }
384
                                        }
385
                                }
386

387
                                v.misc = append(v.misc, '|')
75✔
388
                                v.misc = append(v.misc, ct.tag...)
75✔
389

75✔
390
                                if ct.hasParam {
91✔
391
                                        v.misc = append(v.misc, '=')
16✔
392
                                        v.misc = append(v.misc, ct.param...)
16✔
393
                                }
16✔
394

395
                                if ct.isBlockEnd || ct.next == nil {
94✔
396
                                        // if we get here, no valid 'or' value and no more tags
19✔
397
                                        v.str1 = string(append(ns, cf.altName...))
19✔
398

19✔
399
                                        if v.v.hasTagNameFunc {
20✔
400
                                                v.str2 = string(append(structNs, cf.name...))
1✔
401
                                        } else {
19✔
402
                                                v.str2 = v.str1
18✔
403
                                        }
18✔
404

405
                                        if ct.hasAlias {
31✔
406

12✔
407
                                                v.errs = append(v.errs,
12✔
408
                                                        &fieldError{
12✔
409
                                                                v:              v.v,
12✔
410
                                                                tag:            ct.aliasTag,
12✔
411
                                                                actualTag:      ct.actualAliasTag,
12✔
412
                                                                ns:             v.str1,
12✔
413
                                                                structNs:       v.str2,
12✔
414
                                                                fieldLen:       uint8(len(cf.altName)),
12✔
415
                                                                structfieldLen: uint8(len(cf.name)),
12✔
416
                                                                value:          getValue(current),
12✔
417
                                                                param:          ct.param,
12✔
418
                                                                kind:           kind,
12✔
419
                                                                typ:            typ,
12✔
420
                                                        },
12✔
421
                                                )
12✔
422

12✔
423
                                        } else {
19✔
424

7✔
425
                                                tVal := string(v.misc)[1:]
7✔
426

7✔
427
                                                v.errs = append(v.errs,
7✔
428
                                                        &fieldError{
7✔
429
                                                                v:              v.v,
7✔
430
                                                                tag:            tVal,
7✔
431
                                                                actualTag:      tVal,
7✔
432
                                                                ns:             v.str1,
7✔
433
                                                                structNs:       v.str2,
7✔
434
                                                                fieldLen:       uint8(len(cf.altName)),
7✔
435
                                                                structfieldLen: uint8(len(cf.name)),
7✔
436
                                                                value:          getValue(current),
7✔
437
                                                                param:          ct.param,
7✔
438
                                                                kind:           kind,
7✔
439
                                                                typ:            typ,
7✔
440
                                                        },
7✔
441
                                                )
7✔
442
                                        }
7✔
443

444
                                        return
19✔
445
                                }
446

447
                                ct = ct.next
56✔
448
                        }
449

450
                default:
2,955✔
451

2,955✔
452
                        // set Field Level fields
2,955✔
453
                        v.slflParent = parent
2,955✔
454
                        v.flField = current
2,955✔
455
                        v.cf = cf
2,955✔
456
                        v.ct = ct
2,955✔
457

2,955✔
458
                        if !ct.fn(ctx, v) {
4,210✔
459
                                v.str1 = string(append(ns, cf.altName...))
1,255✔
460

1,255✔
461
                                if v.v.hasTagNameFunc {
1,273✔
462
                                        v.str2 = string(append(structNs, cf.name...))
18✔
463
                                } else {
1,255✔
464
                                        v.str2 = v.str1
1,237✔
465
                                }
1,237✔
466

467
                                v.errs = append(v.errs,
1,255✔
468
                                        &fieldError{
1,255✔
469
                                                v:              v.v,
1,255✔
470
                                                tag:            ct.aliasTag,
1,255✔
471
                                                actualTag:      ct.tag,
1,255✔
472
                                                ns:             v.str1,
1,255✔
473
                                                structNs:       v.str2,
1,255✔
474
                                                fieldLen:       uint8(len(cf.altName)),
1,255✔
475
                                                structfieldLen: uint8(len(cf.name)),
1,255✔
476
                                                value:          getValue(current),
1,255✔
477
                                                param:          ct.param,
1,255✔
478
                                                kind:           kind,
1,255✔
479
                                                typ:            typ,
1,255✔
480
                                        },
1,255✔
481
                                )
1,255✔
482

1,255✔
483
                                return
1,255✔
484
                        }
485
                        ct = ct.next
1,646✔
486
                }
487
        }
488

489
}
490

491
func getValue(val reflect.Value) interface{} {
1,302✔
492
        if val.CanInterface() {
2,570✔
493
                return val.Interface()
1,268✔
494
        }
1,268✔
495

496
        if val.CanAddr() {
54✔
497
                return reflect.NewAt(val.Type(), unsafe.Pointer(val.UnsafeAddr())).Elem().Interface()
20✔
498
        }
20✔
499

500
        switch val.Kind() {
14✔
501
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
5✔
502
                return val.Int()
5✔
503
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
4✔
504
                return val.Uint()
4✔
505
        case reflect.Complex64, reflect.Complex128:
2✔
506
                return val.Complex()
2✔
507
        case reflect.Float32, reflect.Float64:
2✔
508
                return val.Float()
2✔
509
        default:
1✔
510
                return val.String()
1✔
511
        }
512
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc