Merge pull request #141 from bluesuncorp/v6-development
Add RegisterCustomTypeFunc for easier adding of CustomTypeFunc
Dean Karn
8 years ago
1 | 1 | ================ |
2 | 2 | |
3 | 3 | [![Join the chat at https://gitter.im/bluesuncorp/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bluesuncorp/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) |
4 | [![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487374/badge.svg)](https://semaphoreci.com/joeybloggs/validator) | |
4 | [![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487383/badge.svg)](https://semaphoreci.com/joeybloggs/validator) | |
5 | 5 | [![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v6)](https://coveralls.io/r/bluesuncorp/validator?branch=v6) |
6 | 6 | [![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v6?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v6) |
7 | 7 | |
137 | 137 | package main |
138 | 138 | |
139 | 139 | import ( |
140 | "errors" | |
140 | "database/sql" | |
141 | "database/sql/driver" | |
141 | 142 | "fmt" |
142 | 143 | "reflect" |
143 | 144 | |
144 | sql "database/sql/driver" | |
145 | ||
146 | 145 | "gopkg.in/bluesuncorp/validator.v6" |
147 | 146 | ) |
148 | 147 | |
149 | var validate *validator.Validate | |
150 | ||
151 | type valuer struct { | |
152 | Name string | |
153 | } | |
154 | ||
155 | func (v valuer) Value() (sql.Value, error) { | |
156 | ||
157 | if v.Name == "errorme" { | |
158 | return nil, errors.New("some kind of error") | |
159 | } | |
160 | ||
161 | if v.Name == "blankme" { | |
162 | return "", nil | |
163 | } | |
164 | ||
165 | if len(v.Name) == 0 { | |
166 | return nil, nil | |
167 | } | |
168 | ||
169 | return v.Name, nil | |
170 | } | |
171 | ||
172 | // ValidateValuerType implements validator.CustomTypeFunc | |
173 | func ValidateValuerType(field reflect.Value) interface{} { | |
174 | if valuer, ok := field.Interface().(sql.Valuer); ok { | |
175 | val, err := valuer.Value() | |
176 | if err != nil { | |
177 | // handle the error how you want | |
178 | return nil | |
179 | } | |
180 | ||
181 | return val | |
182 | } | |
183 | ||
184 | return nil | |
148 | // DbBackedUser User struct | |
149 | type DbBackedUser struct { | |
150 | Name sql.NullString `validate:"required"` | |
151 | Age sql.NullInt64 `validate:"required"` | |
185 | 152 | } |
186 | 153 | |
187 | 154 | func main() { |
188 | ||
189 | customTypes := map[reflect.Type]validator.CustomTypeFunc{} | |
190 | customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType | |
191 | customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType | |
192 | 155 | |
193 | 156 | config := validator.Config{ |
194 | 157 | TagName: "validate", |
195 | 158 | ValidationFuncs: validator.BakedInValidators, |
196 | CustomTypeFuncs: customTypes, | |
197 | } | |
198 | ||
199 | validate = validator.New(config) | |
200 | ||
201 | validateCustomFieldType() | |
202 | } | |
203 | ||
204 | func validateCustomFieldType() { | |
205 | val := valuer{ | |
206 | Name: "blankme", | |
207 | } | |
208 | ||
209 | errs := validate.Field(val, "required") | |
210 | if errs != nil { | |
211 | fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag | |
212 | return | |
213 | } | |
214 | ||
215 | // all ok | |
216 | } | |
217 | ||
159 | } | |
160 | ||
161 | validate := validator.New(config) | |
162 | ||
163 | // register all sql.Null* types to use the ValidateValuer CustomTypeFunc | |
164 | validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) | |
165 | ||
166 | x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} | |
167 | errs := validate.Struct(x) | |
168 | ||
169 | if len(errs) > 0 { | |
170 | fmt.Printf("Errs:\n%+v\n", errs) | |
171 | } | |
172 | } | |
173 | ||
174 | // ValidateValuer implements validator.CustomTypeFunc | |
175 | func ValidateValuer(field reflect.Value) interface{} { | |
176 | if valuer, ok := field.Interface().(driver.Valuer); ok { | |
177 | val, err := valuer.Value() | |
178 | if err == nil { | |
179 | return val | |
180 | } | |
181 | // handle the error how you want | |
182 | } | |
183 | return nil | |
184 | } | |
218 | 185 | ``` |
219 | 186 | |
220 | 187 | Benchmarks |
0 | package main | |
1 | ||
2 | import ( | |
3 | "database/sql" | |
4 | "database/sql/driver" | |
5 | "fmt" | |
6 | "reflect" | |
7 | ||
8 | "gopkg.in/bluesuncorp/validator.v6" | |
9 | ) | |
10 | ||
11 | // DbBackedUser User struct | |
12 | type DbBackedUser struct { | |
13 | Name sql.NullString `validate:"required"` | |
14 | Age sql.NullInt64 `validate:"required"` | |
15 | } | |
16 | ||
17 | func main() { | |
18 | ||
19 | config := validator.Config{ | |
20 | TagName: "validate", | |
21 | ValidationFuncs: validator.BakedInValidators, | |
22 | } | |
23 | ||
24 | validate := validator.New(config) | |
25 | ||
26 | // register all sql.Null* types to use the ValidateValuer CustomTypeFunc | |
27 | validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) | |
28 | ||
29 | x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} | |
30 | errs := validate.Struct(x) | |
31 | ||
32 | if len(errs) > 0 { | |
33 | fmt.Printf("Errs:\n%+v\n", errs) | |
34 | } | |
35 | } | |
36 | ||
37 | // ValidateValuer implements validator.CustomTypeFunc | |
38 | func ValidateValuer(field reflect.Value) interface{} { | |
39 | if valuer, ok := field.Interface().(driver.Valuer); ok { | |
40 | val, err := valuer.Value() | |
41 | if err == nil { | |
42 | return val | |
43 | } | |
44 | // handle the error how you want | |
45 | } | |
46 | return nil | |
47 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "fmt" | |
5 | "reflect" | |
6 | ||
7 | sql "database/sql/driver" | |
8 | ||
9 | "gopkg.in/bluesuncorp/validator.v6" | |
10 | ) | |
11 | ||
12 | // User contains user information | |
13 | type User struct { | |
14 | FirstName string `validate:"required"` | |
15 | LastName string `validate:"required"` | |
16 | Age uint8 `validate:"gte=0,lte=130"` | |
17 | Email string `validate:"required,email"` | |
18 | FavouriteColor string `validate:"hexcolor|rgb|rgba"` | |
19 | Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... | |
20 | } | |
21 | ||
22 | // Address houses a users address information | |
23 | type Address struct { | |
24 | Street string `validate:"required"` | |
25 | City string `validate:"required"` | |
26 | Planet string `validate:"required"` | |
27 | Phone string `validate:"required"` | |
28 | } | |
29 | ||
30 | var validate *validator.Validate | |
31 | ||
32 | func main() { | |
33 | ||
34 | config := validator.Config{ | |
35 | TagName: "validate", | |
36 | ValidationFuncs: validator.BakedInValidators, | |
37 | } | |
38 | ||
39 | validate = validator.New(config) | |
40 | ||
41 | validateStruct() | |
42 | validateField() | |
43 | } | |
44 | ||
45 | func validateStruct() { | |
46 | ||
47 | address := &Address{ | |
48 | Street: "Eavesdown Docks", | |
49 | Planet: "Persphone", | |
50 | Phone: "none", | |
51 | } | |
52 | ||
53 | user := &User{ | |
54 | FirstName: "Badger", | |
55 | LastName: "Smith", | |
56 | Age: 135, | |
57 | Email: "Badger.Smith@gmail.com", | |
58 | FavouriteColor: "#000", | |
59 | Addresses: []*Address{address}, | |
60 | } | |
61 | ||
62 | // returns nil or ValidationErrors ( map[string]*FieldError ) | |
63 | errs := validate.Struct(user) | |
64 | ||
65 | if errs != nil { | |
66 | ||
67 | fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag | |
68 | // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag | |
69 | err := errs["User.Addresses[0].City"] | |
70 | fmt.Println(err.Field) // output: City | |
71 | fmt.Println(err.Tag) // output: required | |
72 | fmt.Println(err.Kind) // output: string | |
73 | fmt.Println(err.Type) // output: string | |
74 | fmt.Println(err.Param) // output: | |
75 | fmt.Println(err.Value) // output: | |
76 | ||
77 | // from here you can create your own error messages in whatever language you wish | |
78 | return | |
79 | } | |
80 | ||
81 | // save user to database | |
82 | } | |
83 | ||
84 | func validateField() { | |
85 | myEmail := "joeybloggs.gmail.com" | |
86 | ||
87 | errs := validate.Field(myEmail, "required,email") | |
88 | ||
89 | if errs != nil { | |
90 | fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag | |
91 | return | |
92 | } | |
93 | ||
94 | // email ok, move on | |
95 | } | |
96 | ||
97 | var validate2 *validator.Validate | |
98 | ||
99 | type valuer struct { | |
100 | Name string | |
101 | } | |
102 | ||
103 | func (v valuer) Value() (sql.Value, error) { | |
104 | ||
105 | if v.Name == "errorme" { | |
106 | return nil, errors.New("some kind of error") | |
107 | } | |
108 | ||
109 | if v.Name == "blankme" { | |
110 | return "", nil | |
111 | } | |
112 | ||
113 | if len(v.Name) == 0 { | |
114 | return nil, nil | |
115 | } | |
116 | ||
117 | return v.Name, nil | |
118 | } | |
119 | ||
120 | // ValidateValuerType implements validator.CustomTypeFunc | |
121 | func ValidateValuerType(field reflect.Value) interface{} { | |
122 | if valuer, ok := field.Interface().(sql.Valuer); ok { | |
123 | val, err := valuer.Value() | |
124 | if err != nil { | |
125 | // handle the error how you want | |
126 | return nil | |
127 | } | |
128 | ||
129 | return val | |
130 | } | |
131 | ||
132 | return nil | |
133 | } | |
134 | ||
135 | func main2() { | |
136 | ||
137 | customTypes := map[reflect.Type]validator.CustomTypeFunc{} | |
138 | customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType | |
139 | customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType | |
140 | ||
141 | config := validator.Config{ | |
142 | TagName: "validate", | |
143 | ValidationFuncs: validator.BakedInValidators, | |
144 | CustomTypeFuncs: customTypes, | |
145 | } | |
146 | ||
147 | validate2 = validator.New(config) | |
148 | ||
149 | validateCustomFieldType() | |
150 | } | |
151 | ||
152 | func validateCustomFieldType() { | |
153 | val := valuer{ | |
154 | Name: "blankme", | |
155 | } | |
156 | ||
157 | errs := validate2.Field(val, "required") | |
158 | if errs != nil { | |
159 | fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag | |
160 | return | |
161 | } | |
162 | ||
163 | // all ok | |
164 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "fmt" | |
5 | "reflect" | |
6 | ||
7 | sql "database/sql/driver" | |
8 | ||
9 | "gopkg.in/bluesuncorp/validator.v6" | |
10 | ) | |
11 | ||
12 | // User contains user information | |
13 | type User struct { | |
14 | FirstName string `validate:"required"` | |
15 | LastName string `validate:"required"` | |
16 | Age uint8 `validate:"gte=0,lte=130"` | |
17 | Email string `validate:"required,email"` | |
18 | FavouriteColor string `validate:"hexcolor|rgb|rgba"` | |
19 | Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... | |
20 | } | |
21 | ||
22 | // Address houses a users address information | |
23 | type Address struct { | |
24 | Street string `validate:"required"` | |
25 | City string `validate:"required"` | |
26 | Planet string `validate:"required"` | |
27 | Phone string `validate:"required"` | |
28 | } | |
29 | ||
30 | var validate *validator.Validate | |
31 | ||
32 | func main() { | |
33 | ||
34 | config := validator.Config{ | |
35 | TagName: "validate", | |
36 | ValidationFuncs: validator.BakedInValidators, | |
37 | } | |
38 | ||
39 | validate = validator.New(config) | |
40 | ||
41 | validateStruct() | |
42 | validateField() | |
43 | } | |
44 | ||
45 | func validateStruct() { | |
46 | ||
47 | address := &Address{ | |
48 | Street: "Eavesdown Docks", | |
49 | Planet: "Persphone", | |
50 | Phone: "none", | |
51 | } | |
52 | ||
53 | user := &User{ | |
54 | FirstName: "Badger", | |
55 | LastName: "Smith", | |
56 | Age: 135, | |
57 | Email: "Badger.Smith@gmail.com", | |
58 | FavouriteColor: "#000", | |
59 | Addresses: []*Address{address}, | |
60 | } | |
61 | ||
62 | // returns nil or ValidationErrors ( map[string]*FieldError ) | |
63 | errs := validate.Struct(user) | |
64 | ||
65 | if errs != nil { | |
66 | ||
67 | fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag | |
68 | // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag | |
69 | err := errs["User.Addresses[0].City"] | |
70 | fmt.Println(err.Field) // output: City | |
71 | fmt.Println(err.Tag) // output: required | |
72 | fmt.Println(err.Kind) // output: string | |
73 | fmt.Println(err.Type) // output: string | |
74 | fmt.Println(err.Param) // output: | |
75 | fmt.Println(err.Value) // output: | |
76 | ||
77 | // from here you can create your own error messages in whatever language you wish | |
78 | return | |
79 | } | |
80 | ||
81 | // save user to database | |
82 | } | |
83 | ||
84 | func validateField() { | |
85 | myEmail := "joeybloggs.gmail.com" | |
86 | ||
87 | errs := validate.Field(myEmail, "required,email") | |
88 | ||
89 | if errs != nil { | |
90 | fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag | |
91 | return | |
92 | } | |
93 | ||
94 | // email ok, move on | |
95 | } | |
96 | ||
97 | var validate2 *validator.Validate | |
98 | ||
99 | type valuer struct { | |
100 | Name string | |
101 | } | |
102 | ||
103 | func (v valuer) Value() (sql.Value, error) { | |
104 | ||
105 | if v.Name == "errorme" { | |
106 | return nil, errors.New("some kind of error") | |
107 | } | |
108 | ||
109 | if v.Name == "blankme" { | |
110 | return "", nil | |
111 | } | |
112 | ||
113 | if len(v.Name) == 0 { | |
114 | return nil, nil | |
115 | } | |
116 | ||
117 | return v.Name, nil | |
118 | } | |
119 | ||
120 | // ValidateValuerType implements validator.CustomTypeFunc | |
121 | func ValidateValuerType(field reflect.Value) interface{} { | |
122 | if valuer, ok := field.Interface().(sql.Valuer); ok { | |
123 | val, err := valuer.Value() | |
124 | if err != nil { | |
125 | // handle the error how you want | |
126 | return nil | |
127 | } | |
128 | ||
129 | return val | |
130 | } | |
131 | ||
132 | return nil | |
133 | } | |
134 | ||
135 | func main2() { | |
136 | ||
137 | customTypes := map[reflect.Type]validator.CustomTypeFunc{} | |
138 | customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType | |
139 | customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType | |
140 | ||
141 | config := validator.Config{ | |
142 | TagName: "validate", | |
143 | ValidationFuncs: validator.BakedInValidators, | |
144 | CustomTypeFuncs: customTypes, | |
145 | } | |
146 | ||
147 | validate2 = validator.New(config) | |
148 | ||
149 | validateCustomFieldType() | |
150 | } | |
151 | ||
152 | func validateCustomFieldType() { | |
153 | val := valuer{ | |
154 | Name: "blankme", | |
155 | } | |
156 | ||
157 | errs := validate2.Field(val, "required") | |
158 | if errs != nil { | |
159 | fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag | |
160 | return | |
161 | } | |
162 | ||
163 | // all ok | |
164 | } |
156 | 156 | return nil |
157 | 157 | } |
158 | 158 | |
159 | // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types | |
160 | func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { | |
161 | ||
162 | if v.config.CustomTypeFuncs == nil { | |
163 | v.config.CustomTypeFuncs = map[reflect.Type]CustomTypeFunc{} | |
164 | } | |
165 | ||
166 | for _, t := range types { | |
167 | v.config.CustomTypeFuncs[reflect.TypeOf(t)] = fn | |
168 | } | |
169 | ||
170 | v.config.hasCustomFuncs = true | |
171 | } | |
172 | ||
159 | 173 | // Field validates a single field using tag style validation and returns ValidationErrors |
160 | 174 | // NOTE: it returns ValidationErrors instead of a single FieldError because this can also |
161 | 175 | // validate Array, Slice and maps fields which may contain more than one error |
0 | 0 | package validator |
1 | 1 | |
2 | 2 | import ( |
3 | "database/sql" | |
4 | "database/sql/driver" | |
3 | 5 | "errors" |
4 | 6 | "fmt" |
5 | 7 | "reflect" |
6 | 8 | "testing" |
7 | 9 | "time" |
8 | ||
9 | sql "database/sql/driver" | |
10 | 10 | |
11 | 11 | . "gopkg.in/bluesuncorp/assert.v1" |
12 | 12 | ) |
125 | 125 | Name string |
126 | 126 | } |
127 | 127 | |
128 | func (v valuer) Value() (sql.Value, error) { | |
128 | func (v valuer) Value() (driver.Value, error) { | |
129 | 129 | |
130 | 130 | if v.Name == "errorme" { |
131 | 131 | return nil, errors.New("some kind of error") |
177 | 177 | } |
178 | 178 | |
179 | 179 | func ValidateValuerType(field reflect.Value) interface{} { |
180 | if valuer, ok := field.Interface().(sql.Valuer); ok { | |
180 | if valuer, ok := field.Interface().(driver.Valuer); ok { | |
181 | 181 | val, err := valuer.Value() |
182 | 182 | if err != nil { |
183 | 183 | // handle the error how you want |
190 | 190 | return nil |
191 | 191 | } |
192 | 192 | |
193 | func TestSQLValue2Validation(t *testing.T) { | |
194 | ||
195 | config := Config{ | |
196 | TagName: "validate", | |
197 | ValidationFuncs: BakedInValidators, | |
198 | } | |
199 | ||
200 | validate := New(config) | |
201 | validate.RegisterCustomTypeFunc(ValidateValuerType, valuer{}, (*driver.Valuer)(nil), sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) | |
202 | validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) | |
203 | validate.RegisterCustomTypeFunc(OverrideIntTypeForSomeReason, 1) | |
204 | ||
205 | val := valuer{ | |
206 | Name: "", | |
207 | } | |
208 | ||
209 | errs := validate.Field(val, "required") | |
210 | NotEqual(t, errs, nil) | |
211 | AssertError(t, errs, "", "", "required") | |
212 | ||
213 | val.Name = "Valid Name" | |
214 | errs = validate.Field(val, "required") | |
215 | Equal(t, errs, nil) | |
216 | ||
217 | val.Name = "errorme" | |
218 | ||
219 | PanicMatches(t, func() { errs = validate.Field(val, "required") }, "SQL Driver Valuer error: some kind of error") | |
220 | ||
221 | type myValuer valuer | |
222 | ||
223 | myVal := valuer{ | |
224 | Name: "", | |
225 | } | |
226 | ||
227 | errs = validate.Field(myVal, "required") | |
228 | NotEqual(t, errs, nil) | |
229 | AssertError(t, errs, "", "", "required") | |
230 | ||
231 | cust := MadeUpCustomType{ | |
232 | FirstName: "Joey", | |
233 | LastName: "Bloggs", | |
234 | } | |
235 | ||
236 | c := CustomMadeUpStruct{MadeUp: cust, OverriddenInt: 2} | |
237 | ||
238 | errs = validate.Struct(c) | |
239 | Equal(t, errs, nil) | |
240 | ||
241 | c.MadeUp.FirstName = "" | |
242 | c.OverriddenInt = 1 | |
243 | ||
244 | errs = validate.Struct(c) | |
245 | NotEqual(t, errs, nil) | |
246 | Equal(t, len(errs), 2) | |
247 | AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required") | |
248 | AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt") | |
249 | } | |
250 | ||
193 | 251 | func TestSQLValueValidation(t *testing.T) { |
194 | 252 | |
195 | 253 | customTypes := map[reflect.Type]CustomTypeFunc{} |
196 | customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType | |
254 | customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType | |
197 | 255 | customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType |
198 | 256 | customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType |
199 | 257 | customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason |