Add contextual validation support via context.Context (#296)
* Add contextual validation support via context.Context
Added:
- RegisterValidationCtx
- RegisterStructValidationCtx
- StructCtx
- StructFilteredCtx
- StructPartialCtx
- StructExceptCtx
- VarCtx
- VarWithValueCtx
Dean Karn authored 6 years ago
GitHub committed 6 years ago
64 | 64 | |
65 | 65 | Benchmarks |
66 | 66 | ------ |
67 | ###### Run on i5-7600 16 GB DDR4-2400 using Go version go1.8 linux/amd64 | |
67 | ###### Run on Dell XPS 15 i7-7700HQ 32GB Go version go1.8.3 linux/amd64 | |
68 | 68 | ```go |
69 | BenchmarkFieldSuccess-4 20000000 74.3 ns/op 0 B/op 0 allocs/op | |
70 | BenchmarkFieldSuccessParallel-4 50000000 31.5 ns/op 0 B/op 0 allocs/op | |
71 | BenchmarkFieldFailure-4 3000000 556 ns/op 208 B/op 4 allocs/op | |
72 | BenchmarkFieldFailureParallel-4 20000000 88.7 ns/op 208 B/op 4 allocs/op | |
73 | BenchmarkFieldDiveSuccess-4 2000000 630 ns/op 201 B/op 11 allocs/op | |
74 | BenchmarkFieldDiveSuccessParallel-4 10000000 173 ns/op 201 B/op 11 allocs/op | |
75 | BenchmarkFieldDiveFailure-4 1000000 1350 ns/op 412 B/op 16 allocs/op | |
76 | BenchmarkFieldDiveFailureParallel-4 5000000 250 ns/op 412 B/op 16 allocs/op | |
77 | BenchmarkFieldCustomTypeSuccess-4 10000000 202 ns/op 32 B/op 2 allocs/op | |
78 | BenchmarkFieldCustomTypeSuccessParallel-4 20000000 63.5 ns/op 32 B/op 2 allocs/op | |
79 | BenchmarkFieldCustomTypeFailure-4 5000000 568 ns/op 208 B/op 4 allocs/op | |
80 | BenchmarkFieldCustomTypeFailureParallel-4 20000000 87.5 ns/op 208 B/op 4 allocs/op | |
81 | BenchmarkFieldOrTagSuccess-4 2000000 703 ns/op 16 B/op 1 allocs/op | |
82 | BenchmarkFieldOrTagSuccessParallel-4 3000000 447 ns/op 16 B/op 1 allocs/op | |
83 | BenchmarkFieldOrTagFailure-4 3000000 604 ns/op 224 B/op 5 allocs/op | |
84 | BenchmarkFieldOrTagFailureParallel-4 5000000 353 ns/op 224 B/op 5 allocs/op | |
85 | BenchmarkStructLevelValidationSuccess-4 10000000 190 ns/op 32 B/op 2 allocs/op | |
86 | BenchmarkStructLevelValidationSuccessParallel-4 30000000 59.9 ns/op 32 B/op 2 allocs/op | |
87 | BenchmarkStructLevelValidationFailure-4 2000000 705 ns/op 304 B/op 8 allocs/op | |
88 | BenchmarkStructLevelValidationFailureParallel-4 10000000 146 ns/op 304 B/op 8 allocs/op | |
89 | BenchmarkStructSimpleCustomTypeSuccess-4 5000000 361 ns/op 32 B/op 2 allocs/op | |
90 | BenchmarkStructSimpleCustomTypeSuccessParallel-4 20000000 101 ns/op 32 B/op 2 allocs/op | |
91 | BenchmarkStructSimpleCustomTypeFailure-4 1000000 1210 ns/op 424 B/op 9 allocs/op | |
92 | BenchmarkStructSimpleCustomTypeFailureParallel-4 10000000 196 ns/op 440 B/op 10 allocs/op | |
93 | BenchmarkStructFilteredSuccess-4 2000000 757 ns/op 288 B/op 9 allocs/op | |
94 | BenchmarkStructFilteredSuccessParallel-4 10000000 167 ns/op 288 B/op 9 allocs/op | |
95 | BenchmarkStructFilteredFailure-4 3000000 619 ns/op 256 B/op 7 allocs/op | |
96 | BenchmarkStructFilteredFailureParallel-4 10000000 134 ns/op 256 B/op 7 allocs/op | |
97 | BenchmarkStructPartialSuccess-4 2000000 687 ns/op 256 B/op 6 allocs/op | |
98 | BenchmarkStructPartialSuccessParallel-4 10000000 159 ns/op 256 B/op 6 allocs/op | |
99 | BenchmarkStructPartialFailure-4 1000000 1281 ns/op 480 B/op 11 allocs/op | |
100 | BenchmarkStructPartialFailureParallel-4 10000000 218 ns/op 480 B/op 11 allocs/op | |
101 | BenchmarkStructExceptSuccess-4 1000000 1041 ns/op 496 B/op 12 allocs/op | |
102 | BenchmarkStructExceptSuccessParallel-4 10000000 140 ns/op 240 B/op 5 allocs/op | |
103 | BenchmarkStructExceptFailure-4 1000000 1014 ns/op 464 B/op 10 allocs/op | |
104 | BenchmarkStructExceptFailureParallel-4 10000000 201 ns/op 464 B/op 10 allocs/op | |
105 | BenchmarkStructSimpleCrossFieldSuccess-4 5000000 364 ns/op 72 B/op 3 allocs/op | |
106 | BenchmarkStructSimpleCrossFieldSuccessParallel-4 20000000 103 ns/op 72 B/op 3 allocs/op | |
107 | BenchmarkStructSimpleCrossFieldFailure-4 2000000 789 ns/op 304 B/op 8 allocs/op | |
108 | BenchmarkStructSimpleCrossFieldFailureParallel-4 10000000 174 ns/op 304 B/op 8 allocs/op | |
109 | BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 3000000 522 ns/op 80 B/op 4 allocs/op | |
110 | BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-4 10000000 146 ns/op 80 B/op 4 allocs/op | |
111 | BenchmarkStructSimpleCrossStructCrossFieldFailure-4 2000000 879 ns/op 320 B/op 9 allocs/op | |
112 | BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-4 10000000 225 ns/op 320 B/op 9 allocs/op | |
113 | BenchmarkStructSimpleSuccess-4 10000000 223 ns/op 0 B/op 0 allocs/op | |
114 | BenchmarkStructSimpleSuccessParallel-4 20000000 63.3 ns/op 0 B/op 0 allocs/op | |
115 | BenchmarkStructSimpleFailure-4 2000000 1097 ns/op 424 B/op 9 allocs/op | |
116 | BenchmarkStructSimpleFailureParallel-4 10000000 182 ns/op 424 B/op 9 allocs/op | |
117 | BenchmarkStructComplexSuccess-4 1000000 1362 ns/op 128 B/op 8 allocs/op | |
118 | BenchmarkStructComplexSuccessParallel-4 5000000 359 ns/op 128 B/op 8 allocs/op | |
119 | BenchmarkStructComplexFailure-4 300000 6446 ns/op 3040 B/op 53 allocs/op | |
120 | BenchmarkStructComplexFailureParallel-4 1000000 1203 ns/op 3040 B/op 53 allocs/op | |
69 | go test -run=XXX -bench=. -benchmem=true | |
70 | BenchmarkFieldSuccess-8 20000000 88.3 ns/op 0 B/op 0 allocs/op | |
71 | BenchmarkFieldSuccessParallel-8 50000000 30.4 ns/op 0 B/op 0 allocs/op | |
72 | BenchmarkFieldFailure-8 3000000 428 ns/op 208 B/op 4 allocs/op | |
73 | BenchmarkFieldFailureParallel-8 20000000 96.0 ns/op 208 B/op 4 allocs/op | |
74 | BenchmarkFieldDiveSuccess-8 2000000 695 ns/op 201 B/op 11 allocs/op | |
75 | BenchmarkFieldDiveSuccessParallel-8 10000000 205 ns/op 201 B/op 11 allocs/op | |
76 | BenchmarkFieldDiveFailure-8 1000000 1083 ns/op 412 B/op 16 allocs/op | |
77 | BenchmarkFieldDiveFailureParallel-8 5000000 278 ns/op 413 B/op 16 allocs/op | |
78 | BenchmarkFieldCustomTypeSuccess-8 10000000 229 ns/op 32 B/op 2 allocs/op | |
79 | BenchmarkFieldCustomTypeSuccessParallel-8 20000000 72.4 ns/op 32 B/op 2 allocs/op | |
80 | BenchmarkFieldCustomTypeFailure-8 5000000 377 ns/op 208 B/op 4 allocs/op | |
81 | BenchmarkFieldCustomTypeFailureParallel-8 20000000 93.0 ns/op 208 B/op 4 allocs/op | |
82 | BenchmarkFieldOrTagSuccess-8 2000000 767 ns/op 16 B/op 1 allocs/op | |
83 | BenchmarkFieldOrTagSuccessParallel-8 3000000 425 ns/op 16 B/op 1 allocs/op | |
84 | BenchmarkFieldOrTagFailure-8 2000000 548 ns/op 224 B/op 5 allocs/op | |
85 | BenchmarkFieldOrTagFailureParallel-8 3000000 411 ns/op 224 B/op 5 allocs/op | |
86 | BenchmarkStructLevelValidationSuccess-8 10000000 219 ns/op 32 B/op 2 allocs/op | |
87 | BenchmarkStructLevelValidationSuccessParallel-8 20000000 69.2 ns/op 32 B/op 2 allocs/op | |
88 | BenchmarkStructLevelValidationFailure-8 2000000 628 ns/op 304 B/op 8 allocs/op | |
89 | BenchmarkStructLevelValidationFailureParallel-8 10000000 165 ns/op 304 B/op 8 allocs/op | |
90 | BenchmarkStructSimpleCustomTypeSuccess-8 3000000 411 ns/op 32 B/op 2 allocs/op | |
91 | BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 122 ns/op 32 B/op 2 allocs/op | |
92 | BenchmarkStructSimpleCustomTypeFailure-8 1000000 1022 ns/op 424 B/op 9 allocs/op | |
93 | BenchmarkStructSimpleCustomTypeFailureParallel-8 10000000 228 ns/op 440 B/op 10 allocs/op | |
94 | BenchmarkStructFilteredSuccess-8 2000000 737 ns/op 288 B/op 9 allocs/op | |
95 | BenchmarkStructFilteredSuccessParallel-8 10000000 192 ns/op 288 B/op 9 allocs/op | |
96 | BenchmarkStructFilteredFailure-8 3000000 583 ns/op 256 B/op 7 allocs/op | |
97 | BenchmarkStructFilteredFailureParallel-8 10000000 152 ns/op 256 B/op 7 allocs/op | |
98 | BenchmarkStructPartialSuccess-8 2000000 731 ns/op 256 B/op 6 allocs/op | |
99 | BenchmarkStructPartialSuccessParallel-8 10000000 173 ns/op 256 B/op 6 allocs/op | |
100 | BenchmarkStructPartialFailure-8 1000000 1164 ns/op 480 B/op 11 allocs/op | |
101 | BenchmarkStructPartialFailureParallel-8 5000000 253 ns/op 480 B/op 11 allocs/op | |
102 | BenchmarkStructExceptSuccess-8 1000000 1337 ns/op 496 B/op 12 allocs/op | |
103 | BenchmarkStructExceptSuccessParallel-8 10000000 153 ns/op 240 B/op 5 allocs/op | |
104 | BenchmarkStructExceptFailure-8 2000000 954 ns/op 464 B/op 10 allocs/op | |
105 | BenchmarkStructExceptFailureParallel-8 5000000 234 ns/op 464 B/op 10 allocs/op | |
106 | BenchmarkStructSimpleCrossFieldSuccess-8 3000000 420 ns/op 72 B/op 3 allocs/op | |
107 | BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 125 ns/op 72 B/op 3 allocs/op | |
108 | BenchmarkStructSimpleCrossFieldFailure-8 2000000 790 ns/op 304 B/op 8 allocs/op | |
109 | BenchmarkStructSimpleCrossFieldFailureParallel-8 10000000 205 ns/op 304 B/op 8 allocs/op | |
110 | BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 611 ns/op 80 B/op 4 allocs/op | |
111 | BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 172 ns/op 80 B/op 4 allocs/op | |
112 | BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 1112 ns/op 320 B/op 9 allocs/op | |
113 | BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 258 ns/op 320 B/op 9 allocs/op | |
114 | BenchmarkStructSimpleSuccess-8 5000000 263 ns/op 0 B/op 0 allocs/op | |
115 | BenchmarkStructSimpleSuccessParallel-8 20000000 83.1 ns/op 0 B/op 0 allocs/op | |
116 | BenchmarkStructSimpleFailure-8 2000000 964 ns/op 424 B/op 9 allocs/op | |
117 | BenchmarkStructSimpleFailureParallel-8 10000000 212 ns/op 424 B/op 9 allocs/op | |
118 | BenchmarkStructComplexSuccess-8 1000000 1504 ns/op 128 B/op 8 allocs/op | |
119 | BenchmarkStructComplexSuccessParallel-8 3000000 427 ns/op 128 B/op 8 allocs/op | |
120 | BenchmarkStructComplexFailure-8 300000 7585 ns/op 3041 B/op 53 allocs/op | |
121 | BenchmarkStructComplexFailureParallel-8 1000000 1387 ns/op 3041 B/op 53 allocs/op | |
121 | 122 | ``` |
122 | 123 | |
123 | 124 | Complementary Software |
0 | 0 | package validator |
1 | 1 | |
2 | 2 | import ( |
3 | "context" | |
3 | 4 | "fmt" |
4 | 5 | "net" |
5 | 6 | "net/url" |
9 | 10 | "unicode/utf8" |
10 | 11 | ) |
11 | 12 | |
12 | // Func accepts all values needed for file and cross field validation | |
13 | // fl = FieldLevel validation helper | |
14 | // field = field value for validation | |
15 | // fieldType = fields | |
16 | // param = parameter used in validation i.e. gt=0 param would be 0 | |
13 | // Func accepts a FieldLevel interface for all validation needs | |
17 | 14 | type Func func(fl FieldLevel) bool |
15 | ||
16 | // FuncCtx accepts a context.Context and FieldLevel interface for all validation needs | |
17 | type FuncCtx func(ctx context.Context, fl FieldLevel) bool | |
18 | ||
19 | // wrapFunc wraps noramal Func makes it compatible with FuncCtx | |
20 | func wrapFunc(fn Func) FuncCtx { | |
21 | if fn == nil { | |
22 | return nil // be sure not to wrap a bad function. | |
23 | } | |
24 | return func(ctx context.Context, fl FieldLevel) bool { | |
25 | return fn(fl) | |
26 | } | |
27 | } | |
18 | 28 | |
19 | 29 | var ( |
20 | 30 | restrictedTags = map[string]struct{}{ |
70 | 70 | type cStruct struct { |
71 | 71 | name string |
72 | 72 | fields []*cField |
73 | fn StructLevelFunc | |
73 | fn StructLevelFuncCtx | |
74 | 74 | } |
75 | 75 | |
76 | 76 | type cField struct { |
89 | 89 | hasAlias bool |
90 | 90 | typeof tagType |
91 | 91 | hasTag bool |
92 | fn Func | |
92 | fn FuncCtx | |
93 | 93 | next *cTag |
94 | 94 | } |
95 | 95 |
0 | 0 | package validator |
1 | 1 | |
2 | import "reflect" | |
2 | import ( | |
3 | "context" | |
4 | "reflect" | |
5 | ) | |
3 | 6 | |
4 | 7 | // StructLevelFunc accepts all values needed for struct level validation |
5 | 8 | type StructLevelFunc func(sl StructLevel) |
9 | ||
10 | // StructLevelFuncCtx accepts all values needed for struct level validation | |
11 | // but also allows passing of contextual validation information vi context.Context. | |
12 | type StructLevelFuncCtx func(ctx context.Context, sl StructLevel) | |
13 | ||
14 | // wrapStructLevelFunc wraps noramal StructLevelFunc makes it compatible with StructLevelFuncCtx | |
15 | func wrapStructLevelFunc(fn StructLevelFunc) StructLevelFuncCtx { | |
16 | return func(ctx context.Context, sl StructLevel) { | |
17 | fn(sl) | |
18 | } | |
19 | } | |
6 | 20 | |
7 | 21 | // StructLevel contains all the information and helper functions |
8 | 22 | // to validate a struct |
20 | 34 | Parent() reflect.Value |
21 | 35 | |
22 | 36 | // returns the current struct. |
23 | // this is not needed when implementing 'Validatable' interface, | |
24 | // only when a StructLevel is registered | |
25 | 37 | Current() reflect.Value |
26 | 38 | |
27 | 39 | // ExtractType gets the actual underlying type of field value. |
0 | 0 | package validator |
1 | 1 | |
2 | 2 | import ( |
3 | "context" | |
3 | 4 | "fmt" |
4 | 5 | "reflect" |
5 | 6 | "strconv" |
33 | 34 | } |
34 | 35 | |
35 | 36 | // parent and current will be the same the first run of validateStruct |
36 | func (v *validate) validateStruct(parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) { | |
37 | func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) { | |
37 | 38 | |
38 | 39 | cs, ok := v.v.structCache.Get(typ) |
39 | 40 | if !ok { |
77 | 78 | } |
78 | 79 | } |
79 | 80 | |
80 | v.traverseField(parent, current.Field(f.idx), ns, structNs, f, f.cTags) | |
81 | v.traverseField(ctx, parent, current.Field(f.idx), ns, structNs, f, f.cTags) | |
81 | 82 | } |
82 | 83 | } |
83 | 84 | |
91 | 92 | v.ns = ns |
92 | 93 | v.actualNs = structNs |
93 | 94 | |
94 | cs.fn(v) | |
95 | cs.fn(ctx, v) | |
95 | 96 | } |
96 | 97 | } |
97 | 98 | |
98 | 99 | // 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(parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) { | |
100 | func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) { | |
100 | 101 | |
101 | 102 | var typ reflect.Type |
102 | 103 | var kind reflect.Kind |
191 | 192 | structNs = append(append(structNs, cf.name...), '.') |
192 | 193 | } |
193 | 194 | |
194 | v.validateStruct(current, current, typ, ns, structNs, ct) | |
195 | v.validateStruct(ctx, current, current, typ, ns, structNs, ct) | |
195 | 196 | return |
196 | 197 | } |
197 | 198 | } |
260 | 261 | reusableCF.altName = string(v.misc) |
261 | 262 | } |
262 | 263 | |
263 | v.traverseField(parent, current.Index(i), ns, structNs, reusableCF, ct) | |
264 | v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct) | |
264 | 265 | } |
265 | 266 | |
266 | 267 | case reflect.Map: |
290 | 291 | reusableCF.altName = string(v.misc) |
291 | 292 | } |
292 | 293 | |
293 | v.traverseField(parent, current.MapIndex(key), ns, structNs, reusableCF, ct) | |
294 | v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct) | |
294 | 295 | } |
295 | 296 | |
296 | 297 | default: |
313 | 314 | v.cf = cf |
314 | 315 | v.ct = ct |
315 | 316 | |
316 | if ct.fn(v) { | |
317 | if ct.fn(ctx, v) { | |
317 | 318 | |
318 | 319 | // drain rest of the 'or' values, then continue or leave |
319 | 320 | for { |
406 | 407 | // v.ns = ns |
407 | 408 | // v.actualNs = structNs |
408 | 409 | |
409 | if !ct.fn(v) { | |
410 | if !ct.fn(ctx, v) { | |
410 | 411 | |
411 | 412 | v.str1 = string(append(ns, cf.altName...)) |
412 | 413 |
0 | 0 | package validator |
1 | 1 | |
2 | 2 | import ( |
3 | "context" | |
3 | 4 | "errors" |
4 | 5 | "fmt" |
5 | 6 | "reflect" |
57 | 58 | hasCustomFuncs bool |
58 | 59 | hasTagNameFunc bool |
59 | 60 | tagNameFunc TagNameFunc |
60 | structLevelFuncs map[reflect.Type]StructLevelFunc | |
61 | structLevelFuncs map[reflect.Type]StructLevelFuncCtx | |
61 | 62 | customFuncs map[reflect.Type]CustomTypeFunc |
62 | 63 | aliases map[string]string |
63 | validations map[string]Func | |
64 | validations map[string]FuncCtx | |
64 | 65 | transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc |
65 | 66 | tagCache *tagCache |
66 | 67 | structCache *structCache |
78 | 79 | v := &Validate{ |
79 | 80 | tagName: defaultTagName, |
80 | 81 | aliases: make(map[string]string, len(bakedInAliases)), |
81 | validations: make(map[string]Func, len(bakedInValidators)), | |
82 | validations: make(map[string]FuncCtx, len(bakedInValidators)), | |
82 | 83 | tagCache: tc, |
83 | 84 | structCache: sc, |
84 | 85 | } |
91 | 92 | // must copy validators for separate validations to be used in each instance |
92 | 93 | for k, val := range bakedInValidators { |
93 | 94 | |
94 | // no need to error check here, baked in will alwaays be valid | |
95 | v.registerValidation(k, val, true) | |
95 | // no need to error check here, baked in will always be valid | |
96 | v.registerValidation(k, wrapFunc(val), true) | |
96 | 97 | } |
97 | 98 | |
98 | 99 | v.pool = &sync.Pool{ |
127 | 128 | // - if the key already exists, the previous validation function will be replaced. |
128 | 129 | // - this method is not thread-safe it is intended that these all be registered prior to any validation |
129 | 130 | func (v *Validate) RegisterValidation(tag string, fn Func) error { |
131 | return v.RegisterValidationCtx(tag, wrapFunc(fn)) | |
132 | } | |
133 | ||
134 | // RegisterValidationCtx does the same as RegisterValidation on accepts a FuncCtx validation | |
135 | // allowing context.Context validation support. | |
136 | func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx) error { | |
130 | 137 | return v.registerValidation(tag, fn, false) |
131 | 138 | } |
132 | 139 | |
133 | func (v *Validate) registerValidation(tag string, fn Func, bakedIn bool) error { | |
140 | func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool) error { | |
134 | 141 | |
135 | 142 | if len(tag) == 0 { |
136 | 143 | return errors.New("Function Key cannot be empty") |
176 | 183 | // a struct out of your control's validation to be overridden |
177 | 184 | // - this method is not thread-safe it is intended that these all be registered prior to any validation |
178 | 185 | func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) { |
186 | v.RegisterStructValidationCtx(wrapStructLevelFunc(fn), types...) | |
187 | } | |
188 | ||
189 | // RegisterStructValidationCtx registers a StructLevelFuncCtx against a number of types and allows passing | |
190 | // of contextual validation information via context.Context. | |
191 | // This is akin to implementing a 'Validatable' interface, but for structs for which | |
192 | // you may not have access or rights to change. | |
193 | // | |
194 | // NOTES: | |
195 | // - if this and the 'Validatable' interface are implemented the Struct Level takes precedence as to enable | |
196 | // a struct out of your control's validation to be overridden | |
197 | // - this method is not thread-safe it is intended that these all be registered prior to any validation | |
198 | func (v *Validate) RegisterStructValidationCtx(fn StructLevelFuncCtx, types ...interface{}) { | |
179 | 199 | |
180 | 200 | if v.structLevelFuncs == nil { |
181 | v.structLevelFuncs = make(map[reflect.Type]StructLevelFunc) | |
201 | v.structLevelFuncs = make(map[reflect.Type]StructLevelFuncCtx) | |
182 | 202 | } |
183 | 203 | |
184 | 204 | for _, t := range types { |
228 | 248 | // |
229 | 249 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. |
230 | 250 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. |
231 | func (v *Validate) Struct(s interface{}) (err error) { | |
251 | func (v *Validate) Struct(s interface{}) error { | |
252 | return v.StructCtx(context.Background(), s) | |
253 | } | |
254 | ||
255 | // StructCtx validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified | |
256 | // and also allows passing of context.Context for contextual validation information. | |
257 | // | |
258 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. | |
259 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. | |
260 | func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) { | |
232 | 261 | |
233 | 262 | val := reflect.ValueOf(s) |
234 | 263 | top := val |
247 | 276 | vd.isPartial = false |
248 | 277 | // vd.hasExcludes = false // only need to reset in StructPartial and StructExcept |
249 | 278 | |
250 | vd.validateStruct(top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) | |
279 | vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) | |
251 | 280 | |
252 | 281 | if len(vd.errs) > 0 { |
253 | 282 | err = vd.errs |
264 | 293 | // |
265 | 294 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. |
266 | 295 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. |
267 | func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) (err error) { | |
268 | ||
296 | func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) error { | |
297 | return v.StructFilteredCtx(context.Background(), s, fn) | |
298 | } | |
299 | ||
300 | // StructFilteredCtx validates a structs exposed fields, that pass the FilterFunc check and automatically validates | |
301 | // nested structs, unless otherwise specified and also allows passing of contextual validation information via | |
302 | // context.Context | |
303 | // | |
304 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. | |
305 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. | |
306 | func (v *Validate) StructFilteredCtx(ctx context.Context, s interface{}, fn FilterFunc) (err error) { | |
269 | 307 | val := reflect.ValueOf(s) |
270 | 308 | top := val |
271 | 309 | |
284 | 322 | vd.ffn = fn |
285 | 323 | // vd.hasExcludes = false // only need to reset in StructPartial and StructExcept |
286 | 324 | |
287 | vd.validateStruct(top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) | |
325 | vd.validateStruct(context.Background(), top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) | |
288 | 326 | |
289 | 327 | if len(vd.errs) > 0 { |
290 | 328 | err = vd.errs |
302 | 340 | // |
303 | 341 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. |
304 | 342 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. |
305 | func (v *Validate) StructPartial(s interface{}, fields ...string) (err error) { | |
306 | ||
343 | func (v *Validate) StructPartial(s interface{}, fields ...string) error { | |
344 | return v.StructPartialCtx(context.Background(), s, fields...) | |
345 | } | |
346 | ||
347 | // StructPartialCtx validates the fields passed in only, ignoring all others and allows passing of contextual | |
348 | // validation validation information via context.Context | |
349 | // Fields may be provided in a namespaced fashion relative to the struct provided | |
350 | // eg. NestedStruct.Field or NestedArrayField[0].Struct.Name | |
351 | // | |
352 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. | |
353 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. | |
354 | func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields ...string) (err error) { | |
307 | 355 | val := reflect.ValueOf(s) |
308 | 356 | top := val |
309 | 357 | |
363 | 411 | } |
364 | 412 | } |
365 | 413 | |
366 | vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) | |
414 | vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) | |
367 | 415 | |
368 | 416 | if len(vd.errs) > 0 { |
369 | 417 | err = vd.errs |
381 | 429 | // |
382 | 430 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. |
383 | 431 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. |
384 | func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) { | |
385 | ||
432 | func (v *Validate) StructExcept(s interface{}, fields ...string) error { | |
433 | return v.StructExceptCtx(context.Background(), s, fields...) | |
434 | } | |
435 | ||
436 | // StructExceptCtx validates all fields except the ones passed in and allows passing of contextual | |
437 | // validation validation information via context.Context | |
438 | // Fields may be provided in a namespaced fashion relative to the struct provided | |
439 | // i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name | |
440 | // | |
441 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. | |
442 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. | |
443 | func (v *Validate) StructExceptCtx(ctx context.Context, s interface{}, fields ...string) (err error) { | |
386 | 444 | val := reflect.ValueOf(s) |
387 | 445 | top := val |
388 | 446 | |
418 | 476 | vd.includeExclude[string(vd.misc)] = struct{}{} |
419 | 477 | } |
420 | 478 | |
421 | vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) | |
479 | vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) | |
422 | 480 | |
423 | 481 | if len(vd.errs) > 0 { |
424 | 482 | err = vd.errs |
442 | 500 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. |
443 | 501 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. |
444 | 502 | // validate Array, Slice and maps fields which may contain more than one error |
445 | func (v *Validate) Var(field interface{}, tag string) (err error) { | |
446 | ||
503 | func (v *Validate) Var(field interface{}, tag string) error { | |
504 | return v.VarCtx(context.Background(), field, tag) | |
505 | } | |
506 | ||
507 | // VarCtx validates a single variable using tag style validation and allows passing of contextual | |
508 | // validation validation information via context.Context. | |
509 | // eg. | |
510 | // var i int | |
511 | // validate.Var(i, "gt=1,lt=10") | |
512 | // | |
513 | // WARNING: a struct can be passed for validation eg. time.Time is a struct or if you have a custom type and have registered | |
514 | // a custom type handler, so must allow it; however unforseen validations will occur if trying to validate a struct | |
515 | // that is meant to be passed to 'validate.Struct' | |
516 | // | |
517 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. | |
518 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. | |
519 | // validate Array, Slice and maps fields which may contain more than one error | |
520 | func (v *Validate) VarCtx(ctx context.Context, field interface{}, tag string) (err error) { | |
447 | 521 | if len(tag) == 0 || tag == skipValidationTag { |
448 | 522 | return nil |
449 | 523 | } |
469 | 543 | vd.top = val |
470 | 544 | vd.isPartial = false |
471 | 545 | |
472 | vd.traverseField(val, val, vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) | |
546 | vd.traverseField(ctx, val, val, vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) | |
473 | 547 | |
474 | 548 | if len(vd.errs) > 0 { |
475 | 549 | err = vd.errs |
494 | 568 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. |
495 | 569 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. |
496 | 570 | // validate Array, Slice and maps fields which may contain more than one error |
497 | func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) (err error) { | |
498 | ||
571 | func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) error { | |
572 | return v.VarWithValueCtx(context.Background(), field, other, tag) | |
573 | } | |
574 | ||
575 | // VarWithValueCtx validates a single variable, against another variable/field's value using tag style validation and | |
576 | // allows passing of contextual validation validation information via context.Context. | |
577 | // eg. | |
578 | // s1 := "abcd" | |
579 | // s2 := "abcd" | |
580 | // validate.VarWithValue(s1, s2, "eqcsfield") // returns true | |
581 | // | |
582 | // WARNING: a struct can be passed for validation eg. time.Time is a struct or if you have a custom type and have registered | |
583 | // a custom type handler, so must allow it; however unforseen validations will occur if trying to validate a struct | |
584 | // that is meant to be passed to 'validate.Struct' | |
585 | // | |
586 | // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. | |
587 | // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. | |
588 | // validate Array, Slice and maps fields which may contain more than one error | |
589 | func (v *Validate) VarWithValueCtx(ctx context.Context, field interface{}, other interface{}, tag string) (err error) { | |
499 | 590 | if len(tag) == 0 || tag == skipValidationTag { |
500 | 591 | return nil |
501 | 592 | } |
521 | 612 | vd.top = otherVal |
522 | 613 | vd.isPartial = false |
523 | 614 | |
524 | vd.traverseField(otherVal, reflect.ValueOf(field), vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) | |
615 | vd.traverseField(ctx, otherVal, reflect.ValueOf(field), vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) | |
525 | 616 | |
526 | 617 | if len(vd.errs) > 0 { |
527 | 618 | err = vd.errs |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "context" | |
4 | 5 | "database/sql" |
5 | 6 | "database/sql/driver" |
6 | 7 | "encoding/json" |
775 | 776 | |
776 | 777 | // the following should all return no errors as everything is valid in |
777 | 778 | // the default state |
778 | errs := validate.StructPartial(tPartial, p1...) | |
779 | errs := validate.StructPartialCtx(context.Background(), tPartial, p1...) | |
779 | 780 | Equal(t, errs, nil) |
780 | 781 | |
781 | 782 | errs = validate.StructPartial(tPartial, p2...) |
785 | 786 | errs = validate.StructPartial(tPartial.SubSlice[0], p3...) |
786 | 787 | Equal(t, errs, nil) |
787 | 788 | |
788 | errs = validate.StructExcept(tPartial, p1...) | |
789 | errs = validate.StructExceptCtx(context.Background(), tPartial, p1...) | |
789 | 790 | Equal(t, errs, nil) |
790 | 791 | |
791 | 792 | errs = validate.StructExcept(tPartial, p2...) |
990 | 991 | AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "ltecsfield") |
991 | 992 | AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "ltecsfield") |
992 | 993 | |
993 | errs = validate.VarWithValue(1, "", "ltecsfield") | |
994 | errs = validate.VarWithValueCtx(context.Background(), 1, "", "ltecsfield") | |
994 | 995 | NotEqual(t, errs, nil) |
995 | 996 | AssertError(t, errs, "", "", "", "", "ltecsfield") |
996 | 997 | |
1826 | 1827 | AssertError(t, errs, "", "", "", "", "required") |
1827 | 1828 | |
1828 | 1829 | val.Name = "Valid Name" |
1829 | errs = validate.Var(val, "required") | |
1830 | errs = validate.VarCtx(context.Background(), val, "required") | |
1830 | 1831 | Equal(t, errs, nil) |
1831 | 1832 | |
1832 | 1833 | val.Name = "errorme" |
5126 | 5127 | return true |
5127 | 5128 | } |
5128 | 5129 | |
5130 | fnCtx := func(ctx context.Context, fl FieldLevel) bool { | |
5131 | return true | |
5132 | } | |
5133 | ||
5129 | 5134 | validate := New() |
5130 | 5135 | |
5131 | 5136 | errs := validate.RegisterValidation("new", fn) |
5138 | 5143 | NotEqual(t, errs, nil) |
5139 | 5144 | |
5140 | 5145 | errs = validate.RegisterValidation("new", fn) |
5146 | Equal(t, errs, nil) | |
5147 | ||
5148 | errs = validate.RegisterValidationCtx("new", fnCtx) | |
5141 | 5149 | Equal(t, errs, nil) |
5142 | 5150 | |
5143 | 5151 | PanicMatches(t, func() { validate.RegisterValidation("dive", fn) }, "Tag 'dive' either contains restricted characters or is the same as a restricted tag needed for normal operation") |
5237 | 5245 | errs = validate.Var(tm, "gt") |
5238 | 5246 | Equal(t, errs, nil) |
5239 | 5247 | |
5240 | t2 := time.Now().UTC() | |
5248 | t2 := time.Now().UTC().Add(-time.Hour) | |
5241 | 5249 | |
5242 | 5250 | errs = validate.Var(t2, "gt") |
5243 | 5251 | NotEqual(t, errs, nil) |
5275 | 5283 | errs := validate.Var(t1, "gte") |
5276 | 5284 | Equal(t, errs, nil) |
5277 | 5285 | |
5278 | t2 := time.Now().UTC() | |
5286 | t2 := time.Now().UTC().Add(-time.Hour) | |
5279 | 5287 | |
5280 | 5288 | errs = validate.Var(t2, "gte") |
5281 | 5289 | NotEqual(t, errs, nil) |
5322 | 5330 | i := true |
5323 | 5331 | PanicMatches(t, func() { validate.Var(i, "lt") }, "Bad field type bool") |
5324 | 5332 | |
5325 | t1 := time.Now().UTC() | |
5333 | t1 := time.Now().UTC().Add(-time.Hour) | |
5326 | 5334 | |
5327 | 5335 | errs = validate.Var(t1, "lt") |
5328 | 5336 | Equal(t, errs, nil) |
5361 | 5369 | i := true |
5362 | 5370 | PanicMatches(t, func() { validate.Var(i, "lte") }, "Bad field type bool") |
5363 | 5371 | |
5364 | t1 := time.Now().UTC() | |
5372 | t1 := time.Now().UTC().Add(-time.Hour) | |
5365 | 5373 | |
5366 | 5374 | errs := validate.Var(t1, "lte") |
5367 | 5375 | Equal(t, errs, nil) |
6694 | 6702 | |
6695 | 6703 | // the following should all return no errors as everything is valid in |
6696 | 6704 | // the default state |
6697 | errs := validate.StructFiltered(tPartial, p1) | |
6705 | errs := validate.StructFilteredCtx(context.Background(), tPartial, p1) | |
6698 | 6706 | Equal(t, errs, nil) |
6699 | 6707 | |
6700 | 6708 | errs = validate.StructFiltered(tPartial, p2) |
7078 | 7086 | Equal(t, res5, "json5") |
7079 | 7087 | Equal(t, alt5, "Map2") |
7080 | 7088 | } |
7089 | ||
7090 | func TestValidateStructRegisterCtx(t *testing.T) { | |
7091 | ||
7092 | var ctxVal string | |
7093 | ||
7094 | fnCtx := func(ctx context.Context, fl FieldLevel) bool { | |
7095 | ctxVal = ctx.Value(&ctxVal).(string) | |
7096 | return true | |
7097 | } | |
7098 | ||
7099 | var ctxSlVal string | |
7100 | slFn := func(ctx context.Context, sl StructLevel) { | |
7101 | ctxSlVal = ctx.Value(&ctxSlVal).(string) | |
7102 | } | |
7103 | ||
7104 | type Test struct { | |
7105 | Field string `validate:"val"` | |
7106 | } | |
7107 | ||
7108 | var tst Test | |
7109 | ||
7110 | validate := New() | |
7111 | validate.RegisterValidationCtx("val", fnCtx) | |
7112 | validate.RegisterStructValidationCtx(slFn, Test{}) | |
7113 | ||
7114 | ctx := context.WithValue(context.Background(), &ctxVal, "testval") | |
7115 | ctx = context.WithValue(ctx, &ctxSlVal, "slVal") | |
7116 | errs := validate.StructCtx(ctx, tst) | |
7117 | Equal(t, errs, nil) | |
7118 | Equal(t, ctxVal, "testval") | |
7119 | Equal(t, ctxSlVal, "slVal") | |
7120 | } |