From a5d0ef37b8327b10841b0977666604bf1a26ce9f Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Thu, 23 Jul 2015 12:30:29 -0400 Subject: [PATCH 01/16] update doc.go, some v5 specific notes were left. --- doc.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/doc.go b/doc.go index df2db9f..6733893 100644 --- a/doc.go +++ b/doc.go @@ -121,11 +121,6 @@ Here is a list of the current built in validators: gt=0 will be applied to [] []string will be spared validation required will be applied to string - NOTE: in Example2 if the required validation failed, but all others passed - the hierarchy of FieldError's in the middle with have their IsPlaceHolder field - set to true. If a FieldError has IsSliceOrMap=true or IsMap=true then the - FieldError is a Slice or Map field and if IsPlaceHolder=true then contains errors - within its SliceOrArrayErrs or MapErrs fields. required This validates that the value is not the data types default value. From aef21d4d303b4dea856c2bb867f9617c1ef61dbf Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Thu, 23 Jul 2015 12:44:17 -0400 Subject: [PATCH 02/16] Update Benchmarks Split out benchmarks into Success and Failure for more realistic numbers. I was validating 2 structs within the benchmarks, both a Successful and Failing struct which was artificially inflating the numbers. --- README.md | 18 ++++--- benchmarks_test.go | 116 ++++++++++++++++++++++++++++++++------------- 2 files changed, 96 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 96aa669..312a30e 100644 --- a/README.md +++ b/README.md @@ -120,12 +120,18 @@ hurt parallel performance too much. ```go $ go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkField-4 5000000 314 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTag-4 500000 2425 ns/op 20 B/op 2 allocs/op -BenchmarkStructSimple-4 500000 3117 ns/op 553 B/op 14 allocs/op -BenchmarkStructSimpleParallel-4 1000000 1149 ns/op 553 B/op 14 allocs/op -BenchmarkStructComplex-4 100000 19580 ns/op 3230 B/op 102 allocs/op -BenchmarkStructComplexParallel-4 200000 6686 ns/op 3232 B/op 102 allocs/op +BenchmarkFieldSuccess-4 5000000 326 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 327 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagSuccess-4 500000 2738 ns/op 20 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1341 ns/op 384 B/op 6 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1282 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1870 ns/op 529 B/op 11 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 348 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 807 ns/op 529 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 8081 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailure-4 100000 12418 ns/op 2861 B/op 72 allocs/op +BenchmarkStructComplexSuccessParallel-4 500000 2249 ns/op 369 B/op 30 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 5183 ns/op 2863 B/op 72 allocs/op ``` How to Contribute diff --git a/benchmarks_test.go b/benchmarks_test.go index 14c5d2b..9fcf85b 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -2,19 +2,31 @@ package validator import "testing" -func BenchmarkField(b *testing.B) { +func BenchmarkFieldSuccess(b *testing.B) { for n := 0; n < b.N; n++ { validate.Field("1", "len=1") } } -func BenchmarkFieldOrTag(b *testing.B) { +func BenchmarkFieldFailure(b *testing.B) { + for n := 0; n < b.N; n++ { + validate.Field("2", "len=1") + } +} + +func BenchmarkFieldOrTagSuccess(b *testing.B) { for n := 0; n < b.N; n++ { validate.Field("rgba(0,0,0,1)", "rgb|rgba") } } -func BenchmarkStructSimple(b *testing.B) { +func BenchmarkFieldOrTagFailure(b *testing.B) { + for n := 0; n < b.N; n++ { + validate.Field("#000", "rgb|rgba") + } +} + +func BenchmarkStructSimpleSuccess(b *testing.B) { type Foo struct { StringValue string `validate:"min=5,max=10"` @@ -22,15 +34,27 @@ func BenchmarkStructSimple(b *testing.B) { } validFoo := &Foo{StringValue: "Foobar", IntValue: 7} - invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} for n := 0; n < b.N; n++ { validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleFailure(b *testing.B) { + + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + + invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} + + for n := 0; n < b.N; n++ { validate.Struct(invalidFoo) } } -func BenchmarkStructSimpleParallel(b *testing.B) { +func BenchmarkStructSimpleSuccessParallel(b *testing.B) { type Foo struct { StringValue string `validate:"min=5,max=10"` @@ -38,42 +62,32 @@ func BenchmarkStructSimpleParallel(b *testing.B) { } validFoo := &Foo{StringValue: "Foobar", IntValue: 7} - invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} b.RunParallel(func(pb *testing.PB) { for pb.Next() { validate.Struct(validFoo) - validate.Struct(invalidFoo) } }) } -func BenchmarkStructComplex(b *testing.B) { +func BenchmarkStructSimpleFailureParallel(b *testing.B) { - tFail := &TestString{ - Required: "", - Len: "", - Min: "", - Max: "12345678901", - MinMax: "", - Lt: "0123456789", - Lte: "01234567890", - Gt: "1", - Gte: "1", - OmitEmpty: "12345678901", - Sub: &SubTest{ - Test: "", - }, - Anonymous: struct { - A string `validate:"required"` - }{ - A: "", - }, - Iface: &Impl{ - F: "12", - }, + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` } + invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + validate.Struct(invalidFoo) + } + }) +} + +func BenchmarkStructComplexSuccess(b *testing.B) { + tSuccess := &TestString{ Required: "Required", Len: "length==10", @@ -103,11 +117,10 @@ func BenchmarkStructComplex(b *testing.B) { for n := 0; n < b.N; n++ { validate.Struct(tSuccess) - validate.Struct(tFail) } } -func BenchmarkStructComplexParallel(b *testing.B) { +func BenchmarkStructComplexFailure(b *testing.B) { tFail := &TestString{ Required: "", @@ -133,6 +146,13 @@ func BenchmarkStructComplexParallel(b *testing.B) { }, } + for n := 0; n < b.N; n++ { + validate.Struct(tFail) + } +} + +func BenchmarkStructComplexSuccessParallel(b *testing.B) { + tSuccess := &TestString{ Required: "Required", Len: "length==10", @@ -163,6 +183,38 @@ func BenchmarkStructComplexParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { validate.Struct(tSuccess) + } + }) +} + +func BenchmarkStructComplexFailureParallel(b *testing.B) { + + tFail := &TestString{ + Required: "", + Len: "", + Min: "", + Max: "12345678901", + MinMax: "", + Lt: "0123456789", + Lte: "01234567890", + Gt: "1", + Gte: "1", + OmitEmpty: "12345678901", + Sub: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + }{ + A: "", + }, + Iface: &Impl{ + F: "12", + }, + } + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { validate.Struct(tFail) } }) From 201c892979bb6605615f660ab371088623ac3347 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Thu, 23 Jul 2015 16:48:13 -0400 Subject: [PATCH 03/16] Split out assertions and put in external repo split our assertion functions like IsEqual, Equal, NotEqual, PanicMatches etc... into another library https://github.com/bluesuncorp/assert so that I can use them in other projects. --- validator_test.go | 178 +++++++++------------------------------------- 1 file changed, 34 insertions(+), 144 deletions(-) diff --git a/validator_test.go b/validator_test.go index 4021e86..79b5b66 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2,11 +2,11 @@ package validator import ( "fmt" - "path" "reflect" - "runtime" "testing" "time" + + . "gopkg.in/bluesuncorp/assert.v1" ) // NOTES: @@ -110,116 +110,6 @@ type TestSlice struct { var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators}) -func IsEqual(t *testing.T, val1, val2 interface{}) bool { - v1 := reflect.ValueOf(val1) - v2 := reflect.ValueOf(val2) - - if v1.Kind() == reflect.Ptr { - v1 = v1.Elem() - } - - if v2.Kind() == reflect.Ptr { - v2 = v2.Elem() - } - - if !v1.IsValid() && !v2.IsValid() { - return true - } - - switch v1.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - if v1.IsNil() { - v1 = reflect.ValueOf(nil) - } - } - - switch v2.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - if v2.IsNil() { - v2 = reflect.ValueOf(nil) - } - } - - v1Underlying := reflect.Zero(reflect.TypeOf(v1)).Interface() - v2Underlying := reflect.Zero(reflect.TypeOf(v2)).Interface() - - if v1 == v1Underlying { - if v2 == v2Underlying { - goto CASE4 - } else { - goto CASE3 - } - } else { - if v2 == v2Underlying { - goto CASE2 - } else { - goto CASE1 - } - } - -CASE1: - // fmt.Println("CASE 1") - return reflect.DeepEqual(v1.Interface(), v2.Interface()) -CASE2: - // fmt.Println("CASE 2") - return reflect.DeepEqual(v1.Interface(), v2) -CASE3: - // fmt.Println("CASE 3") - return reflect.DeepEqual(v1, v2.Interface()) -CASE4: - // fmt.Println("CASE 4") - return reflect.DeepEqual(v1, v2) -} - -func Equal(t *testing.T, val1, val2 interface{}) { - EqualSkip(t, 2, val1, val2) -} - -func EqualSkip(t *testing.T, skip int, val1, val2 interface{}) { - - if !IsEqual(t, val1, val2) { - - _, file, line, _ := runtime.Caller(skip) - fmt.Printf("%s:%d %v does not equal %v\n", path.Base(file), line, val1, val2) - t.FailNow() - } -} - -func NotEqual(t *testing.T, val1, val2 interface{}) { - NotEqualSkip(t, 2, val1, val2) -} - -func NotEqualSkip(t *testing.T, skip int, val1, val2 interface{}) { - - if IsEqual(t, val1, val2) { - _, file, line, _ := runtime.Caller(skip) - fmt.Printf("%s:%d %v should not be equal %v\n", path.Base(file), line, val1, val2) - t.FailNow() - } -} - -func PanicMatches(t *testing.T, fn func(), matches string) { - PanicMatchesSkip(t, 2, fn, matches) -} - -func PanicMatchesSkip(t *testing.T, skip int, fn func(), matches string) { - - _, file, line, _ := runtime.Caller(skip) - - defer func() { - if r := recover(); r != nil { - err := fmt.Sprintf("%s", r) - - if err != matches { - fmt.Printf("%s:%d Panic... expected [%s] received [%s]", path.Base(file), line, matches, err) - t.FailNow() - } - } - }() - - fn() -} - func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) { val, ok := errs[key] @@ -864,11 +754,11 @@ func TestSSNValidation(t *testing.T) { errs := validate.Field(test.param, "ssn") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d SSN failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d SSN failed Error: %s", i, errs) } else { val := errs[""] @@ -898,11 +788,11 @@ func TestLongitudeValidation(t *testing.T) { errs := validate.Field(test.param, "longitude") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) } else { val := errs[""] @@ -932,11 +822,11 @@ func TestLatitudeValidation(t *testing.T) { errs := validate.Field(test.param, "latitude") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } else { val := errs[""] @@ -972,11 +862,11 @@ func TestDataURIValidation(t *testing.T) { errs := validate.Field(test.param, "datauri") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) } else { val := errs[""] @@ -1010,11 +900,11 @@ func TestMultibyteValidation(t *testing.T) { errs := validate.Field(test.param, "multibyte") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) } else { val := errs[""] @@ -1049,11 +939,11 @@ func TestPrintableASCIIValidation(t *testing.T) { errs := validate.Field(test.param, "printascii") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) } else { val := errs[""] @@ -1087,11 +977,11 @@ func TestASCIIValidation(t *testing.T) { errs := validate.Field(test.param, "ascii") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) } else { val := errs[""] @@ -1122,11 +1012,11 @@ func TestUUID5Validation(t *testing.T) { errs := validate.Field(test.param, "uuid5") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) } else { val := errs[""] @@ -1156,11 +1046,11 @@ func TestUUID4Validation(t *testing.T) { errs := validate.Field(test.param, "uuid4") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) } else { val := errs[""] @@ -1189,11 +1079,11 @@ func TestUUID3Validation(t *testing.T) { errs := validate.Field(test.param, "uuid3") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) } else { val := errs[""] @@ -1225,11 +1115,11 @@ func TestUUIDValidation(t *testing.T) { errs := validate.Field(test.param, "uuid") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d UUID failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID failed Error: %s", i, errs) } else { val := errs[""] @@ -1263,11 +1153,11 @@ func TestISBNValidation(t *testing.T) { errs := validate.Field(test.param, "isbn") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) } else { val := errs[""] @@ -1300,11 +1190,11 @@ func TestISBN13Validation(t *testing.T) { errs := validate.Field(test.param, "isbn13") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) } else { val := errs[""] @@ -1338,11 +1228,11 @@ func TestISBN10Validation(t *testing.T) { errs := validate.Field(test.param, "isbn10") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) } else { val := errs[""] @@ -2702,11 +2592,11 @@ func TestUrl(t *testing.T) { errs := validate.Field(test.param, "url") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d URL failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d URL failed Error: %s", i, errs) } else { val := errs[""] @@ -2766,11 +2656,11 @@ func TestUri(t *testing.T) { errs := validate.Field(test.param, "uri") if test.expected == true { - if !IsEqual(t, errs, nil) { + if !IsEqual(errs, nil) { t.Fatalf("Index: %d URI failed Error: %s", i, errs) } } else { - if IsEqual(t, errs, nil) { + if IsEqual(errs, nil) { t.Fatalf("Index: %d URI failed Error: %s", i, errs) } else { val := errs[""] From ea0db1fa4709a606ab96baed64efffb4b5ca6fce Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 26 Jul 2015 21:48:27 -0400 Subject: [PATCH 04/16] Add single Field validation example --- README.md | 21 ++++++++++++++++++++- examples/simple.go | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 312a30e..e25bffc 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Usage and documentation Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs. -##### Example: +##### Examples: ```go package main @@ -73,6 +73,12 @@ func main() { validate = validator.New(config) + validateStruct() + validateField() +} + +func validateStruct() { + address := &Address{ Street: "Eavesdown Docks", Planet: "Persphone", @@ -109,6 +115,19 @@ func main() { // save user to database } + +func validateField() { + myEmail := "joeybloggs.gmail.com" + + errs := validate.Field(myEmail, "required,email") + + if errs != nil { + fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag + return + } + + // email ok, move on +} ``` Benchmarks diff --git a/examples/simple.go b/examples/simple.go index 98dabed..46e9295 100644 --- a/examples/simple.go +++ b/examples/simple.go @@ -35,6 +35,12 @@ func main() { validate = validator.New(config) + validateStruct() + validateField() +} + +func validateStruct() { + address := &Address{ Street: "Eavesdown Docks", Planet: "Persphone", @@ -71,3 +77,16 @@ func main() { // save user to database } + +func validateField() { + myEmail := "joeybloggs.gmail.com" + + errs := validate.Field(myEmail, "required,email") + + if errs != nil { + fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag + return + } + + // email ok, move on +} From e078205c787cfb7e8b62e6711f9665f1585588d0 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 27 Jul 2015 17:20:42 -0400 Subject: [PATCH 05/16] Update Required & Invalid logic updated required validator to check for a nil value for types: slice, map, pointer, interface, channel and function. updated tranverseField to handle invalid field type. Changes to be committed: modified: baked_in.go modified: doc.go modified: validator.go modified: validator_test.go --- baked_in.go | 6 ++-- doc.go | 5 +-- validator.go | 22 +++++++++++--- validator_test.go | 77 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 98 insertions(+), 12 deletions(-) diff --git a/baked_in.go b/baked_in.go index bf9e512..9bbf399 100644 --- a/baked_in.go +++ b/baked_in.go @@ -403,10 +403,8 @@ func isAlpha(topStruct reflect.Value, currentStruct reflect.Value, field reflect func hasValue(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { - - case reflect.Slice, reflect.Map, reflect.Array: - return !field.IsNil() && int64(field.Len()) > 0 - + case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: + return !field.IsNil() default: return field.IsValid() && field.Interface() != reflect.Zero(fieldType).Interface() } diff --git a/doc.go b/doc.go index 6733893..82804b8 100644 --- a/doc.go +++ b/doc.go @@ -123,9 +123,10 @@ Here is a list of the current built in validators: required will be applied to string required - This validates that the value is not the data types default value. + This validates that the value is not the data types default zero value. For numbers ensures value is not zero. For strings ensures value is - not "". For slices, arrays, and maps, ensures the length is not zero. + not "". For slices, maps, pointers, interfaces, channels and functions + ensures the value is not nil. (Usage: required) len diff --git a/validator.go b/validator.go index 781bea3..19f86a7 100644 --- a/validator.go +++ b/validator.go @@ -243,12 +243,10 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. kind = current.Kind() } - typ := current.Type() - // this also allows for tags 'required' and 'omitempty' to be used on // nested struct fields because when len(tags) > 0 below and the value is nil // then required failes and we check for omitempty just before that - if (kind == reflect.Ptr || kind == reflect.Interface) && current.IsNil() { + if ((kind == reflect.Ptr || kind == reflect.Interface) && current.IsNil()) || kind == reflect.Invalid { if strings.Contains(tag, omitempty) { return @@ -264,19 +262,35 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. param = vals[1] } + if kind == reflect.Invalid { + errs[errPrefix+name] = &FieldError{ + Field: name, + Tag: vals[0], + Param: param, + Kind: kind, + } + return + } + errs[errPrefix+name] = &FieldError{ Field: name, Tag: vals[0], Param: param, Value: current.Interface(), Kind: kind, - Type: typ, + Type: current.Type(), } return } + // if we get here tag length is zero and we can leave + if kind == reflect.Invalid { + return + } } + typ := current.Type() + switch kind { case reflect.Struct, reflect.Interface: diff --git a/validator_test.go b/validator_test.go index 79b5b66..6048606 100644 --- a/validator_test.go +++ b/validator_test.go @@ -119,6 +119,79 @@ func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag st EqualSkip(t, 2, val.Tag, expectedTag) } +func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) { + + var m map[string]string + + errs := validate.Field(m, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + m = map[string]string{} + errs = validate.Field(m, "required") + Equal(t, errs, nil) + + var arr [5]string + errs = validate.Field(arr, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + arr[0] = "ok" + errs = validate.Field(arr, "required") + Equal(t, errs, nil) + + var s []string + errs = validate.Field(s, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + s = []string{} + errs = validate.Field(s, "required") + Equal(t, errs, nil) + + var c chan string + errs = validate.Field(c, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + c = make(chan string) + errs = validate.Field(c, "required") + Equal(t, errs, nil) + + var tst *int + errs = validate.Field(tst, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + one := 1 + tst = &one + errs = validate.Field(tst, "required") + Equal(t, errs, nil) + + var iface interface{} + + errs = validate.Field(iface, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + errs = validate.Field(iface, "omitempty,required") + Equal(t, errs, nil) + + errs = validate.Field(iface, "") + Equal(t, errs, nil) + + var f func(string) + + errs = validate.Field(f, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + f = func(name string) {} + + errs = validate.Field(f, "required") + Equal(t, errs, nil) +} + func TestDatePtrValidationIssueValidation(t *testing.T) { type Test struct { @@ -3262,14 +3335,14 @@ func TestStructSliceValidation(t *testing.T) { Min: []int{1, 2}, Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, MinMax: []int{1, 2, 3, 4, 5}, - OmitEmpty: []int{}, + OmitEmpty: nil, } errs := validate.Struct(tSuccess) Equal(t, errs, nil) tFail := &TestSlice{ - Required: []int{}, + Required: nil, Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, Min: []int{}, Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, From bd16331fc0de0f558c0c7c76009b88b3d7052219 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 27 Jul 2015 22:31:33 -0400 Subject: [PATCH 06/16] Add ip, ipv4 and ipv6 validators --- baked_in.go | 24 ++++++++++ doc.go | 12 +++++ validator_test.go | 114 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) diff --git a/baked_in.go b/baked_in.go index 9bbf399..95a7431 100644 --- a/baked_in.go +++ b/baked_in.go @@ -2,6 +2,7 @@ package validator import ( "fmt" + "net" "net/url" "reflect" "strconv" @@ -64,6 +65,29 @@ var BakedInValidators = map[string]Func{ "latitude": isLatitude, "longitude": isLongitude, "ssn": isSSN, + "ipv4": isIPv4, + "ipv6": isIPv6, + "ip": isIP, +} + +func isIPv4(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + ip := net.ParseIP(field.String()) + + return ip != nil && ip.To4() != nil +} + +func isIPv6(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + ip := net.ParseIP(field.String()) + + return ip != nil && ip.To4() == nil +} + +func isIP(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + ip := net.ParseIP(field.String()) + + return ip != nil } func isSSN(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { diff --git a/doc.go b/doc.go index 82804b8..e81d6c2 100644 --- a/doc.go +++ b/doc.go @@ -372,6 +372,18 @@ Here is a list of the current built in validators: This validates that a string value contains a valid U.S. Social Security Number. (Usage: ssn) + ip + This validates that a string value contains a valid IP Adress. + (Usage: ip) + + ipv4 + This validates that a string value contains a valid v4 IP Adress. + (Usage: ipv4) + + ipv6 + This validates that a string value contains a valid v6 IP Adress. + (Usage: ipv6) + Validator notes: regex diff --git a/validator_test.go b/validator_test.go index 6048606..87e0716 100644 --- a/validator_test.go +++ b/validator_test.go @@ -119,6 +119,120 @@ func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag st EqualSkip(t, 2, val.Tag, expectedTag) } +func TestIPValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.1", true}, + {"172.16.0.1", true}, + {"192.168.0.1", true}, + {"192.168.255.254", true}, + {"192.168.255.256", false}, + {"172.16.255.254", true}, + {"172.16.256.255", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", true}, + {"2001:cdba:0:0:0:0:3257:9652", true}, + {"2001:cdba::3257:9652", true}, + } + + for i, test := range tests { + + errs := validate.Field(test.param, "ip") + + if test.expected == true { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ip failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ip failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "ip" { + t.Fatalf("Index: %d ip failed Error: %s", i, errs) + } + } + } + } +} + +func TestIPv6Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.1", false}, + {"172.16.0.1", false}, + {"192.168.0.1", false}, + {"192.168.255.254", false}, + {"192.168.255.256", false}, + {"172.16.255.254", false}, + {"172.16.256.255", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", true}, + {"2001:cdba:0:0:0:0:3257:9652", true}, + {"2001:cdba::3257:9652", true}, + } + + for i, test := range tests { + + errs := validate.Field(test.param, "ipv6") + + if test.expected == true { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "ipv6" { + t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) + } + } + } + } +} + +func TestIPv4Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.1", true}, + {"172.16.0.1", true}, + {"192.168.0.1", true}, + {"192.168.255.254", true}, + {"192.168.255.256", false}, + {"172.16.255.254", true}, + {"172.16.256.255", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, + {"2001:cdba:0:0:0:0:3257:9652", false}, + {"2001:cdba::3257:9652", false}, + } + + for i, test := range tests { + + errs := validate.Field(test.param, "ipv4") + + if test.expected == true { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "ipv4" { + t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) + } + } + } + } +} + func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) { var m map[string]string From 143b21eba1ed00e1e204970f6d6a11bcd18a2380 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 27 Jul 2015 22:45:59 -0400 Subject: [PATCH 07/16] Add mac validator --- baked_in.go | 6 ++++++ doc.go | 6 ++++++ validator_test.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/baked_in.go b/baked_in.go index 95a7431..b323863 100644 --- a/baked_in.go +++ b/baked_in.go @@ -68,6 +68,12 @@ var BakedInValidators = map[string]Func{ "ipv4": isIPv4, "ipv6": isIPv6, "ip": isIP, + "mac": isMac, +} + +func isMac(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + _, err := net.ParseMAC(field.String()) + return err == nil } func isIPv4(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { diff --git a/doc.go b/doc.go index e81d6c2..ba60177 100644 --- a/doc.go +++ b/doc.go @@ -384,6 +384,12 @@ Here is a list of the current built in validators: This validates that a string value contains a valid v6 IP Adress. (Usage: ipv6) + mac + This validates that a string value contains a valid MAC Adress defined + by go's ParseMAC accepted formats and types see: + http://golang.org/src/net/mac.go?s=866:918#L29 + (Usage: mac) + Validator notes: regex diff --git a/validator_test.go b/validator_test.go index 87e0716..d69c1a2 100644 --- a/validator_test.go +++ b/validator_test.go @@ -119,6 +119,41 @@ func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag st EqualSkip(t, 2, val.Tag, expectedTag) } +func TestMACValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"3D:F2:C9:A6:B3:4F", true}, + {"3D-F2-C9-A6-B3:4F", false}, + {"123", false}, + {"", false}, + {"abacaba", false}, + {"00:25:96:FF:FE:12:34:56", true}, + {"0025:96FF:FE12:3456", false}, + } + + for i, test := range tests { + + errs := validate.Field(test.param, "mac") + + if test.expected == true { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d mac failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d mac failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "mac" { + t.Fatalf("Index: %d mac failed Error: %s", i, errs) + } + } + } + } +} + func TestIPValidation(t *testing.T) { tests := []struct { param string From d363ed316cc156a9a4d70d28c9443ce4d7a5878e Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Thu, 30 Jul 2015 22:24:43 -0400 Subject: [PATCH 08/16] Add handling of custom types can add custom type handling, example: sql driver Valuer can override any base data type like string or int ( I'd be carefull about that, but you can ) --- README.md | 103 +++++++++++++++++++++++++++++++----- benchmarks_test.go | 84 ++++++++++++++++++++++++++++- examples/simple.go | 58 ++++++++++++++++++++ validator.go | 34 ++++++++++-- validator_test.go | 128 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 389 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index e25bffc..6c88537 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ It has the following **unique** features: - Cross Field and Cross Struct validations. - Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated. -- Handles type interface by determining it's underlying type prior to validation. +- Handles type interface by determining it's underlying type prior to validation. +- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29) Installation ------------ @@ -35,6 +36,8 @@ Usage and documentation Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs. ##### Examples: + +Struct & Field validation ```go package main @@ -130,6 +133,76 @@ func validateField() { } ``` +Custom Field Type +```go +package main + +import ( + "errors" + "fmt" + "reflect" + + sql "database/sql/driver" + + "gopkg.in/bluesuncorp/validator.v6" +) + +var validate *validator.Validate + +type valuer struct { + Name string +} + +func (v valuer) Value() (sql.Value, error) { + + if v.Name == "errorme" { + return nil, errors.New("some kind of error") + } + + if v.Name == "blankme" { + return "", nil + } + + if len(v.Name) == 0 { + return nil, nil + } + + return v.Name, nil +} + +func main() { + + customTypes := map[reflect.Type]validator.CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + CustomTypeFuncs: customTypes, + } + + validate = validator.New(config) + + validateCustomFieldType() +} + +func validateCustomFieldType() { + val := valuer{ + Name: "blankme", + } + + errs := validate.Field(val, "required") + if errs != nil { + fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag + return + } + + // all ok +} + +``` + Benchmarks ------ ###### 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. ```go $ go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkFieldSuccess-4 5000000 326 ns/op 16 B/op 1 allocs/op -BenchmarkFieldFailure-4 5000000 327 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagSuccess-4 500000 2738 ns/op 20 B/op 2 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1341 ns/op 384 B/op 6 allocs/op -BenchmarkStructSimpleSuccess-4 1000000 1282 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailure-4 1000000 1870 ns/op 529 B/op 11 allocs/op -BenchmarkStructSimpleSuccessParallel-4 5000000 348 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 807 ns/op 529 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 200000 8081 ns/op 368 B/op 30 allocs/op -BenchmarkStructComplexFailure-4 100000 12418 ns/op 2861 B/op 72 allocs/op -BenchmarkStructComplexSuccessParallel-4 500000 2249 ns/op 369 B/op 30 allocs/op -BenchmarkStructComplexFailureParallel-4 300000 5183 ns/op 2863 B/op 72 allocs/op +BenchmarkFieldSuccess-4 5000000 318 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 316 ns/op 16 B/op 1 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 492 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 843 ns/op 416 B/op 6 allocs/op +BenchmarkFieldOrTagSuccess-4 500000 2384 ns/op 20 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1295 ns/op 384 B/op 6 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1175 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1822 ns/op 529 B/op 11 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1302 ns/op 56 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1847 ns/op 577 B/op 13 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 339 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 733 ns/op 529 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 7104 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailure-4 100000 11996 ns/op 2861 B/op 72 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2252 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 4691 ns/op 2862 B/op 72 allocs/op ``` How to Contribute diff --git a/benchmarks_test.go b/benchmarks_test.go index 9fcf85b..af201e6 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -1,6 +1,10 @@ package validator -import "testing" +import ( + sql "database/sql/driver" + "reflect" + "testing" +) func BenchmarkFieldSuccess(b *testing.B) { for n := 0; n < b.N; n++ { @@ -14,6 +18,38 @@ func BenchmarkFieldFailure(b *testing.B) { } } +func BenchmarkFieldCustomTypeSuccess(b *testing.B) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + val := valuer{ + Name: "1", + } + + for n := 0; n < b.N; n++ { + validate.Field(val, "len=1") + } +} + +func BenchmarkFieldCustomTypeFailure(b *testing.B) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + val := valuer{} + + for n := 0; n < b.N; n++ { + validate.Field(val, "len=1") + } +} + func BenchmarkFieldOrTagSuccess(b *testing.B) { for n := 0; n < b.N; n++ { validate.Field("rgba(0,0,0,1)", "rgb|rgba") @@ -54,6 +90,52 @@ func BenchmarkStructSimpleFailure(b *testing.B) { } } +func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + val := valuer{ + Name: "1", + } + + type Foo struct { + Valuer valuer `validate:"len=1"` + IntValue int `validate:"min=5,max=10"` + } + + validFoo := &Foo{Valuer: val, IntValue: 7} + + for n := 0; n < b.N; n++ { + validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + val := valuer{} + + type Foo struct { + Valuer valuer `validate:"len=1"` + IntValue int `validate:"min=5,max=10"` + } + + validFoo := &Foo{Valuer: val, IntValue: 3} + + for n := 0; n < b.N; n++ { + validate.Struct(validFoo) + } +} + func BenchmarkStructSimpleSuccessParallel(b *testing.B) { type Foo struct { diff --git a/examples/simple.go b/examples/simple.go index 46e9295..fd503c4 100644 --- a/examples/simple.go +++ b/examples/simple.go @@ -1,7 +1,11 @@ package main import ( + "errors" "fmt" + "reflect" + + sql "database/sql/driver" "gopkg.in/bluesuncorp/validator.v6" ) @@ -90,3 +94,57 @@ func validateField() { // email ok, move on } + +var validate2 *validator.Validate + +type valuer struct { + Name string +} + +func (v valuer) Value() (sql.Value, error) { + + if v.Name == "errorme" { + return nil, errors.New("some kind of error") + } + + if v.Name == "blankme" { + return "", nil + } + + if len(v.Name) == 0 { + return nil, nil + } + + return v.Name, nil +} + +func main2() { + + customTypes := map[reflect.Type]validator.CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + CustomTypeFuncs: customTypes, + } + + validate2 = validator.New(config) + + validateCustomFieldType() +} + +func validateCustomFieldType() { + val := valuer{ + Name: "blankme", + } + + errs := validate2.Field(val, "required") + if errs != nil { + fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag + return + } + + // all ok +} diff --git a/validator.go b/validator.go index 19f86a7..e557d28 100644 --- a/validator.go +++ b/validator.go @@ -45,7 +45,7 @@ var ( // returns new ValidationErrors to the pool func newValidationErrors() interface{} { - return map[string]*FieldError{} + return ValidationErrors{} } type tagCache struct { @@ -81,8 +81,15 @@ type Validate struct { type Config struct { TagName string ValidationFuncs map[string]Func + CustomTypeFuncs map[reflect.Type]CustomTypeFunc + hasCustomFuncs bool } +// CustomTypeFunc allows for overriding or adding custom field type handler functions +// field = field value of the type to return a value to be validated +// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29 +type CustomTypeFunc func(field reflect.Value) interface{} + // Func accepts all values needed for file and cross field validation // topStruct = top level struct when validating by struct otherwise nil // currentStruct = current level struct when validating by struct otherwise optional comparison value @@ -124,6 +131,11 @@ type FieldError struct { // New creates a new Validate instance for use. func New(config Config) *Validate { + + if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { + config.hasCustomFuncs = true + } + return &Validate{config: config} } @@ -150,7 +162,7 @@ func (v *Validate) RegisterValidation(key string, f Func) error { // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) Field(field interface{}, tag string) ValidationErrors { - errs := errsPool.Get().(map[string]*FieldError) + errs := errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "") @@ -168,7 +180,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors { // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) ValidationErrors { - errs := errsPool.Get().(map[string]*FieldError) + errs := errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "") @@ -184,7 +196,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. func (v *Validate) Struct(current interface{}) ValidationErrors { - errs := errsPool.Get().(map[string]*FieldError) + errs := errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) v.tranverseStruct(sv, sv, sv, "", errs, true) @@ -316,6 +328,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. if kind == reflect.Struct { + if v.config.hasCustomFuncs { + if fn, ok := v.config.CustomTypeFuncs[typ]; ok { + v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) + return + } + } + // required passed validation above so stop here // if only validating the structs existance. if strings.Contains(tag, structOnlyTag) { @@ -334,6 +353,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. } } + if v.config.hasCustomFuncs { + if fn, ok := v.config.CustomTypeFuncs[typ]; ok { + v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) + return + } + } + tags, isCached := tagsCache.Get(tag) if !isCached { diff --git a/validator_test.go b/validator_test.go index d69c1a2..37b6dee 100644 --- a/validator_test.go +++ b/validator_test.go @@ -1,11 +1,14 @@ package validator import ( + "errors" "fmt" "reflect" "testing" "time" + sql "database/sql/driver" + . "gopkg.in/bluesuncorp/assert.v1" ) @@ -119,6 +122,131 @@ func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag st EqualSkip(t, 2, val.Tag, expectedTag) } +type valuer struct { + Name string +} + +func (v valuer) Value() (sql.Value, error) { + + if v.Name == "errorme" { + return nil, errors.New("some kind of error") + } + + if len(v.Name) == 0 { + return nil, nil + } + + return v.Name, nil +} + +type MadeUpCustomType struct { + FirstName string + LastName string +} + +func ValidateCustomType(field reflect.Value) interface{} { + if cust, ok := field.Interface().(MadeUpCustomType); ok { + + if len(cust.FirstName) == 0 || len(cust.LastName) == 0 { + return "" + } + + return cust.FirstName + " " + cust.LastName + } + + return "" +} + +func OverrideIntTypeForSomeReason(field reflect.Value) interface{} { + + if i, ok := field.Interface().(int); ok { + if i == 1 { + return "1" + } + + if i == 2 { + return "12" + } + } + + return "" +} + +type CustomMadeUpStruct struct { + MadeUp MadeUpCustomType `validate:"required"` + OverriddenInt int `validate:"gt=1"` +} + +func ValidateValuerType(field reflect.Value) interface{} { + if valuer, ok := field.Interface().(sql.Valuer); ok { + val, err := valuer.Value() + if err != nil { + // handle the error how you want + return nil + } + + return val + } + + return nil +} + +func TestSQLValueValidation(t *testing.T) { + + customTypes := map[reflect.Type]CustomTypeFunc{} + customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType + customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason + + validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + + val := valuer{ + Name: "", + } + + errs := validate.Field(val, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + val.Name = "Valid Name" + errs = validate.Field(val, "required") + Equal(t, errs, nil) + + val.Name = "errorme" + + PanicMatches(t, func() { errs = validate.Field(val, "required") }, "SQL Driver Valuer error: some kind of error") + + type myValuer valuer + + myVal := valuer{ + Name: "", + } + + errs = validate.Field(myVal, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + cust := MadeUpCustomType{ + FirstName: "Joey", + LastName: "Bloggs", + } + + c := CustomMadeUpStruct{MadeUp: cust, OverriddenInt: 2} + + errs = validate.Struct(c) + Equal(t, errs, nil) + + c.MadeUp.FirstName = "" + c.OverriddenInt = 1 + + errs = validate.Struct(c) + NotEqual(t, errs, nil) + Equal(t, len(errs), 2) + AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required") + AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt") +} + func TestMACValidation(t *testing.T) { tests := []struct { param string From 790122c21c381a67d664763e8d50922eeb3b345e Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Thu, 30 Jul 2015 22:37:45 -0400 Subject: [PATCH 09/16] Update simple.go tmp blank out of file --- examples/simple.go | 242 ++++++++++++++++++++++----------------------- 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/examples/simple.go b/examples/simple.go index fd503c4..e58414e 100644 --- a/examples/simple.go +++ b/examples/simple.go @@ -1,150 +1,150 @@ package main -import ( - "errors" - "fmt" - "reflect" - - sql "database/sql/driver" - - "gopkg.in/bluesuncorp/validator.v6" -) - -// User contains user information -type User struct { - FirstName string `validate:"required"` - LastName string `validate:"required"` - Age uint8 `validate:"gte=0,lte=130"` - Email string `validate:"required,email"` - FavouriteColor string `validate:"hexcolor|rgb|rgba"` - Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... -} +// import ( +// "errors" +// "fmt" +// "reflect" + +// sql "database/sql/driver" + +// "gopkg.in/bluesuncorp/validator.v6" +// ) + +// // User contains user information +// type User struct { +// FirstName string `validate:"required"` +// LastName string `validate:"required"` +// Age uint8 `validate:"gte=0,lte=130"` +// Email string `validate:"required,email"` +// FavouriteColor string `validate:"hexcolor|rgb|rgba"` +// Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... +// } + +// // Address houses a users address information +// type Address struct { +// Street string `validate:"required"` +// City string `validate:"required"` +// Planet string `validate:"required"` +// Phone string `validate:"required"` +// } + +// var validate *validator.Validate + +func main() { + +// config := validator.Config{ +// TagName: "validate", +// ValidationFuncs: validator.BakedInValidators, +// } -// Address houses a users address information -type Address struct { - Street string `validate:"required"` - City string `validate:"required"` - Planet string `validate:"required"` - Phone string `validate:"required"` +// validate = validator.New(config) + +// validateStruct() +// validateField() } -var validate *validator.Validate +// func validateStruct() { -func main() { +// address := &Address{ +// Street: "Eavesdown Docks", +// Planet: "Persphone", +// Phone: "none", +// } - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } +// user := &User{ +// FirstName: "Badger", +// LastName: "Smith", +// Age: 135, +// Email: "Badger.Smith@gmail.com", +// FavouriteColor: "#000", +// Addresses: []*Address{address}, +// } - validate = validator.New(config) +// // returns nil or ValidationErrors ( map[string]*FieldError ) +// errs := validate.Struct(user) - validateStruct() - validateField() -} +// if errs != nil { -func validateStruct() { - - address := &Address{ - Street: "Eavesdown Docks", - Planet: "Persphone", - Phone: "none", - } - - user := &User{ - FirstName: "Badger", - LastName: "Smith", - Age: 135, - Email: "Badger.Smith@gmail.com", - FavouriteColor: "#000", - Addresses: []*Address{address}, - } - - // returns nil or ValidationErrors ( map[string]*FieldError ) - errs := validate.Struct(user) - - if errs != nil { - - fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag - // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag - err := errs["User.Addresses[0].City"] - fmt.Println(err.Field) // output: City - fmt.Println(err.Tag) // output: required - fmt.Println(err.Kind) // output: string - fmt.Println(err.Type) // output: string - fmt.Println(err.Param) // output: - fmt.Println(err.Value) // output: - - // from here you can create your own error messages in whatever language you wish - return - } - - // save user to database -} +// fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag +// // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag +// err := errs["User.Addresses[0].City"] +// fmt.Println(err.Field) // output: City +// fmt.Println(err.Tag) // output: required +// fmt.Println(err.Kind) // output: string +// fmt.Println(err.Type) // output: string +// fmt.Println(err.Param) // output: +// fmt.Println(err.Value) // output: -func validateField() { - myEmail := "joeybloggs.gmail.com" +// // from here you can create your own error messages in whatever language you wish +// return +// } - errs := validate.Field(myEmail, "required,email") +// // save user to database +// } - if errs != nil { - fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag - return - } +// func validateField() { +// myEmail := "joeybloggs.gmail.com" - // email ok, move on -} +// errs := validate.Field(myEmail, "required,email") -var validate2 *validator.Validate +// if errs != nil { +// fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag +// return +// } -type valuer struct { - Name string -} +// // email ok, move on +// } -func (v valuer) Value() (sql.Value, error) { +// var validate2 *validator.Validate - if v.Name == "errorme" { - return nil, errors.New("some kind of error") - } +// type valuer struct { +// Name string +// } - if v.Name == "blankme" { - return "", nil - } +// func (v valuer) Value() (sql.Value, error) { - if len(v.Name) == 0 { - return nil, nil - } +// if v.Name == "errorme" { +// return nil, errors.New("some kind of error") +// } - return v.Name, nil -} +// if v.Name == "blankme" { +// return "", nil +// } -func main2() { +// if len(v.Name) == 0 { +// return nil, nil +// } - customTypes := map[reflect.Type]validator.CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType +// return v.Name, nil +// } - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - CustomTypeFuncs: customTypes, - } +// func main2() { - validate2 = validator.New(config) +// customTypes := map[reflect.Type]validator.CustomTypeFunc{} +// customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType +// customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - validateCustomFieldType() -} +// config := validator.Config{ +// TagName: "validate", +// ValidationFuncs: validator.BakedInValidators, +// CustomTypeFuncs: customTypes, +// } -func validateCustomFieldType() { - val := valuer{ - Name: "blankme", - } +// validate2 = validator.New(config) - errs := validate2.Field(val, "required") - if errs != nil { - fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag - return - } +// validateCustomFieldType() +// } - // all ok -} +// func validateCustomFieldType() { +// val := valuer{ +// Name: "blankme", +// } + +// errs := validate2.Field(val, "required") +// if errs != nil { +// fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag +// return +// } + +// // all ok +// } From 6a8ed5612a7653aabbc09c69b301c4f9ee3145d5 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Thu, 30 Jul 2015 22:44:06 -0400 Subject: [PATCH 10/16] Update simple.go file && README --- README.md | 15 +++++++++++++++ examples/simple.go | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/README.md b/README.md index 6c88537..32fc641 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,21 @@ func (v valuer) Value() (sql.Value, error) { return v.Name, nil } +// ValidateValuerType implements validator.CustomTypeFunc +func ValidateValuerType(field reflect.Value) interface{} { + if valuer, ok := field.Interface().(sql.Valuer); ok { + val, err := valuer.Value() + if err != nil { + // handle the error how you want + return nil + } + + return val + } + + return nil +} + func main() { customTypes := map[reflect.Type]validator.CustomTypeFunc{} diff --git a/examples/simple.go b/examples/simple.go index fd503c4..b685dda 100644 --- a/examples/simple.go +++ b/examples/simple.go @@ -118,6 +118,21 @@ func (v valuer) Value() (sql.Value, error) { return v.Name, nil } +// ValidateValuerType implements validator.CustomTypeFunc +func ValidateValuerType(field reflect.Value) interface{} { + if valuer, ok := field.Interface().(sql.Valuer); ok { + val, err := valuer.Value() + if err != nil { + // handle the error how you want + return nil + } + + return val + } + + return nil +} + func main2() { customTypes := map[reflect.Type]validator.CustomTypeFunc{} From 7c844893e1d184bc159ae7f4e8a27f30674c6439 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Fri, 31 Jul 2015 19:33:07 -0400 Subject: [PATCH 11/16] Added RegisterCustomTypeFunc method and usage example. --- examples/custom.go | 47 ++++++++++++++++++++++++++++++++++++++++++++++ validator.go | 11 +++++++++++ 2 files changed, 58 insertions(+) create mode 100644 examples/custom.go diff --git a/examples/custom.go b/examples/custom.go new file mode 100644 index 0000000..d7eb3b2 --- /dev/null +++ b/examples/custom.go @@ -0,0 +1,47 @@ +package main + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + + validator "gopkg.in/bluesuncorp/validator.v6" +) + +type DbBackedUser struct { + Name sql.NullString `validate:"required"` + Age sql.NullInt64 `validate:"required"` +} + +func main() { + + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate := validator.New(config) + + // register all sql.Null* types to use the ValidateValuer CustomTypeFunc + validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) + + x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} + errs := validate.Struct(x) + + if len(errs) > 0 { + fmt.Printf("Errs:\n%+v\n", errs) + } +} + +// ValidateValuer implements validator.CustomTypeFunc +func ValidateValuer(field reflect.Value) interface{} { + if valuer, ok := field.Interface().(driver.Valuer); ok { + val, err := valuer.Value() + if err == nil { + return val + } + // handle the error how you want + } + return nil +} diff --git a/validator.go b/validator.go index e557d28..9fb4dcf 100644 --- a/validator.go +++ b/validator.go @@ -157,6 +157,17 @@ func (v *Validate) RegisterValidation(key string, f Func) error { return nil } +// RegisterCustomTypeFunc registers types w/a custom type handler function. +func (v *Validate) RegisterCustomTypeFunc(f CustomTypeFunc, sampleTypeValues ...interface{}) { + if v.config.CustomTypeFuncs == nil { + v.config.CustomTypeFuncs = map[reflect.Type]CustomTypeFunc{} + } + for _, sample := range sampleTypeValues { + v.config.CustomTypeFuncs[reflect.TypeOf(sample)] = f + } + v.config.hasCustomFuncs = true +} + // Field validates a single field using tag style validation and returns ValidationErrors // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error From d2ea21ad1513b0e0da4c19fb205f38491df611bc Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sat, 1 Aug 2015 20:54:40 -0400 Subject: [PATCH 12/16] Add RegisterCustomTypeFunc for easier adding of CustomTypeFunc Thanks @johnniedoe for ths pull request! --- README.md | 83 ++++++++++----------------------- examples/{ => custom}/custom.go | 3 +- examples/{ => simple}/simple.go | 0 validator.go | 11 +++-- validator_test.go | 68 +++++++++++++++++++++++++-- 5 files changed, 97 insertions(+), 68 deletions(-) rename examples/{ => custom}/custom.go (94%) rename examples/{ => simple}/simple.go (100%) diff --git a/README.md b/README.md index d64138a..8c40597 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Package validator ================ [![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) -[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487374/badge.svg)](https://semaphoreci.com/joeybloggs/validator) +[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487383/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v6)](https://coveralls.io/r/bluesuncorp/validator?branch=v6) [![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v6?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v6) @@ -138,84 +138,51 @@ Custom Field Type package main import ( - "errors" + "database/sql" + "database/sql/driver" "fmt" "reflect" - sql "database/sql/driver" - "gopkg.in/bluesuncorp/validator.v6" ) -var validate *validator.Validate - -type valuer struct { - Name string -} - -func (v valuer) Value() (sql.Value, error) { - - if v.Name == "errorme" { - return nil, errors.New("some kind of error") - } - - if v.Name == "blankme" { - return "", nil - } - - if len(v.Name) == 0 { - return nil, nil - } - - return v.Name, nil -} - -// ValidateValuerType implements validator.CustomTypeFunc -func ValidateValuerType(field reflect.Value) interface{} { - if valuer, ok := field.Interface().(sql.Valuer); ok { - val, err := valuer.Value() - if err != nil { - // handle the error how you want - return nil - } - - return val - } - - return nil +// DbBackedUser User struct +type DbBackedUser struct { + Name sql.NullString `validate:"required"` + Age sql.NullInt64 `validate:"required"` } func main() { - customTypes := map[reflect.Type]validator.CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - config := validator.Config{ TagName: "validate", ValidationFuncs: validator.BakedInValidators, - CustomTypeFuncs: customTypes, } - validate = validator.New(config) + validate := validator.New(config) - validateCustomFieldType() -} + // register all sql.Null* types to use the ValidateValuer CustomTypeFunc + validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) -func validateCustomFieldType() { - val := valuer{ - Name: "blankme", - } + x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} + errs := validate.Struct(x) - errs := validate.Field(val, "required") - if errs != nil { - fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag - return + if len(errs) > 0 { + fmt.Printf("Errs:\n%+v\n", errs) } - - // all ok } +// ValidateValuer implements validator.CustomTypeFunc +func ValidateValuer(field reflect.Value) interface{} { + if valuer, ok := field.Interface().(driver.Valuer); ok { + val, err := valuer.Value() + if err == nil { + return val + } + // handle the error how you want + } + return nil +} ``` Benchmarks diff --git a/examples/custom.go b/examples/custom/custom.go similarity index 94% rename from examples/custom.go rename to examples/custom/custom.go index d7eb3b2..897b17f 100644 --- a/examples/custom.go +++ b/examples/custom/custom.go @@ -6,9 +6,10 @@ import ( "fmt" "reflect" - validator "gopkg.in/bluesuncorp/validator.v6" + "gopkg.in/bluesuncorp/validator.v6" ) +// DbBackedUser User struct type DbBackedUser struct { Name sql.NullString `validate:"required"` Age sql.NullInt64 `validate:"required"` diff --git a/examples/simple.go b/examples/simple/simple.go similarity index 100% rename from examples/simple.go rename to examples/simple/simple.go diff --git a/validator.go b/validator.go index 9fb4dcf..d8f35b6 100644 --- a/validator.go +++ b/validator.go @@ -157,14 +157,17 @@ func (v *Validate) RegisterValidation(key string, f Func) error { return nil } -// RegisterCustomTypeFunc registers types w/a custom type handler function. -func (v *Validate) RegisterCustomTypeFunc(f CustomTypeFunc, sampleTypeValues ...interface{}) { +// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types +func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { + if v.config.CustomTypeFuncs == nil { v.config.CustomTypeFuncs = map[reflect.Type]CustomTypeFunc{} } - for _, sample := range sampleTypeValues { - v.config.CustomTypeFuncs[reflect.TypeOf(sample)] = f + + for _, t := range types { + v.config.CustomTypeFuncs[reflect.TypeOf(t)] = fn } + v.config.hasCustomFuncs = true } diff --git a/validator_test.go b/validator_test.go index 37b6dee..4b93f92 100644 --- a/validator_test.go +++ b/validator_test.go @@ -1,14 +1,14 @@ package validator import ( + "database/sql" + "database/sql/driver" "errors" "fmt" "reflect" "testing" "time" - sql "database/sql/driver" - . "gopkg.in/bluesuncorp/assert.v1" ) @@ -126,7 +126,7 @@ type valuer struct { Name string } -func (v valuer) Value() (sql.Value, error) { +func (v valuer) Value() (driver.Value, error) { if v.Name == "errorme" { return nil, errors.New("some kind of error") @@ -178,7 +178,7 @@ type CustomMadeUpStruct struct { } func ValidateValuerType(field reflect.Value) interface{} { - if valuer, ok := field.Interface().(sql.Valuer); ok { + if valuer, ok := field.Interface().(driver.Valuer); ok { val, err := valuer.Value() if err != nil { // handle the error how you want @@ -191,10 +191,68 @@ func ValidateValuerType(field reflect.Value) interface{} { return nil } +func TestSQLValue2Validation(t *testing.T) { + + config := Config{ + TagName: "validate", + ValidationFuncs: BakedInValidators, + } + + validate := New(config) + validate.RegisterCustomTypeFunc(ValidateValuerType, valuer{}, (*driver.Valuer)(nil), sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) + validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) + validate.RegisterCustomTypeFunc(OverrideIntTypeForSomeReason, 1) + + val := valuer{ + Name: "", + } + + errs := validate.Field(val, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + val.Name = "Valid Name" + errs = validate.Field(val, "required") + Equal(t, errs, nil) + + val.Name = "errorme" + + PanicMatches(t, func() { errs = validate.Field(val, "required") }, "SQL Driver Valuer error: some kind of error") + + type myValuer valuer + + myVal := valuer{ + Name: "", + } + + errs = validate.Field(myVal, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "required") + + cust := MadeUpCustomType{ + FirstName: "Joey", + LastName: "Bloggs", + } + + c := CustomMadeUpStruct{MadeUp: cust, OverriddenInt: 2} + + errs = validate.Struct(c) + Equal(t, errs, nil) + + c.MadeUp.FirstName = "" + c.OverriddenInt = 1 + + errs = validate.Struct(c) + NotEqual(t, errs, nil) + Equal(t, len(errs), 2) + AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required") + AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt") +} + func TestSQLValueValidation(t *testing.T) { customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason From f8fd45620ae9124cd4d8d9e30aa62d8e763f871a Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sat, 1 Aug 2015 21:00:31 -0400 Subject: [PATCH 13/16] Update README.md update semaphore badge to production after merge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c40597..25233aa 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Package validator ================ [![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) -[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487383/badge.svg)](https://semaphoreci.com/joeybloggs/validator) +[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487374/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v6)](https://coveralls.io/r/bluesuncorp/validator?branch=v6) [![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v6?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v6) From 7e97fcf2c40ff3b5e96846d778ab696cd394f98a Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 3 Aug 2015 20:47:11 -0400 Subject: [PATCH 14/16] Add exists tag exists tag used to ensure that a Pointer, Interface or Invalid has a value, but won't interfere with any other validation. --- doc.go | 8 ++++++++ validator.go | 5 +++++ validator_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/doc.go b/doc.go index ba60177..db05710 100644 --- a/doc.go +++ b/doc.go @@ -102,6 +102,14 @@ Here is a list of the current built in validators: you know the struct will be valid, but need to verify it has been assigned. NOTE: only "required" and "omitempty" can be used on a struct itself. + exists + Is a special tag without a validation function attached. It is used when a field + is a Pointer, Interface or Invalid and you wish to validate that it exists. + Example: want to ensure a bool exists if you define the bool as a pointer and + use exists it will ensure there is a value; couldn't use required as it would + fail when the bool was false. exists will fail is the value is a Pointer, Interface + or Invalid and is nil. (Usage: exists) + omitempty Allows conditional validation, for example if a field is not set with a value (Determined by the "required" validator) then other validation diff --git a/validator.go b/validator.go index d8f35b6..d02ad10 100644 --- a/validator.go +++ b/validator.go @@ -29,6 +29,7 @@ const ( omitempty = "omitempty" skipValidationTag = "-" diveTag = "dive" + existsTag = "exists" fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" arrayIndexFieldName = "%s[%d]" mapIndexFieldName = "%s[%v]" @@ -418,6 +419,10 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. for _, cTag := range tags { + if cTag.tagVals[0][0] == existsTag { + continue + } + if cTag.tagVals[0][0] == diveTag { dive = true diveSubTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",") diff --git a/validator_test.go b/validator_test.go index 4b93f92..9bcd09e 100644 --- a/validator_test.go +++ b/validator_test.go @@ -3,6 +3,7 @@ package validator import ( "database/sql" "database/sql/driver" + "encoding/json" "errors" "fmt" "reflect" @@ -191,6 +192,36 @@ func ValidateValuerType(field reflect.Value) interface{} { return nil } +func TestExistsValidation(t *testing.T) { + + jsonText := "{ \"truthiness2\": true }" + + type Thing struct { + Truthiness *bool `json:"truthiness" validate:"exists,required"` + } + + var ting Thing + + err := json.Unmarshal([]byte(jsonText), &ting) + Equal(t, err, nil) + NotEqual(t, ting, nil) + Equal(t, ting.Truthiness, nil) + + errs := validate.Struct(ting) + NotEqual(t, errs, nil) + AssertError(t, errs, "Thing.Truthiness", "Truthiness", "exists") + + jsonText = "{ \"truthiness\": true }" + + err = json.Unmarshal([]byte(jsonText), &ting) + Equal(t, err, nil) + NotEqual(t, ting, nil) + Equal(t, ting.Truthiness, true) + + errs = validate.Struct(ting) + Equal(t, errs, nil) +} + func TestSQLValue2Validation(t *testing.T) { config := Config{ From 50f0798342ac4ab472332e871a54a5e5fbde16b1 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 3 Aug 2015 22:53:08 -0400 Subject: [PATCH 15/16] Run gofmt -s -w . on code code cleanup, only a few changes. --- validator.go | 2 +- validator_test.go | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/validator.go b/validator.go index d02ad10..709a1fc 100644 --- a/validator.go +++ b/validator.go @@ -384,7 +384,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. for _, t := range strings.Split(tag, tagSeparator) { if t == diveTag { - tags = append(tags, &tagCache{tagVals: [][]string{[]string{t}}}) + tags = append(tags, &tagCache{tagVals: [][]string{{t}}}) break } diff --git a/validator_test.go b/validator_test.go index 9bcd09e..5ca43f9 100644 --- a/validator_test.go +++ b/validator_test.go @@ -725,9 +725,9 @@ func TestInterfaceErrValidation(t *testing.T) { var errStructPtr2Array [][]*Inner - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, nil}) tmsp2 := &TestMultiDimensionalStructsPtr2{ Errs: errStructPtr2Array, @@ -807,7 +807,7 @@ func TestMapDiveValidation(t *testing.T) { Errs map[int]Inner `validate:"gt=0,dive"` } - mi := map[int]Inner{0: Inner{"ok"}, 3: Inner{""}, 4: Inner{"ok"}} + mi := map[int]Inner{0: {"ok"}, 3: {""}, 4: {"ok"}} ms := &TestMapStruct{ Errs: mi, @@ -843,7 +843,7 @@ func TestMapDiveValidation(t *testing.T) { Errs map[int]*Inner `validate:"gt=0,dive,required"` } - mip := map[int]*Inner{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}} + mip := map[int]*Inner{0: {"ok"}, 3: nil, 4: {"ok"}} msp := &TestMapStructPtr{ Errs: mip, @@ -858,7 +858,7 @@ func TestMapDiveValidation(t *testing.T) { Errs map[int]*Inner `validate:"gt=0,dive,omitempty,required"` } - mip2 := map[int]*Inner{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}} + mip2 := map[int]*Inner{0: {"ok"}, 3: nil, 4: {"ok"}} msp2 := &TestMapStructPtr2{ Errs: mip2, @@ -945,8 +945,8 @@ func TestArrayDiveValidation(t *testing.T) { var errStructArray [][]Inner - errStructArray = append(errStructArray, []Inner{Inner{"ok"}, Inner{""}, Inner{""}}) - errStructArray = append(errStructArray, []Inner{Inner{"ok"}, Inner{""}, Inner{""}}) + errStructArray = append(errStructArray, []Inner{{"ok"}, {""}, {""}}) + errStructArray = append(errStructArray, []Inner{{"ok"}, {""}, {""}}) tms := &TestMultiDimensionalStructs{ Errs: errStructArray, @@ -966,9 +966,9 @@ func TestArrayDiveValidation(t *testing.T) { var errStructPtrArray [][]*Inner - errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) + errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, {""}}) + errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, {""}}) + errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, nil}) tmsp := &TestMultiDimensionalStructsPtr{ Errs: errStructPtrArray, @@ -991,9 +991,9 @@ func TestArrayDiveValidation(t *testing.T) { var errStructPtr2Array [][]*Inner - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, nil}) tmsp2 := &TestMultiDimensionalStructsPtr2{ Errs: errStructPtr2Array, @@ -1015,9 +1015,9 @@ func TestArrayDiveValidation(t *testing.T) { var errStructPtr3Array [][]*Inner - errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}}) - errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil}) + errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, nil}) tmsp3 := &TestMultiDimensionalStructsPtr3{ Errs: errStructPtr3Array, From 844f5f46f721f5550873ee45ec96e84684952449 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 17 Aug 2015 08:27:13 -0400 Subject: [PATCH 16/16] Updated Panic Tests after updates to assertion library --- validator_test.go | 67 +++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/validator_test.go b/validator_test.go index 5ca43f9..c17167a 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4,7 +4,6 @@ import ( "database/sql" "database/sql/driver" "encoding/json" - "errors" "fmt" "reflect" "testing" @@ -130,7 +129,8 @@ type valuer struct { func (v valuer) Value() (driver.Value, error) { if v.Name == "errorme" { - return nil, errors.New("some kind of error") + panic("SQL Driver Valuer error: some kind of error") + // return nil, errors.New("some kind of error") } if len(v.Name) == 0 { @@ -248,7 +248,7 @@ func TestSQLValue2Validation(t *testing.T) { val.Name = "errorme" - PanicMatches(t, func() { errs = validate.Field(val, "required") }, "SQL Driver Valuer error: some kind of error") + PanicMatches(t, func() { validate.Field(val, "required") }, "SQL Driver Valuer error: some kind of error") type myValuer valuer @@ -3187,7 +3187,9 @@ func TestHsla(t *testing.T) { AssertError(t, errs, "", "", "hsla") i := 1 - PanicMatches(t, func() { validate.Field(i, "hsla") }, "interface conversion: interface is int, not string") + validate.Field(i, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsla") } func TestHsl(t *testing.T) { @@ -3221,7 +3223,9 @@ func TestHsl(t *testing.T) { AssertError(t, errs, "", "", "hsl") i := 1 - PanicMatches(t, func() { validate.Field(i, "hsl") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsl") } func TestRgba(t *testing.T) { @@ -3263,7 +3267,9 @@ func TestRgba(t *testing.T) { AssertError(t, errs, "", "", "rgba") i := 1 - PanicMatches(t, func() { validate.Field(i, "rgba") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgba") } func TestRgb(t *testing.T) { @@ -3301,7 +3307,9 @@ func TestRgb(t *testing.T) { AssertError(t, errs, "", "", "rgb") i := 1 - PanicMatches(t, func() { validate.Field(i, "rgb") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgb") } func TestEmail(t *testing.T) { @@ -3331,7 +3339,9 @@ func TestEmail(t *testing.T) { AssertError(t, errs, "", "", "email") i := true - PanicMatches(t, func() { validate.Field(i, "email") }, "interface conversion: interface is bool, not string") + errs = validate.Field(i, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "email") } func TestHexColor(t *testing.T) { @@ -3355,7 +3365,9 @@ func TestHexColor(t *testing.T) { AssertError(t, errs, "", "", "hexcolor") i := true - PanicMatches(t, func() { validate.Field(i, "hexcolor") }, "interface conversion: interface is bool, not string") + errs = validate.Field(i, "hexcolor") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hexcolor") } func TestHexadecimal(t *testing.T) { @@ -3370,7 +3382,9 @@ func TestHexadecimal(t *testing.T) { AssertError(t, errs, "", "", "hexadecimal") i := true - PanicMatches(t, func() { validate.Field(i, "hexadecimal") }, "interface conversion: interface is bool, not string") + errs = validate.Field(i, "hexadecimal") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hexadecimal") } func TestNumber(t *testing.T) { @@ -3415,7 +3429,9 @@ func TestNumber(t *testing.T) { AssertError(t, errs, "", "", "number") i := 1 - PanicMatches(t, func() { validate.Field(i, "number") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "number") } func TestNumeric(t *testing.T) { @@ -3455,7 +3471,9 @@ func TestNumeric(t *testing.T) { AssertError(t, errs, "", "", "numeric") i := 1 - PanicMatches(t, func() { validate.Field(i, "numeric") }, "interface conversion: interface is int, not string") + errs = validate.Field(i, "numeric") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "numeric") } func TestAlphaNumeric(t *testing.T) { @@ -3469,7 +3487,9 @@ func TestAlphaNumeric(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "alphanum") - PanicMatches(t, func() { validate.Field(1, "alphanum") }, "interface conversion: interface is int, not string") + errs = validate.Field(1, "alphanum") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "alphanum") } func TestAlpha(t *testing.T) { @@ -3481,10 +3501,11 @@ func TestAlpha(t *testing.T) { s = "abc1" errs = validate.Field(s, "alpha") NotEqual(t, errs, nil) - AssertError(t, errs, "", "", "alpha") - PanicMatches(t, func() { validate.Field(1, "alpha") }, "interface conversion: interface is int, not string") + errs = validate.Field(1, "alpha") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "alpha") } func TestStructStringValidation(t *testing.T) { @@ -3737,22 +3758,6 @@ func TestInvalidStruct(t *testing.T) { PanicMatches(t, func() { validate.Struct(s.Test) }, "value passed for validation is not a struct") } -func TestInvalidField(t *testing.T) { - s := &SubTest{ - Test: "1", - } - - PanicMatches(t, func() { validate.Field(s, "required") }, "Invalid field passed to traverseField") -} - -func TestInvalidTagField(t *testing.T) { - s := &SubTest{ - Test: "1", - } - - PanicMatches(t, func() { validate.Field(s.Test, "") }, fmt.Sprintf("Invalid validation tag on field %s", "")) -} - func TestInvalidValidatorFunction(t *testing.T) { s := &SubTest{ Test: "1",