Skip to content

Commit 885fd6c

Browse files
Dean KarnDean Karn
authored andcommitted
Merge pull request #203 from joeybloggs/v8-development
Merge Struct Level Validations
2 parents 5f64c22 + a48387a commit 885fd6c

File tree

5 files changed

+457
-38
lines changed

5 files changed

+457
-38
lines changed

README.md

Lines changed: 131 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -201,38 +201,143 @@ func ValidateValuer(field reflect.Value) interface{} {
201201
}
202202
```
203203

204+
Struct Level Validation
205+
```go
206+
package main
207+
208+
import (
209+
"fmt"
210+
"reflect"
211+
212+
"gopkg.in/go-playground/validator.v8"
213+
)
214+
215+
// User contains user information
216+
type User struct {
217+
FirstName string `json:"fname"`
218+
LastName string `json:"lname"`
219+
Age uint8 `validate:"gte=0,lte=130"`
220+
Email string `validate:"required,email"`
221+
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
222+
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
223+
}
224+
225+
// Address houses a users address information
226+
type Address struct {
227+
Street string `validate:"required"`
228+
City string `validate:"required"`
229+
Planet string `validate:"required"`
230+
Phone string `validate:"required"`
231+
}
232+
233+
var validate *validator.Validate
234+
235+
func main() {
236+
237+
config := &validator.Config{TagName: "validate"}
238+
239+
validate = validator.New(config)
240+
validate.RegisterStructValidation(UserStructLevelValidation, User{})
241+
242+
validateStruct()
243+
}
244+
245+
// UserStructLevelValidation contains custom struct level validations that don't always
246+
// make sense at the field validation level. For Example this function validates that either
247+
// FirstName or LastName exist; could have done that with a custom field validation but then
248+
// would have had to add it to both fields duplicating the logic + overhead, this way it's
249+
// only validated once.
250+
//
251+
// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
252+
// hooks right into validator and you can combine with validation tags and still have a
253+
// common error output format.
254+
func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) {
255+
256+
user := structLevel.CurrentStruct.Interface().(User)
257+
258+
if len(user.FirstName) == 0 && len(user.LastName) == 0 {
259+
structLevel.ReportError(reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname")
260+
structLevel.ReportError(reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname")
261+
}
262+
263+
// plus can to more, even with different tag than "fnameorlname"
264+
}
265+
266+
func validateStruct() {
267+
268+
address := &Address{
269+
Street: "Eavesdown Docks",
270+
Planet: "Persphone",
271+
Phone: "none",
272+
City: "Unknown",
273+
}
274+
275+
user := &User{
276+
FirstName: "",
277+
LastName: "",
278+
Age: 45,
279+
280+
FavouriteColor: "#000",
281+
Addresses: []*Address{address},
282+
}
283+
284+
// returns nil or ValidationErrors ( map[string]*FieldError )
285+
errs := validate.Struct(user)
286+
287+
if errs != nil {
288+
289+
fmt.Println(errs) // output: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag
290+
// Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag
291+
err := errs.(validator.ValidationErrors)["User.FirstName"]
292+
fmt.Println(err.Field) // output: FirstName
293+
fmt.Println(err.Tag) // output: fnameorlname
294+
fmt.Println(err.Kind) // output: string
295+
fmt.Println(err.Type) // output: string
296+
fmt.Println(err.Param) // output:
297+
fmt.Println(err.Value) // output:
298+
299+
// from here you can create your own error messages in whatever language you wish
300+
return
301+
}
302+
303+
// save user to database
304+
}
305+
```
306+
204307
Benchmarks
205308
------
206309
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go 1.5.1
207310
```go
208311
$ go test -cpu=4 -bench=. -benchmem=true
209312
PASS
210-
BenchmarkFieldSuccess-4 5000000 291 ns/op 16 B/op 1 allocs/op
211-
BenchmarkFieldFailure-4 5000000 294 ns/op 16 B/op 1 allocs/op
212-
BenchmarkFieldDiveSuccess-4 500000 3498 ns/op 528 B/op 28 allocs/op
213-
BenchmarkFieldDiveFailure-4 300000 4094 ns/op 928 B/op 32 allocs/op
214-
BenchmarkFieldCustomTypeSuccess-4 3000000 460 ns/op 32 B/op 2 allocs/op
215-
BenchmarkFieldCustomTypeFailure-4 2000000 758 ns/op 400 B/op 4 allocs/op
216-
BenchmarkFieldOrTagSuccess-4 1000000 1393 ns/op 32 B/op 2 allocs/op
217-
BenchmarkFieldOrTagFailure-4 1000000 1181 ns/op 432 B/op 6 allocs/op
218-
BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1218 ns/op 80 B/op 5 allocs/op
219-
BenchmarkStructSimpleCustomTypeFailure-4 1000000 1748 ns/op 624 B/op 11 allocs/op
220-
BenchmarkStructPartialSuccess-4 1000000 1392 ns/op 400 B/op 11 allocs/op
221-
BenchmarkStructPartialFailure-4 1000000 1938 ns/op 816 B/op 16 allocs/op
222-
BenchmarkStructExceptSuccess-4 2000000 903 ns/op 368 B/op 9 allocs/op
223-
BenchmarkStructExceptFailure-4 1000000 1381 ns/op 400 B/op 11 allocs/op
224-
BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1215 ns/op 128 B/op 6 allocs/op
225-
BenchmarkStructSimpleCrossFieldFailure-4 1000000 1781 ns/op 560 B/op 11 allocs/op
226-
BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1801 ns/op 160 B/op 8 allocs/op
227-
BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 2357 ns/op 592 B/op 13 allocs/op
228-
BenchmarkStructSimpleSuccess-4 1000000 1161 ns/op 48 B/op 3 allocs/op
229-
BenchmarkStructSimpleFailure-4 1000000 1818 ns/op 624 B/op 11 allocs/op
230-
BenchmarkStructSimpleSuccessParallel-4 5000000 375 ns/op 48 B/op 3 allocs/op
231-
BenchmarkStructSimpleFailureParallel-4 2000000 757 ns/op 624 B/op 11 allocs/op
232-
BenchmarkStructComplexSuccess-4 200000 8053 ns/op 432 B/op 27 allocs/op
233-
BenchmarkStructComplexFailure-4 100000 12634 ns/op 3335 B/op 69 allocs/op
234-
BenchmarkStructComplexSuccessParallel-4 1000000 2718 ns/op 432 B/op 27 allocs/op
235-
BenchmarkStructComplexFailureParallel-4 300000 5086 ns/op 3336 B/op 69 allocs/op
313+
BenchmarkFieldSuccess-4 5000000 305 ns/op 16 B/op 1 allocs/op
314+
BenchmarkFieldFailure-4 5000000 301 ns/op 16 B/op 1 allocs/op
315+
BenchmarkFieldDiveSuccess-4 500000 3544 ns/op 528 B/op 28 allocs/op
316+
BenchmarkFieldDiveFailure-4 300000 4120 ns/op 928 B/op 32 allocs/op
317+
BenchmarkFieldCustomTypeSuccess-4 3000000 465 ns/op 32 B/op 2 allocs/op
318+
BenchmarkFieldCustomTypeFailure-4 2000000 769 ns/op 400 B/op 4 allocs/op
319+
BenchmarkFieldOrTagSuccess-4 1000000 1372 ns/op 32 B/op 2 allocs/op
320+
BenchmarkFieldOrTagFailure-4 1000000 1218 ns/op 432 B/op 6 allocs/op
321+
BenchmarkStructLevelValidationSuccess-4 2000000 840 ns/op 160 B/op 6 allocs/op
322+
BenchmarkStructLevelValidationFailure-4 1000000 1443 ns/op 592 B/op 11 allocs/op
323+
BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1262 ns/op 80 B/op 5 allocs/op
324+
BenchmarkStructSimpleCustomTypeFailure-4 1000000 1812 ns/op 624 B/op 11 allocs/op
325+
BenchmarkStructPartialSuccess-4 1000000 1419 ns/op 400 B/op 11 allocs/op
326+
BenchmarkStructPartialFailure-4 1000000 1967 ns/op 816 B/op 16 allocs/op
327+
BenchmarkStructExceptSuccess-4 2000000 954 ns/op 368 B/op 9 allocs/op
328+
BenchmarkStructExceptFailure-4 1000000 1422 ns/op 400 B/op 11 allocs/op
329+
BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1286 ns/op 128 B/op 6 allocs/op
330+
BenchmarkStructSimpleCrossFieldFailure-4 1000000 1885 ns/op 560 B/op 11 allocs/op
331+
BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1948 ns/op 176 B/op 9 allocs/op
332+
BenchmarkStructSimpleCrossStructCrossFieldFailure-4 500000 2491 ns/op 608 B/op 14 allocs/op
333+
BenchmarkStructSimpleSuccess-4 1000000 1239 ns/op 48 B/op 3 allocs/op
334+
BenchmarkStructSimpleFailure-4 1000000 1891 ns/op 624 B/op 11 allocs/op
335+
BenchmarkStructSimpleSuccessParallel-4 5000000 386 ns/op 48 B/op 3 allocs/op
336+
BenchmarkStructSimpleFailureParallel-4 2000000 842 ns/op 624 B/op 11 allocs/op
337+
BenchmarkStructComplexSuccess-4 200000 8604 ns/op 512 B/op 30 allocs/op
338+
BenchmarkStructComplexFailure-4 100000 13332 ns/op 3416 B/op 72 allocs/op
339+
BenchmarkStructComplexSuccessParallel-4 1000000 2929 ns/op 512 B/op 30 allocs/op
340+
BenchmarkStructComplexFailureParallel-4 300000 5220 ns/op 3416 B/op 72 allocs/op
236341
```
237342

238343
How to Contribute

benchmarks_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,32 @@ func BenchmarkFieldOrTagFailure(b *testing.B) {
6767
}
6868
}
6969

70+
func BenchmarkStructLevelValidationSuccess(b *testing.B) {
71+
72+
validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{})
73+
74+
tst := &TestStruct{
75+
String: "good value",
76+
}
77+
78+
for n := 0; n < b.N; n++ {
79+
validate.Struct(tst)
80+
}
81+
}
82+
83+
func BenchmarkStructLevelValidationFailure(b *testing.B) {
84+
85+
validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{})
86+
87+
tst := &TestStruct{
88+
String: "good value",
89+
}
90+
91+
for n := 0; n < b.N; n++ {
92+
validate.Struct(tst)
93+
}
94+
}
95+
7096
func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
7197

7298
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
7+
"gopkg.in/go-playground/validator.v8"
8+
)
9+
10+
// User contains user information
11+
type User struct {
12+
FirstName string `json:"fname"`
13+
LastName string `json:"lname"`
14+
Age uint8 `validate:"gte=0,lte=130"`
15+
Email string `validate:"required,email"`
16+
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
17+
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
18+
}
19+
20+
// Address houses a users address information
21+
type Address struct {
22+
Street string `validate:"required"`
23+
City string `validate:"required"`
24+
Planet string `validate:"required"`
25+
Phone string `validate:"required"`
26+
}
27+
28+
var validate *validator.Validate
29+
30+
func main() {
31+
32+
config := &validator.Config{TagName: "validate"}
33+
34+
validate = validator.New(config)
35+
validate.RegisterStructValidation(UserStructLevelValidation, User{})
36+
37+
validateStruct()
38+
}
39+
40+
// UserStructLevelValidation contains custom struct level validations that don't always
41+
// make sense at the field validation level. For Example this function validates that either
42+
// FirstName or LastName exist; could have done that with a custom field validation but then
43+
// would have had to add it to both fields duplicating the logic + overhead, this way it's
44+
// only validated once.
45+
//
46+
// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
47+
// hooks right into validator and you can combine with validation tags and still have a
48+
// common error output format.
49+
func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) {
50+
51+
user := structLevel.CurrentStruct.Interface().(User)
52+
53+
if len(user.FirstName) == 0 && len(user.LastName) == 0 {
54+
structLevel.ReportError(reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname")
55+
structLevel.ReportError(reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname")
56+
}
57+
58+
// plus can to more, even with different tag than "fnameorlname"
59+
}
60+
61+
func validateStruct() {
62+
63+
address := &Address{
64+
Street: "Eavesdown Docks",
65+
Planet: "Persphone",
66+
Phone: "none",
67+
City: "Unknown",
68+
}
69+
70+
user := &User{
71+
FirstName: "",
72+
LastName: "",
73+
Age: 45,
74+
75+
FavouriteColor: "#000",
76+
Addresses: []*Address{address},
77+
}
78+
79+
// returns nil or ValidationErrors ( map[string]*FieldError )
80+
errs := validate.Struct(user)
81+
82+
if errs != nil {
83+
84+
fmt.Println(errs) // output: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag
85+
// Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag
86+
err := errs.(validator.ValidationErrors)["User.FirstName"]
87+
fmt.Println(err.Field) // output: FirstName
88+
fmt.Println(err.Tag) // output: fnameorlname
89+
fmt.Println(err.Kind) // output: string
90+
fmt.Println(err.Type) // output: string
91+
fmt.Println(err.Param) // output:
92+
fmt.Println(err.Value) // output:
93+
94+
// from here you can create your own error messages in whatever language you wish
95+
return
96+
}
97+
98+
// save user to database
99+
}

0 commit comments

Comments
 (0)