Codebase list golang-github-go-playground-validator-v10 / v9.9.0
add map key validation support (#324) Dean Karn authored 6 years ago GitHub committed 6 years ago
9 changed file(s) with 558 addition(s) and 67 deletion(s). Raw diff Collapse all Expand all
00 Package validator
11 ================
22 <img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
3 ![Project status](https://img.shields.io/badge/version-9.8.0-green.svg)
3 ![Project status](https://img.shields.io/badge/version-9.9.0-green.svg)
44 [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
55 [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9)
66 [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
1212 It has the following **unique** features:
1313
1414 - Cross Field and Cross Struct validations by using validation tags or custom validators.
15 - Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
15 - Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
16 - Ability to dive into both map keys and values for validation
1617 - Handles type interface by determining it's underlying type prior to validation.
1718 - Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
1819 - Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs
6465
6566 Benchmarks
6667 ------
67 ###### Run on MacBook Pro (15-inch, 2017) Go version go1.9.1 linux/amd64
68 ###### Run on MacBook Pro (15-inch, 2017) Go version go1.9.2 darwin/amd64
6869 ```go
6970 go test -bench=. -benchmem=true
70 BenchmarkFieldSuccess-8 20000000 87.2 ns/op 0 B/op 0 allocs/op
71 BenchmarkFieldSuccessParallel-8 50000000 26.1 ns/op 0 B/op 0 allocs/op
72 BenchmarkFieldFailure-8 5000000 299 ns/op 208 B/op 4 allocs/op
73 BenchmarkFieldFailureParallel-8 20000000 100 ns/op 208 B/op 4 allocs/op
74 BenchmarkFieldDiveSuccess-8 2000000 645 ns/op 201 B/op 11 allocs/op
75 BenchmarkFieldDiveSuccessParallel-8 10000000 198 ns/op 201 B/op 11 allocs/op
76 BenchmarkFieldDiveFailure-8 2000000 876 ns/op 412 B/op 16 allocs/op
77 BenchmarkFieldDiveFailureParallel-8 5000000 268 ns/op 413 B/op 16 allocs/op
78 BenchmarkFieldCustomTypeSuccess-8 10000000 228 ns/op 32 B/op 2 allocs/op
79 BenchmarkFieldCustomTypeSuccessParallel-8 20000000 70.0 ns/op 32 B/op 2 allocs/op
80 BenchmarkFieldCustomTypeFailure-8 5000000 286 ns/op 208 B/op 4 allocs/op
81 BenchmarkFieldCustomTypeFailureParallel-8 20000000 95.6 ns/op 208 B/op 4 allocs/op
82 BenchmarkFieldOrTagSuccess-8 2000000 857 ns/op 16 B/op 1 allocs/op
83 BenchmarkFieldOrTagSuccessParallel-8 3000000 397 ns/op 16 B/op 1 allocs/op
84 BenchmarkFieldOrTagFailure-8 3000000 495 ns/op 224 B/op 5 allocs/op
85 BenchmarkFieldOrTagFailureParallel-8 5000000 376 ns/op 224 B/op 5 allocs/op
86 BenchmarkStructLevelValidationSuccess-8 10000000 226 ns/op 32 B/op 2 allocs/op
87 BenchmarkStructLevelValidationSuccessParallel-8 20000000 68.4 ns/op 32 B/op 2 allocs/op
88 BenchmarkStructLevelValidationFailure-8 3000000 497 ns/op 304 B/op 8 allocs/op
89 BenchmarkStructLevelValidationFailureParallel-8 10000000 170 ns/op 304 B/op 8 allocs/op
90 BenchmarkStructSimpleCustomTypeSuccess-8 3000000 420 ns/op 32 B/op 2 allocs/op
91 BenchmarkStructSimpleCustomTypeSuccessParallel-8 20000000 124 ns/op 32 B/op 2 allocs/op
92 BenchmarkStructSimpleCustomTypeFailure-8 2000000 681 ns/op 424 B/op 9 allocs/op
93 BenchmarkStructSimpleCustomTypeFailureParallel-8 10000000 244 ns/op 440 B/op 10 allocs/op
94 BenchmarkStructFilteredSuccess-8 2000000 659 ns/op 288 B/op 9 allocs/op
95 BenchmarkStructFilteredSuccessParallel-8 10000000 211 ns/op 288 B/op 9 allocs/op
96 BenchmarkStructFilteredFailure-8 3000000 482 ns/op 256 B/op 7 allocs/op
97 BenchmarkStructFilteredFailureParallel-8 10000000 162 ns/op 256 B/op 7 allocs/op
98 BenchmarkStructPartialSuccess-8 3000000 564 ns/op 256 B/op 6 allocs/op
99 BenchmarkStructPartialSuccessParallel-8 10000000 180 ns/op 256 B/op 6 allocs/op
100 BenchmarkStructPartialFailure-8 2000000 779 ns/op 480 B/op 11 allocs/op
101 BenchmarkStructPartialFailureParallel-8 5000000 268 ns/op 480 B/op 11 allocs/op
102 BenchmarkStructExceptSuccess-8 2000000 879 ns/op 496 B/op 12 allocs/op
71 BenchmarkFieldSuccess-8 20000000 79.9 ns/op 0 B/op 0 allocs/op
72 BenchmarkFieldSuccessParallel-8 50000000 25.0 ns/op 0 B/op 0 allocs/op
73 BenchmarkFieldFailure-8 5000000 281 ns/op 208 B/op 4 allocs/op
74 BenchmarkFieldFailureParallel-8 20000000 97.0 ns/op 208 B/op 4 allocs/op
75 BenchmarkFieldArrayDiveSuccess-8 3000000 591 ns/op 201 B/op 11 allocs/op
76 BenchmarkFieldArrayDiveSuccessParallel-8 10000000 195 ns/op 201 B/op 11 allocs/op
77 BenchmarkFieldArrayDiveFailure-8 2000000 878 ns/op 412 B/op 16 allocs/op
78 BenchmarkFieldArrayDiveFailureParallel-8 5000000 274 ns/op 413 B/op 16 allocs/op
79 BenchmarkFieldMapDiveSuccess-8 1000000 1279 ns/op 432 B/op 18 allocs/op
80 BenchmarkFieldMapDiveSuccessParallel-8 5000000 401 ns/op 432 B/op 18 allocs/op
81 BenchmarkFieldMapDiveFailure-8 1000000 1060 ns/op 512 B/op 16 allocs/op
82 BenchmarkFieldMapDiveFailureParallel-8 5000000 334 ns/op 512 B/op 16 allocs/op
83 BenchmarkFieldMapDiveWithKeysSuccess-8 1000000 1462 ns/op 480 B/op 21 allocs/op
84 BenchmarkFieldMapDiveWithKeysSuccessParallel-8 3000000 463 ns/op 480 B/op 21 allocs/op
85 BenchmarkFieldMapDiveWithKeysFailure-8 1000000 1414 ns/op 721 B/op 21 allocs/op
86 BenchmarkFieldMapDiveWithKeysFailureParallel-8 3000000 446 ns/op 721 B/op 21 allocs/op
87 BenchmarkFieldCustomTypeSuccess-8 10000000 211 ns/op 32 B/op 2 allocs/op
88 BenchmarkFieldCustomTypeSuccessParallel-8 20000000 65.9 ns/op 32 B/op 2 allocs/op
89 BenchmarkFieldCustomTypeFailure-8 5000000 270 ns/op 208 B/op 4 allocs/op
90 BenchmarkFieldCustomTypeFailureParallel-8 20000000 93.3 ns/op 208 B/op 4 allocs/op
91 BenchmarkFieldOrTagSuccess-8 2000000 729 ns/op 16 B/op 1 allocs/op
92 BenchmarkFieldOrTagSuccessParallel-8 5000000 367 ns/op 16 B/op 1 allocs/op
93 BenchmarkFieldOrTagFailure-8 3000000 472 ns/op 224 B/op 5 allocs/op
94 BenchmarkFieldOrTagFailureParallel-8 5000000 373 ns/op 224 B/op 5 allocs/op
95 BenchmarkStructLevelValidationSuccess-8 10000000 201 ns/op 32 B/op 2 allocs/op
96 BenchmarkStructLevelValidationSuccessParallel-8 20000000 66.3 ns/op 32 B/op 2 allocs/op
97 BenchmarkStructLevelValidationFailure-8 3000000 468 ns/op 304 B/op 8 allocs/op
98 BenchmarkStructLevelValidationFailureParallel-8 10000000 172 ns/op 304 B/op 8 allocs/op
99 BenchmarkStructSimpleCustomTypeSuccess-8 5000000 376 ns/op 32 B/op 2 allocs/op
100 BenchmarkStructSimpleCustomTypeSuccessParallel-8 20000000 126 ns/op 32 B/op 2 allocs/op
101 BenchmarkStructSimpleCustomTypeFailure-8 2000000 646 ns/op 424 B/op 9 allocs/op
102 BenchmarkStructSimpleCustomTypeFailureParallel-8 10000000 240 ns/op 440 B/op 10 allocs/op
103 BenchmarkStructFilteredSuccess-8 3000000 582 ns/op 288 B/op 9 allocs/op
104 BenchmarkStructFilteredSuccessParallel-8 10000000 198 ns/op 288 B/op 9 allocs/op
105 BenchmarkStructFilteredFailure-8 3000000 447 ns/op 256 B/op 7 allocs/op
106 BenchmarkStructFilteredFailureParallel-8 10000000 156 ns/op 256 B/op 7 allocs/op
107 BenchmarkStructPartialSuccess-8 3000000 536 ns/op 256 B/op 6 allocs/op
108 BenchmarkStructPartialSuccessParallel-8 10000000 175 ns/op 256 B/op 6 allocs/op
109 BenchmarkStructPartialFailure-8 2000000 738 ns/op 480 B/op 11 allocs/op
110 BenchmarkStructPartialFailureParallel-8 5000000 256 ns/op 480 B/op 11 allocs/op
111 BenchmarkStructExceptSuccess-8 2000000 835 ns/op 496 B/op 12 allocs/op
103112 BenchmarkStructExceptSuccessParallel-8 10000000 163 ns/op 240 B/op 5 allocs/op
104 BenchmarkStructExceptFailure-8 2000000 734 ns/op 464 B/op 10 allocs/op
105 BenchmarkStructExceptFailureParallel-8 5000000 259 ns/op 464 B/op 10 allocs/op
106 BenchmarkStructSimpleCrossFieldSuccess-8 3000000 432 ns/op 72 B/op 3 allocs/op
107 BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 129 ns/op 72 B/op 3 allocs/op
108 BenchmarkStructSimpleCrossFieldFailure-8 2000000 671 ns/op 304 B/op 8 allocs/op
109 BenchmarkStructSimpleCrossFieldFailureParallel-8 10000000 229 ns/op 304 B/op 8 allocs/op
110 BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 628 ns/op 80 B/op 4 allocs/op
111 BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 182 ns/op 80 B/op 4 allocs/op
112 BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 872 ns/op 320 B/op 9 allocs/op
113 BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 267 ns/op 320 B/op 9 allocs/op
114 BenchmarkStructSimpleSuccess-8 5000000 274 ns/op 0 B/op 0 allocs/op
115 BenchmarkStructSimpleSuccessParallel-8 20000000 79.0 ns/op 0 B/op 0 allocs/op
116 BenchmarkStructSimpleFailure-8 2000000 647 ns/op 424 B/op 9 allocs/op
117 BenchmarkStructSimpleFailureParallel-8 10000000 224 ns/op 424 B/op 9 allocs/op
118 BenchmarkStructComplexSuccess-8 1000000 1557 ns/op 128 B/op 8 allocs/op
119 BenchmarkStructComplexSuccessParallel-8 3000000 473 ns/op 128 B/op 8 allocs/op
120 BenchmarkStructComplexFailure-8 300000 4373 ns/op 3041 B/op 53 allocs/op
121 BenchmarkStructComplexFailureParallel-8 1000000 1554 ns/op 3041 B/op 53 allocs/op
113 BenchmarkStructExceptFailure-8 2000000 682 ns/op 464 B/op 10 allocs/op
114 BenchmarkStructExceptFailureParallel-8 10000000 244 ns/op 464 B/op 10 allocs/op
115 BenchmarkStructSimpleCrossFieldSuccess-8 5000000 392 ns/op 72 B/op 3 allocs/op
116 BenchmarkStructSimpleCrossFieldSuccessParallel-8 20000000 126 ns/op 72 B/op 3 allocs/op
117 BenchmarkStructSimpleCrossFieldFailure-8 2000000 611 ns/op 304 B/op 8 allocs/op
118 BenchmarkStructSimpleCrossFieldFailureParallel-8 10000000 214 ns/op 304 B/op 8 allocs/op
119 BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 3000000 567 ns/op 80 B/op 4 allocs/op
120 BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 177 ns/op 80 B/op 4 allocs/op
121 BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 807 ns/op 320 B/op 9 allocs/op
122 BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 268 ns/op 320 B/op 9 allocs/op
123 BenchmarkStructSimpleSuccess-8 5000000 256 ns/op 0 B/op 0 allocs/op
124 BenchmarkStructSimpleSuccessParallel-8 20000000 76.3 ns/op 0 B/op 0 allocs/op
125 BenchmarkStructSimpleFailure-8 2000000 625 ns/op 424 B/op 9 allocs/op
126 BenchmarkStructSimpleFailureParallel-8 10000000 219 ns/op 424 B/op 9 allocs/op
127 BenchmarkStructComplexSuccess-8 1000000 1431 ns/op 128 B/op 8 allocs/op
128 BenchmarkStructComplexSuccessParallel-8 3000000 427 ns/op 128 B/op 8 allocs/op
129 BenchmarkStructComplexFailure-8 300000 4065 ns/op 3041 B/op 53 allocs/op
130 BenchmarkStructComplexFailureParallel-8 1000000 1478 ns/op 3041 B/op 53 allocs/op
122131 ```
123132
124133 Complementary Software
0 package main
1
2 import (
3 "fmt"
4
5 "gopkg.in/go-playground/validator.v9"
6 )
7
8 // Test ...
9 type Test struct {
10 Array []string `validate:"required,gt=0,dive,required"`
11 Map map[string]string `validate:"required,gt=0,dive,keys,keymax,endkeys,required,max=1000"`
12 }
13
14 // use a single instance of Validate, it caches struct info
15 var validate *validator.Validate
16
17 func main() {
18
19 validate = validator.New()
20
21 // registering alias so we can see the differences between
22 // map key, value validation errors
23 validate.RegisterAlias("keymax", "max=10")
24
25 var test Test
26
27 val(test)
28
29 test.Array = []string{""}
30 test.Map = map[string]string{"test > than 10": ""}
31 val(test)
32 }
33
34 func val(test Test) {
35 fmt.Println("testing")
36 err := validate.Struct(test)
37 fmt.Println(err)
38 }
3131 var (
3232 restrictedTags = map[string]struct{}{
3333 diveTag: {},
34 keysTag: {},
35 endKeysTag: {},
3436 structOnlyTag: {},
3537 omitempty: {},
3638 skipValidationTag: {},
5858 })
5959 }
6060
61 func BenchmarkFieldDiveSuccess(b *testing.B) {
61 func BenchmarkFieldArrayDiveSuccess(b *testing.B) {
6262
6363 validate := New()
6464
7171 }
7272 }
7373
74 func BenchmarkFieldDiveSuccessParallel(b *testing.B) {
74 func BenchmarkFieldArrayDiveSuccessParallel(b *testing.B) {
7575
7676 validate := New()
7777
8585 })
8686 }
8787
88 func BenchmarkFieldDiveFailure(b *testing.B) {
88 func BenchmarkFieldArrayDiveFailure(b *testing.B) {
8989
9090 validate := New()
9191
9797 }
9898 }
9999
100 func BenchmarkFieldDiveFailureParallel(b *testing.B) {
100 func BenchmarkFieldArrayDiveFailureParallel(b *testing.B) {
101101
102102 validate := New()
103103
107107 b.RunParallel(func(pb *testing.PB) {
108108 for pb.Next() {
109109 validate.Var(m, "required,dive,required")
110 }
111 })
112 }
113
114 func BenchmarkFieldMapDiveSuccess(b *testing.B) {
115
116 validate := New()
117
118 m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"}
119
120 b.ResetTimer()
121
122 for n := 0; n < b.N; n++ {
123 validate.Var(m, "required,dive,required")
124 }
125 }
126
127 func BenchmarkFieldMapDiveSuccessParallel(b *testing.B) {
128
129 validate := New()
130
131 m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"}
132
133 b.ResetTimer()
134 b.RunParallel(func(pb *testing.PB) {
135 for pb.Next() {
136 validate.Var(m, "required,dive,required")
137 }
138 })
139 }
140
141 func BenchmarkFieldMapDiveFailure(b *testing.B) {
142
143 validate := New()
144
145 m := map[string]string{"": "", "val3": "val3"}
146
147 b.ResetTimer()
148 for n := 0; n < b.N; n++ {
149 validate.Var(m, "required,dive,required")
150 }
151 }
152
153 func BenchmarkFieldMapDiveFailureParallel(b *testing.B) {
154
155 validate := New()
156
157 m := map[string]string{"": "", "val3": "val3"}
158
159 b.ResetTimer()
160 b.RunParallel(func(pb *testing.PB) {
161 for pb.Next() {
162 validate.Var(m, "required,dive,required")
163 }
164 })
165 }
166
167 func BenchmarkFieldMapDiveWithKeysSuccess(b *testing.B) {
168
169 validate := New()
170
171 m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"}
172
173 b.ResetTimer()
174
175 for n := 0; n < b.N; n++ {
176 validate.Var(m, "required,dive,keys,required,endkeys,required")
177 }
178 }
179
180 func BenchmarkFieldMapDiveWithKeysSuccessParallel(b *testing.B) {
181
182 validate := New()
183
184 m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"}
185
186 b.ResetTimer()
187 b.RunParallel(func(pb *testing.PB) {
188 for pb.Next() {
189 validate.Var(m, "required,dive,keys,required,endkeys,required")
190 }
191 })
192 }
193
194 func BenchmarkFieldMapDiveWithKeysFailure(b *testing.B) {
195
196 validate := New()
197
198 m := map[string]string{"": "", "val3": "val3"}
199
200 b.ResetTimer()
201 for n := 0; n < b.N; n++ {
202 validate.Var(m, "required,dive,keys,required,endkeys,required")
203 }
204 }
205
206 func BenchmarkFieldMapDiveWithKeysFailureParallel(b *testing.B) {
207
208 validate := New()
209
210 m := map[string]string{"": "", "val3": "val3"}
211
212 b.ResetTimer()
213 b.RunParallel(func(pb *testing.PB) {
214 for pb.Next() {
215 validate.Var(m, "required,dive,keys,required,endkeys,required")
110216 }
111217 })
112218 }
1717 typeStructOnly
1818 typeDive
1919 typeOr
20 typeKeys
21 typeEndKeys
2022 )
2123
2224 const (
2325 invalidValidation = "Invalid validation tag on field '%s'"
2426 undefinedValidation = "Undefined validation function '%s' on field '%s'"
27 keysTagNotDefined = "'" + endKeysTag + "' tag encountered without a corresponding '" + keysTag + "' tag"
2528 )
2629
2730 type structCache struct {
8790 aliasTag string
8891 actualAliasTag string
8992 param string
93 typeof tagType
94 keys *cTag // only populated when using tag's 'keys' and 'endkeys' for map key validation
95 next *cTag
96 hasTag bool
9097 hasAlias bool
91 typeof tagType
92 hasTag bool
9398 fn FuncCtx
94 next *cTag
9599 }
96100
97101 func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
184188
185189 // check map for alias and process new tags, otherwise process as usual
186190 if tagsVal, found := v.aliases[t]; found {
187
188191 if i == 0 {
189192 firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
190193 } else {
196199 continue
197200 }
198201
202 var prevTag tagType
203
199204 if i == 0 {
200205 current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
201206 firstCtag = current
202207 } else {
208 prevTag = current.typeof
203209 current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
204210 current = current.next
205211 }
209215 case diveTag:
210216 current.typeof = typeDive
211217 continue
218
219 case keysTag:
220 current.typeof = typeKeys
221
222 if i == 0 || prevTag != typeDive {
223 panic(fmt.Sprintf("'%s' tag must be immediately preceeded by the '%s' tag", keysTag, diveTag))
224 }
225
226 current.typeof = typeKeys
227
228 // need to pass along only keys tag
229 // need to increment i to skip over the keys tags
230 b := make([]byte, 0, 64)
231
232 i++
233
234 for ; i < len(tags); i++ {
235
236 b = append(b, tags[i]...)
237 b = append(b, ',')
238
239 if tags[i] == endKeysTag {
240 break
241 }
242 }
243
244 current.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), fieldName, "", false)
245 continue
246
247 case endKeysTag:
248 current.typeof = typeEndKeys
249
250 // if there are more in tags then there was no keysTag defined
251 // and an error should be thrown
252 if i != len(tags)-1 {
253 panic(keysTagNotDefined)
254 }
255 return
212256
213257 case omitempty:
214258 current.typeof = typeOmitEmpty
192192 This tells the validator to dive into a slice, array or map and validate that
193193 level of the slice, array or map with the validation tags that follow.
194194 Multidimensional nesting is also supported, each level you wish to dive will
195 require another dive tag.
195 require another dive tag. dive has some sub-tags, 'keys' & 'endkeys', please see
196 the Keys & EndKeys section just below.
196197
197198 Usage: dive
198199
209210 // gt=0 will be applied to []
210211 // []string will be spared validation
211212 // required will be applied to string
213
214 Keys & EndKeys
215
216 These are to be used together directly after the dive tag and tells the validator
217 that anything between 'keys' and 'endkeys' applies to the keys of a map and not the
218 values; think of it like the 'dive' tag, but for map keys instead of values.
219 Multidimensional nesting is also supported, each level you wish to validate will
220 require another 'keys' and 'endkeys' tag. These tags are only valid for maps.
221
222 Usage: dive,keys,othertagvalidation(s),endkeys,valuevalidationtags
223
224 Example #1
225
226 map[string]string with validation tag "gt=0,dive,keys,eg=1|eq=2,endkeys,required"
227 // gt=0 will be applied to the map itself
228 // eg=1|eq=2 will be applied to the map keys
229 // required will be applied to map values
230
231 Example #2
232
233 map[[2]string]string with validation tag "gt=0,dive,keys,dive,eq=1|eq=2,endkeys,required"
234 // gt=0 will be applied to the map itself
235 // eg=1|eq=2 will be applied to each array element in the the map keys
236 // required will be applied to map values
212237
213238 Required
214239
259259 ct = ct.next
260260 continue
261261
262 case typeEndKeys:
263 return
264
262265 case typeDive:
263266
264267 ct = ct.next
293296
294297 reusableCF.altName = string(v.misc)
295298 }
296
297299 v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct)
298300 }
299301
324326 reusableCF.altName = string(v.misc)
325327 }
326328
327 v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct)
329 if ct != nil && ct.typeof == typeKeys && ct.keys != nil {
330 v.traverseField(ctx, parent, key, ns, structNs, reusableCF, ct.keys)
331 // can be nil when just keys being validated
332 if ct.next != nil {
333 v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct.next)
334 }
335 } else {
336 v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct)
337 }
328338 }
329339
330340 default:
2424 isdefault = "isdefault"
2525 skipValidationTag = "-"
2626 diveTag = "dive"
27 keysTag = "keys"
28 endKeysTag = "endkeys"
2729 requiredTag = "required"
2830 namespaceSeparator = "."
2931 leftBracket = "["
1111 "testing"
1212 "time"
1313
14 . "gopkg.in/go-playground/assert.v1"
15
1614 "github.com/go-playground/locales/en"
1715 "github.com/go-playground/locales/fr"
1816 "github.com/go-playground/locales/nl"
1917 ut "github.com/go-playground/universal-translator"
18 . "gopkg.in/go-playground/assert.v1"
2019 )
2120
2221 // NOTES:
125124 EqualSkip(t, 2, fe.Field(), field)
126125 EqualSkip(t, 2, fe.StructField(), structField)
127126 EqualSkip(t, 2, fe.Tag(), expectedTag)
127 }
128
129 func AssertDeepError(t *testing.T, err error, nsKey, structNsKey, field, structField, expectedTag, actualTag string) {
130 errs := err.(ValidationErrors)
131
132 found := false
133 var fe FieldError
134
135 for i := 0; i < len(errs); i++ {
136 if errs[i].Namespace() == nsKey && errs[i].StructNamespace() == structNsKey && errs[i].Tag() == expectedTag && errs[i].ActualTag() == actualTag {
137 found = true
138 fe = errs[i]
139 break
140 }
141 }
142
143 EqualSkip(t, 2, found, true)
144 NotEqualSkip(t, 2, fe, nil)
145 EqualSkip(t, 2, fe.Field(), field)
146 EqualSkip(t, 2, fe.StructField(), structField)
128147 }
129148
130149 func getError(err error, nsKey, structNsKey string) FieldError {
73127331 }
73137332 PanicMatches(t, func() { validate.Var(1.0, "unique") }, "Bad field type float64")
73147333 }
7334
7335 func TestKeys(t *testing.T) {
7336
7337 type Test struct {
7338 Test1 map[string]string `validate:"gt=0,dive,keys,eq=testkey,endkeys,eq=testval" json:"test1"`
7339 Test2 map[int]int `validate:"gt=0,dive,keys,eq=3,endkeys,eq=4" json:"test2"`
7340 Test3 map[int]int `validate:"gt=0,dive,keys,eq=3,endkeys" json:"test3"`
7341 }
7342
7343 var tst Test
7344
7345 validate := New()
7346 err := validate.Struct(tst)
7347 NotEqual(t, err, nil)
7348 Equal(t, len(err.(ValidationErrors)), 3)
7349 AssertError(t, err.(ValidationErrors), "Test.Test1", "Test.Test1", "Test1", "Test1", "gt")
7350 AssertError(t, err.(ValidationErrors), "Test.Test2", "Test.Test2", "Test2", "Test2", "gt")
7351 AssertError(t, err.(ValidationErrors), "Test.Test3", "Test.Test3", "Test3", "Test3", "gt")
7352
7353 tst.Test1 = map[string]string{
7354 "testkey": "testval",
7355 }
7356
7357 tst.Test2 = map[int]int{
7358 3: 4,
7359 }
7360
7361 tst.Test3 = map[int]int{
7362 3: 4,
7363 }
7364
7365 err = validate.Struct(tst)
7366 Equal(t, err, nil)
7367
7368 tst.Test1["badtestkey"] = "badtestvalue"
7369 tst.Test2[10] = 11
7370
7371 err = validate.Struct(tst)
7372 NotEqual(t, err, nil)
7373
7374 errs := err.(ValidationErrors)
7375
7376 Equal(t, len(errs), 4)
7377
7378 AssertDeepError(t, errs, "Test.Test1[badtestkey]", "Test.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq", "eq")
7379 AssertDeepError(t, errs, "Test.Test1[badtestkey]", "Test.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq", "eq")
7380 AssertDeepError(t, errs, "Test.Test2[10]", "Test.Test2[10]", "Test2[10]", "Test2[10]", "eq", "eq")
7381 AssertDeepError(t, errs, "Test.Test2[10]", "Test.Test2[10]", "Test2[10]", "Test2[10]", "eq", "eq")
7382
7383 type Test2 struct {
7384 NestedKeys map[[1]string]string `validate:"gt=0,dive,keys,dive,eq=innertestkey,endkeys,eq=outertestval"`
7385 }
7386
7387 var tst2 Test2
7388
7389 err = validate.Struct(tst2)
7390 NotEqual(t, err, nil)
7391 Equal(t, len(err.(ValidationErrors)), 1)
7392 AssertError(t, err.(ValidationErrors), "Test2.NestedKeys", "Test2.NestedKeys", "NestedKeys", "NestedKeys", "gt")
7393
7394 tst2.NestedKeys = map[[1]string]string{
7395 [1]string{"innertestkey"}: "outertestval",
7396 }
7397
7398 err = validate.Struct(tst2)
7399 Equal(t, err, nil)
7400
7401 tst2.NestedKeys[[1]string{"badtestkey"}] = "badtestvalue"
7402
7403 err = validate.Struct(tst2)
7404 NotEqual(t, err, nil)
7405
7406 errs = err.(ValidationErrors)
7407
7408 Equal(t, len(errs), 2)
7409 AssertDeepError(t, errs, "Test2.NestedKeys[[badtestkey]][0]", "Test2.NestedKeys[[badtestkey]][0]", "NestedKeys[[badtestkey]][0]", "NestedKeys[[badtestkey]][0]", "eq", "eq")
7410 AssertDeepError(t, errs, "Test2.NestedKeys[[badtestkey]]", "Test2.NestedKeys[[badtestkey]]", "NestedKeys[[badtestkey]]", "NestedKeys[[badtestkey]]", "eq", "eq")
7411
7412 // test bad tag definitions
7413
7414 PanicMatches(t, func() { validate.Var(map[string]string{"key": "val"}, "endkeys,dive,eq=val") }, "'endkeys' tag encountered without a corresponding 'keys' tag")
7415 PanicMatches(t, func() { validate.Var(1, "keys,eq=1,endkeys") }, "'keys' tag must be immediately preceeded by the 'dive' tag")
7416
7417 // test custom tag name
7418 validate = New()
7419 validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
7420 name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
7421
7422 if name == "-" {
7423 return ""
7424 }
7425 return name
7426 })
7427
7428 err = validate.Struct(tst)
7429 NotEqual(t, err, nil)
7430
7431 errs = err.(ValidationErrors)
7432
7433 Equal(t, len(errs), 4)
7434
7435 AssertDeepError(t, errs, "Test.test1[badtestkey]", "Test.Test1[badtestkey]", "test1[badtestkey]", "Test1[badtestkey]", "eq", "eq")
7436 AssertDeepError(t, errs, "Test.test1[badtestkey]", "Test.Test1[badtestkey]", "test1[badtestkey]", "Test1[badtestkey]", "eq", "eq")
7437 AssertDeepError(t, errs, "Test.test2[10]", "Test.Test2[10]", "test2[10]", "Test2[10]", "eq", "eq")
7438 AssertDeepError(t, errs, "Test.test2[10]", "Test.Test2[10]", "test2[10]", "Test2[10]", "eq", "eq")
7439 }
7440
7441 // Thanks @adrian-sgn specific test for your specific scenario
7442 func TestKeysCustomValidation(t *testing.T) {
7443
7444 type LangCode string
7445 type Label map[LangCode]string
7446
7447 type TestMapStructPtr struct {
7448 Label Label `validate:"dive,keys,lang_code,endkeys,required"`
7449 }
7450
7451 validate := New()
7452 validate.RegisterValidation("lang_code", func(fl FieldLevel) bool {
7453 validLangCodes := map[LangCode]struct{}{
7454 "en": {},
7455 "es": {},
7456 "pt": {},
7457 }
7458
7459 _, ok := validLangCodes[fl.Field().Interface().(LangCode)]
7460 return ok
7461 })
7462
7463 label := Label{
7464 "en": "Good morning!",
7465 "pt": "",
7466 "es": "¡Buenos días!",
7467 "xx": "Bad key",
7468 "xxx": "",
7469 }
7470
7471 err := validate.Struct(TestMapStructPtr{label})
7472 NotEqual(t, err, nil)
7473
7474 errs := err.(ValidationErrors)
7475 Equal(t, len(errs), 4)
7476
7477 AssertDeepError(t, errs, "TestMapStructPtr.Label[xx]", "TestMapStructPtr.Label[xx]", "Label[xx]", "Label[xx]", "lang_code", "lang_code")
7478 AssertDeepError(t, errs, "TestMapStructPtr.Label[pt]", "TestMapStructPtr.Label[pt]", "Label[pt]", "Label[pt]", "required", "required")
7479 AssertDeepError(t, errs, "TestMapStructPtr.Label[xxx]", "TestMapStructPtr.Label[xxx]", "Label[xxx]", "Label[xxx]", "lang_code", "lang_code")
7480 AssertDeepError(t, errs, "TestMapStructPtr.Label[xxx]", "TestMapStructPtr.Label[xxx]", "Label[xxx]", "Label[xxx]", "required", "required")
7481
7482 // find specific error
7483
7484 var e FieldError
7485 for _, e = range errs {
7486 if e.Namespace() == "TestMapStructPtr.Label[xxx]" {
7487 break
7488 }
7489 }
7490
7491 Equal(t, e.Param(), "")
7492 Equal(t, e.Value().(LangCode), LangCode("xxx"))
7493
7494 for _, e = range errs {
7495 if e.Namespace() == "TestMapStructPtr.Label[xxx]" && e.Tag() == "required" {
7496 break
7497 }
7498 }
7499
7500 Equal(t, e.Param(), "")
7501 Equal(t, e.Value().(string), "")
7502 }
7503
7504 func TestKeyOrs(t *testing.T) {
7505
7506 type Test struct {
7507 Test1 map[string]string `validate:"gt=0,dive,keys,eq=testkey|eq=testkeyok,endkeys,eq=testval" json:"test1"`
7508 }
7509
7510 var tst Test
7511
7512 validate := New()
7513 err := validate.Struct(tst)
7514 NotEqual(t, err, nil)
7515 Equal(t, len(err.(ValidationErrors)), 1)
7516 AssertError(t, err.(ValidationErrors), "Test.Test1", "Test.Test1", "Test1", "Test1", "gt")
7517
7518 tst.Test1 = map[string]string{
7519 "testkey": "testval",
7520 }
7521
7522 err = validate.Struct(tst)
7523 Equal(t, err, nil)
7524
7525 tst.Test1["badtestkey"] = "badtestval"
7526
7527 err = validate.Struct(tst)
7528 NotEqual(t, err, nil)
7529
7530 errs := err.(ValidationErrors)
7531
7532 Equal(t, len(errs), 2)
7533
7534 AssertDeepError(t, errs, "Test.Test1[badtestkey]", "Test.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq=testkey|eq=testkeyok", "eq=testkey|eq=testkeyok")
7535 AssertDeepError(t, errs, "Test.Test1[badtestkey]", "Test.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq", "eq")
7536
7537 validate.RegisterAlias("okkey", "eq=testkey|eq=testkeyok")
7538
7539 type Test2 struct {
7540 Test1 map[string]string `validate:"gt=0,dive,keys,okkey,endkeys,eq=testval" json:"test1"`
7541 }
7542
7543 var tst2 Test2
7544
7545 err = validate.Struct(tst2)
7546 NotEqual(t, err, nil)
7547 Equal(t, len(err.(ValidationErrors)), 1)
7548 AssertError(t, err.(ValidationErrors), "Test2.Test1", "Test2.Test1", "Test1", "Test1", "gt")
7549
7550 tst2.Test1 = map[string]string{
7551 "testkey": "testval",
7552 }
7553
7554 err = validate.Struct(tst2)
7555 Equal(t, err, nil)
7556
7557 tst2.Test1["badtestkey"] = "badtestval"
7558
7559 err = validate.Struct(tst2)
7560 NotEqual(t, err, nil)
7561
7562 errs = err.(ValidationErrors)
7563
7564 Equal(t, len(errs), 2)
7565
7566 AssertDeepError(t, errs, "Test2.Test1[badtestkey]", "Test2.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "okkey", "eq=testkey|eq=testkeyok")
7567 AssertDeepError(t, errs, "Test2.Test1[badtestkey]", "Test2.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq", "eq")
7568 }