Skip to content

Commit d802346

Browse files
Dean KarnDean Karn
authored andcommitted
Merge pull request #136 from bluesuncorp/v6-development
V6 development
2 parents 55e8c8d + a05046f commit d802346

File tree

5 files changed

+389
-18
lines changed

5 files changed

+389
-18
lines changed

README.md

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ It has the following **unique** features:
1212

1313
- Cross Field and Cross Struct validations.
1414
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
15-
- Handles type interface by determining it's underlying type prior to validation.
15+
- Handles type interface by determining it's underlying type prior to validation.
16+
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
1617

1718
Installation
1819
------------
@@ -35,6 +36,8 @@ Usage and documentation
3536
Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs.
3637

3738
##### Examples:
39+
40+
Struct & Field validation
3841
```go
3942
package main
4043

@@ -130,6 +133,76 @@ func validateField() {
130133
}
131134
```
132135

136+
Custom Field Type
137+
```go
138+
package main
139+
140+
import (
141+
"errors"
142+
"fmt"
143+
"reflect"
144+
145+
sql "database/sql/driver"
146+
147+
"gopkg.in/bluesuncorp/validator.v6"
148+
)
149+
150+
var validate *validator.Validate
151+
152+
type valuer struct {
153+
Name string
154+
}
155+
156+
func (v valuer) Value() (sql.Value, error) {
157+
158+
if v.Name == "errorme" {
159+
return nil, errors.New("some kind of error")
160+
}
161+
162+
if v.Name == "blankme" {
163+
return "", nil
164+
}
165+
166+
if len(v.Name) == 0 {
167+
return nil, nil
168+
}
169+
170+
return v.Name, nil
171+
}
172+
173+
func main() {
174+
175+
customTypes := map[reflect.Type]validator.CustomTypeFunc{}
176+
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
177+
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
178+
179+
config := validator.Config{
180+
TagName: "validate",
181+
ValidationFuncs: validator.BakedInValidators,
182+
CustomTypeFuncs: customTypes,
183+
}
184+
185+
validate = validator.New(config)
186+
187+
validateCustomFieldType()
188+
}
189+
190+
func validateCustomFieldType() {
191+
val := valuer{
192+
Name: "blankme",
193+
}
194+
195+
errs := validate.Field(val, "required")
196+
if errs != nil {
197+
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag
198+
return
199+
}
200+
201+
// all ok
202+
}
203+
204+
```
205+
133206
Benchmarks
134207
------
135208
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3
@@ -139,18 +212,22 @@ hurt parallel performance too much.
139212
```go
140213
$ go test -cpu=4 -bench=. -benchmem=true
141214
PASS
142-
BenchmarkFieldSuccess-4 5000000 326 ns/op 16 B/op 1 allocs/op
143-
BenchmarkFieldFailure-4 5000000 327 ns/op 16 B/op 1 allocs/op
144-
BenchmarkFieldOrTagSuccess-4 500000 2738 ns/op 20 B/op 2 allocs/op
145-
BenchmarkFieldOrTagFailure-4 1000000 1341 ns/op 384 B/op 6 allocs/op
146-
BenchmarkStructSimpleSuccess-4 1000000 1282 ns/op 24 B/op 3 allocs/op
147-
BenchmarkStructSimpleFailure-4 1000000 1870 ns/op 529 B/op 11 allocs/op
148-
BenchmarkStructSimpleSuccessParallel-4 5000000 348 ns/op 24 B/op 3 allocs/op
149-
BenchmarkStructSimpleFailureParallel-4 2000000 807 ns/op 529 B/op 11 allocs/op
150-
BenchmarkStructComplexSuccess-4 200000 8081 ns/op 368 B/op 30 allocs/op
151-
BenchmarkStructComplexFailure-4 100000 12418 ns/op 2861 B/op 72 allocs/op
152-
BenchmarkStructComplexSuccessParallel-4 500000 2249 ns/op 369 B/op 30 allocs/op
153-
BenchmarkStructComplexFailureParallel-4 300000 5183 ns/op 2863 B/op 72 allocs/op
215+
BenchmarkFieldSuccess-4 5000000 318 ns/op 16 B/op 1 allocs/op
216+
BenchmarkFieldFailure-4 5000000 316 ns/op 16 B/op 1 allocs/op
217+
BenchmarkFieldCustomTypeSuccess-4 3000000 492 ns/op 32 B/op 2 allocs/op
218+
BenchmarkFieldCustomTypeFailure-4 2000000 843 ns/op 416 B/op 6 allocs/op
219+
BenchmarkFieldOrTagSuccess-4 500000 2384 ns/op 20 B/op 2 allocs/op
220+
BenchmarkFieldOrTagFailure-4 1000000 1295 ns/op 384 B/op 6 allocs/op
221+
BenchmarkStructSimpleSuccess-4 1000000 1175 ns/op 24 B/op 3 allocs/op
222+
BenchmarkStructSimpleFailure-4 1000000 1822 ns/op 529 B/op 11 allocs/op
223+
BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1302 ns/op 56 B/op 5 allocs/op
224+
BenchmarkStructSimpleCustomTypeFailure-4 1000000 1847 ns/op 577 B/op 13 allocs/op
225+
BenchmarkStructSimpleSuccessParallel-4 5000000 339 ns/op 24 B/op 3 allocs/op
226+
BenchmarkStructSimpleFailureParallel-4 2000000 733 ns/op 529 B/op 11 allocs/op
227+
BenchmarkStructComplexSuccess-4 200000 7104 ns/op 368 B/op 30 allocs/op
228+
BenchmarkStructComplexFailure-4 100000 11996 ns/op 2861 B/op 72 allocs/op
229+
BenchmarkStructComplexSuccessParallel-4 1000000 2252 ns/op 368 B/op 30 allocs/op
230+
BenchmarkStructComplexFailureParallel-4 300000 4691 ns/op 2862 B/op 72 allocs/op
154231
```
155232

156233
How to Contribute

benchmarks_test.go

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package validator
22

3-
import "testing"
3+
import (
4+
sql "database/sql/driver"
5+
"reflect"
6+
"testing"
7+
)
48

59
func BenchmarkFieldSuccess(b *testing.B) {
610
for n := 0; n < b.N; n++ {
@@ -14,6 +18,38 @@ func BenchmarkFieldFailure(b *testing.B) {
1418
}
1519
}
1620

21+
func BenchmarkFieldCustomTypeSuccess(b *testing.B) {
22+
23+
customTypes := map[reflect.Type]CustomTypeFunc{}
24+
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
25+
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
26+
27+
validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})
28+
29+
val := valuer{
30+
Name: "1",
31+
}
32+
33+
for n := 0; n < b.N; n++ {
34+
validate.Field(val, "len=1")
35+
}
36+
}
37+
38+
func BenchmarkFieldCustomTypeFailure(b *testing.B) {
39+
40+
customTypes := map[reflect.Type]CustomTypeFunc{}
41+
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
42+
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
43+
44+
validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})
45+
46+
val := valuer{}
47+
48+
for n := 0; n < b.N; n++ {
49+
validate.Field(val, "len=1")
50+
}
51+
}
52+
1753
func BenchmarkFieldOrTagSuccess(b *testing.B) {
1854
for n := 0; n < b.N; n++ {
1955
validate.Field("rgba(0,0,0,1)", "rgb|rgba")
@@ -54,6 +90,52 @@ func BenchmarkStructSimpleFailure(b *testing.B) {
5490
}
5591
}
5692

93+
func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
94+
95+
customTypes := map[reflect.Type]CustomTypeFunc{}
96+
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
97+
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
98+
99+
validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})
100+
101+
val := valuer{
102+
Name: "1",
103+
}
104+
105+
type Foo struct {
106+
Valuer valuer `validate:"len=1"`
107+
IntValue int `validate:"min=5,max=10"`
108+
}
109+
110+
validFoo := &Foo{Valuer: val, IntValue: 7}
111+
112+
for n := 0; n < b.N; n++ {
113+
validate.Struct(validFoo)
114+
}
115+
}
116+
117+
func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {
118+
119+
customTypes := map[reflect.Type]CustomTypeFunc{}
120+
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
121+
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
122+
123+
validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})
124+
125+
val := valuer{}
126+
127+
type Foo struct {
128+
Valuer valuer `validate:"len=1"`
129+
IntValue int `validate:"min=5,max=10"`
130+
}
131+
132+
validFoo := &Foo{Valuer: val, IntValue: 3}
133+
134+
for n := 0; n < b.N; n++ {
135+
validate.Struct(validFoo)
136+
}
137+
}
138+
57139
func BenchmarkStructSimpleSuccessParallel(b *testing.B) {
58140

59141
type Foo struct {

examples/simple.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package main
22

33
import (
4+
"errors"
45
"fmt"
6+
"reflect"
7+
8+
sql "database/sql/driver"
59

610
"gopkg.in/bluesuncorp/validator.v6"
711
)
@@ -90,3 +94,57 @@ func validateField() {
9094

9195
// email ok, move on
9296
}
97+
98+
var validate2 *validator.Validate
99+
100+
type valuer struct {
101+
Name string
102+
}
103+
104+
func (v valuer) Value() (sql.Value, error) {
105+
106+
if v.Name == "errorme" {
107+
return nil, errors.New("some kind of error")
108+
}
109+
110+
if v.Name == "blankme" {
111+
return "", nil
112+
}
113+
114+
if len(v.Name) == 0 {
115+
return nil, nil
116+
}
117+
118+
return v.Name, nil
119+
}
120+
121+
func main2() {
122+
123+
customTypes := map[reflect.Type]validator.CustomTypeFunc{}
124+
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
125+
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
126+
127+
config := validator.Config{
128+
TagName: "validate",
129+
ValidationFuncs: validator.BakedInValidators,
130+
CustomTypeFuncs: customTypes,
131+
}
132+
133+
validate2 = validator.New(config)
134+
135+
validateCustomFieldType()
136+
}
137+
138+
func validateCustomFieldType() {
139+
val := valuer{
140+
Name: "blankme",
141+
}
142+
143+
errs := validate2.Field(val, "required")
144+
if errs != nil {
145+
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag
146+
return
147+
}
148+
149+
// all ok
150+
}

validator.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ var (
4545

4646
// returns new ValidationErrors to the pool
4747
func newValidationErrors() interface{} {
48-
return map[string]*FieldError{}
48+
return ValidationErrors{}
4949
}
5050

5151
type tagCache struct {
@@ -81,8 +81,15 @@ type Validate struct {
8181
type Config struct {
8282
TagName string
8383
ValidationFuncs map[string]Func
84+
CustomTypeFuncs map[reflect.Type]CustomTypeFunc
85+
hasCustomFuncs bool
8486
}
8587

88+
// CustomTypeFunc allows for overriding or adding custom field type handler functions
89+
// field = field value of the type to return a value to be validated
90+
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
91+
type CustomTypeFunc func(field reflect.Value) interface{}
92+
8693
// Func accepts all values needed for file and cross field validation
8794
// topStruct = top level struct when validating by struct otherwise nil
8895
// currentStruct = current level struct when validating by struct otherwise optional comparison value
@@ -124,6 +131,11 @@ type FieldError struct {
124131

125132
// New creates a new Validate instance for use.
126133
func New(config Config) *Validate {
134+
135+
if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 {
136+
config.hasCustomFuncs = true
137+
}
138+
127139
return &Validate{config: config}
128140
}
129141

@@ -150,7 +162,7 @@ func (v *Validate) RegisterValidation(key string, f Func) error {
150162
// validate Array, Slice and maps fields which may contain more than one error
151163
func (v *Validate) Field(field interface{}, tag string) ValidationErrors {
152164

153-
errs := errsPool.Get().(map[string]*FieldError)
165+
errs := errsPool.Get().(ValidationErrors)
154166
fieldVal := reflect.ValueOf(field)
155167

156168
v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "")
@@ -168,7 +180,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors {
168180
// validate Array, Slice and maps fields which may contain more than one error
169181
func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) ValidationErrors {
170182

171-
errs := errsPool.Get().(map[string]*FieldError)
183+
errs := errsPool.Get().(ValidationErrors)
172184
topVal := reflect.ValueOf(val)
173185

174186
v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "")
@@ -184,7 +196,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string
184196
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
185197
func (v *Validate) Struct(current interface{}) ValidationErrors {
186198

187-
errs := errsPool.Get().(map[string]*FieldError)
199+
errs := errsPool.Get().(ValidationErrors)
188200
sv := reflect.ValueOf(current)
189201

190202
v.tranverseStruct(sv, sv, sv, "", errs, true)
@@ -316,6 +328,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
316328

317329
if kind == reflect.Struct {
318330

331+
if v.config.hasCustomFuncs {
332+
if fn, ok := v.config.CustomTypeFuncs[typ]; ok {
333+
v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name)
334+
return
335+
}
336+
}
337+
319338
// required passed validation above so stop here
320339
// if only validating the structs existance.
321340
if strings.Contains(tag, structOnlyTag) {
@@ -334,6 +353,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
334353
}
335354
}
336355

356+
if v.config.hasCustomFuncs {
357+
if fn, ok := v.config.CustomTypeFuncs[typ]; ok {
358+
v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name)
359+
return
360+
}
361+
}
362+
337363
tags, isCached := tagsCache.Get(tag)
338364

339365
if !isCached {

0 commit comments

Comments
 (0)