From b0883e6ed80adf7309204896cf6b4a8802b1f951 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 8 Aug 2016 09:54:52 -0400 Subject: [PATCH] RC1 Release - Updated docs ( much more to come ) - v9 is much simpler to use. v8 vs v9 improvements ``` benchmark old ns/op new ns/op delta BenchmarkFieldSuccess-8 118 147 +24.58% BenchmarkFieldFailure-8 758 417 -44.99% BenchmarkFieldDiveSuccess-8 2471 876 -64.55% BenchmarkFieldDiveFailure-8 3172 1185 -62.64% BenchmarkFieldCustomTypeSuccess-8 300 321 +7.00% BenchmarkFieldCustomTypeFailure-8 775 416 -46.32% BenchmarkFieldOrTagSuccess-8 1122 1119 -0.27% BenchmarkFieldOrTagFailure-8 1167 715 -38.73% BenchmarkStructLevelValidationSuccess-8 548 399 -27.19% BenchmarkStructLevelValidationFailure-8 558 749 +34.23% BenchmarkStructSimpleCustomTypeSuccess-8 623 673 +8.03% BenchmarkStructSimpleCustomTypeFailure-8 1381 1056 -23.53% BenchmarkStructPartialSuccess-8 1036 789 -23.84% BenchmarkStructPartialFailure-8 1734 1105 -36.27% BenchmarkStructExceptSuccess-8 888 1212 +36.49% BenchmarkStructExceptFailure-8 1036 1004 -3.09% BenchmarkStructSimpleCrossFieldSuccess-8 773 656 -15.14% BenchmarkStructSimpleCrossFieldFailure-8 1487 968 -34.90% BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 1261 1000 -20.70% BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2055 1324 -35.57% BenchmarkStructSimpleSuccess-8 519 534 +2.89% BenchmarkStructSimpleFailure-8 1429 1039 -27.29% BenchmarkStructSimpleSuccessParallel-8 146 144 -1.37% BenchmarkStructSimpleFailureParallel-8 551 419 -23.96% BenchmarkStructComplexSuccess-8 3269 2678 -18.08% BenchmarkStructComplexFailure-8 8436 6342 -24.82% BenchmarkStructComplexSuccessParallel-8 1024 874 -14.65% BenchmarkStructComplexFailureParallel-8 3536 2875 -18.69% benchmark old allocs new allocs delta BenchmarkFieldSuccess-8 0 0 +0.00% BenchmarkFieldFailure-8 4 4 +0.00% BenchmarkFieldDiveSuccess-8 28 11 -60.71% BenchmarkFieldDiveFailure-8 32 16 -50.00% BenchmarkFieldCustomTypeSuccess-8 2 2 +0.00% BenchmarkFieldCustomTypeFailure-8 4 4 +0.00% BenchmarkFieldOrTagSuccess-8 1 1 +0.00% BenchmarkFieldOrTagFailure-8 6 5 -16.67% BenchmarkStructLevelValidationSuccess-8 5 2 -60.00% BenchmarkStructLevelValidationFailure-8 5 8 +60.00% BenchmarkStructSimpleCustomTypeSuccess-8 3 2 -33.33% BenchmarkStructSimpleCustomTypeFailure-8 9 9 +0.00% BenchmarkStructPartialSuccess-8 9 6 -33.33% BenchmarkStructPartialFailure-8 14 11 -21.43% BenchmarkStructExceptSuccess-8 7 12 +71.43% BenchmarkStructExceptFailure-8 9 10 +11.11% BenchmarkStructSimpleCrossFieldSuccess-8 4 3 -25.00% BenchmarkStructSimpleCrossFieldFailure-8 9 8 -11.11% BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 7 4 -42.86% BenchmarkStructSimpleCrossStructCrossFieldFailure-8 12 9 -25.00% BenchmarkStructSimpleSuccess-8 1 0 -100.00% BenchmarkStructSimpleFailure-8 9 9 +0.00% BenchmarkStructSimpleSuccessParallel-8 1 0 -100.00% BenchmarkStructSimpleFailureParallel-8 9 9 +0.00% BenchmarkStructComplexSuccess-8 15 8 -46.67% BenchmarkStructComplexFailure-8 60 53 -11.67% BenchmarkStructComplexSuccessParallel-8 15 8 -46.67% BenchmarkStructComplexFailureParallel-8 60 53 -11.67% benchmark old bytes new bytes delta BenchmarkFieldSuccess-8 0 0 +0.00% BenchmarkFieldFailure-8 432 192 -55.56% BenchmarkFieldDiveSuccess-8 464 201 -56.68% BenchmarkFieldDiveFailure-8 896 396 -55.80% BenchmarkFieldCustomTypeSuccess-8 32 32 +0.00% BenchmarkFieldCustomTypeFailure-8 432 192 -55.56% BenchmarkFieldOrTagSuccess-8 4 16 +300.00% BenchmarkFieldOrTagFailure-8 448 208 -53.57% BenchmarkStructLevelValidationSuccess-8 160 32 -80.00% BenchmarkStructLevelValidationFailure-8 160 288 +80.00% BenchmarkStructSimpleCustomTypeSuccess-8 36 32 -11.11% BenchmarkStructSimpleCustomTypeFailure-8 640 392 -38.75% BenchmarkStructPartialSuccess-8 272 256 -5.88% BenchmarkStructPartialFailure-8 730 464 -36.44% BenchmarkStructExceptSuccess-8 250 480 +92.00% BenchmarkStructExceptFailure-8 272 448 +64.71% BenchmarkStructSimpleCrossFieldSuccess-8 80 72 -10.00% BenchmarkStructSimpleCrossFieldFailure-8 536 288 -46.27% BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 112 80 -28.57% BenchmarkStructSimpleCrossStructCrossFieldFailure-8 576 304 -47.22% BenchmarkStructSimpleSuccess-8 4 0 -100.00% BenchmarkStructSimpleFailure-8 640 392 -38.75% BenchmarkStructSimpleSuccessParallel-8 4 0 -100.00% BenchmarkStructSimpleFailureParallel-8 640 392 -38.75% BenchmarkStructComplexSuccess-8 244 128 -47.54% BenchmarkStructComplexFailure-8 3609 2833 -21.50% BenchmarkStructComplexSuccessParallel-8 244 128 -47.54% BenchmarkStructComplexFailureParallel-8 3609 2833 -21.50% ``` --- README.md | 245 +++++++++++------- doc.go | 20 +- examples/custom/{custom.go => main.go} | 20 +- examples/simple/main.go | 101 ++++++++ examples/simple/simple.go | 155 ----------- .../struct-level/{struct_level.go => main.go} | 98 +++---- 6 files changed, 325 insertions(+), 314 deletions(-) rename examples/custom/{custom.go => main.go} (75%) create mode 100644 examples/simple/main.go delete mode 100644 examples/simple/simple.go rename examples/struct-level/{struct_level.go => main.go} (56%) diff --git a/README.md b/README.md index 4f52d21..53a712d 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/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/alpha-9.0.0-red.svg) +![Project status](https://img.shields.io/badge/RC1-9.0.0-yellow.svg) [![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/530054/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) @@ -77,7 +77,7 @@ type User struct { LastName string `validate:"required"` Age uint8 `validate:"gte=0,lte=130"` Email string `validate:"required,email"` - FavouriteColor string `validate:"hexcolor|rgb|rgba"` + FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla' Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... } @@ -89,16 +89,15 @@ type Address struct { Phone string `validate:"required"` } +// use a single instance of Validate, it caches struct info var validate *validator.Validate func main() { - config := &validator.Config{TagName: "validate"} - - validate = validator.New(config) + validate = validator.New() validateStruct() - validateField() + validateVariable() } func validateStruct() { @@ -114,24 +113,36 @@ func validateStruct() { LastName: "Smith", Age: 135, Email: "Badger.Smith@gmail.com", - FavouriteColor: "#000", + FavouriteColor: "#000-", Addresses: []*Address{address}, } // returns nil or ValidationErrors ( map[string]*FieldError ) - errs := validate.Struct(user) - - if errs != nil { + err := validate.Struct(user) + if err != nil { + + // this check is only needed when your code could produce + // an invalid value for validation such as interface with nil + // value most including myself do not usually have code like this. + if _, ok := err.(*validator.InvalidValidationError); ok { + fmt.Println(err) + return + } - 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.(validator.ValidationErrors)["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: + for _, err := range err.(validator.ValidationErrors) { + + fmt.Println(err.Namespace()) + fmt.Println(err.Field()) + fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or + fmt.Println(err.StructField()) // by passing alt name to ReportError like below + fmt.Println(err.Tag()) + fmt.Println(err.ActualTag()) + fmt.Println(err.Kind()) + fmt.Println(err.Type()) + fmt.Println(err.Value()) + fmt.Println(err.Param()) + fmt.Println() + } // from here you can create your own error messages in whatever language you wish return @@ -140,10 +151,11 @@ func validateStruct() { // save user to database } -func validateField() { +func validateVariable() { + myEmail := "joeybloggs.gmail.com" - errs := validate.Field(myEmail, "required,email") + errs := validate.Var(myEmail, "required,email") if errs != nil { fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag @@ -164,7 +176,7 @@ import ( "fmt" "reflect" - "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v9" ) // DbBackedUser User struct @@ -173,32 +185,38 @@ type DbBackedUser struct { Age sql.NullInt64 `validate:"required"` } -func main() { +// use a single instance of Validate, it caches struct info +var validate *validator.Validate - config := &validator.Config{TagName: "validate"} +func main() { - validate := validator.New(config) + validate = validator.New() // register all sql.Null* types to use the ValidateValuer CustomTypeFunc validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) + // build object for validation x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} - errs := validate.Struct(x) - if len(errs.(validator.ValidationErrors)) > 0 { - fmt.Printf("Errs:\n%+v\n", errs) + err := validate.Struct(x) + + if err != nil { + fmt.Printf("Err(s):\n%+v\n", err) } } // 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 } ``` @@ -209,7 +227,6 @@ package main import ( "fmt" - "reflect" "gopkg.in/go-playground/validator.v9" ) @@ -232,41 +249,19 @@ type Address struct { Phone string `validate:"required"` } +// use a single instance of Validate, it caches struct info var validate *validator.Validate func main() { - config := &validator.Config{TagName: "validate"} + validate = validator.New() - validate = validator.New(config) + // register validation for 'User' + // NOTE: only have to register a non-pointer type for 'User', validator + // interanlly dereferences during it's type checks. validate.RegisterStructValidation(UserStructLevelValidation, User{}) - validateStruct() -} - -// UserStructLevelValidation contains custom struct level validations that don't always -// make sense at the field validation level. For Example this function validates that either -// FirstName or LastName exist; could have done that with a custom field validation but then -// would have had to add it to both fields duplicating the logic + overhead, this way it's -// only validated once. -// -// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way -// hooks right into validator and you can combine with validation tags and still have a -// common error output format. -func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) { - - user := structLevel.CurrentStruct.Interface().(User) - - if len(user.FirstName) == 0 && len(user.LastName) == 0 { - structLevel.ReportError(reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname") - structLevel.ReportError(reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname") - } - - // plus can to more, even with different tag than "fnameorlname" -} - -func validateStruct() { - + // build 'User' info, normally posted data etc... address := &Address{ Street: "Eavesdown Docks", Planet: "Persphone", @@ -283,20 +278,32 @@ func validateStruct() { Addresses: []*Address{address}, } - // returns nil or ValidationErrors ( map[string]*FieldError ) - errs := validate.Struct(user) + // returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError ) + err := validate.Struct(user) + if err != nil { - if errs != nil { + // this check is only needed when your code could produce + // an invalid value for validation such as interface with nil + // value most including myself do not usually have code like this. + if _, ok := err.(*validator.InvalidValidationError); ok { + fmt.Println(err) + return + } - fmt.Println(errs) // output: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag - // Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag - err := errs.(validator.ValidationErrors)["User.FirstName"] - fmt.Println(err.Field) // output: FirstName - fmt.Println(err.Tag) // output: fnameorlname - fmt.Println(err.Kind) // output: string - fmt.Println(err.Type) // output: string - fmt.Println(err.Param) // output: - fmt.Println(err.Value) // output: + for _, err := range err.(validator.ValidationErrors) { + + fmt.Println(err.Namespace()) + fmt.Println(err.Field()) + fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or + fmt.Println(err.StructField()) // by passing alt name to ReportError like below + fmt.Println(err.Tag()) + fmt.Println(err.ActualTag()) + fmt.Println(err.Kind()) + fmt.Println(err.Type()) + fmt.Println(err.Value()) + fmt.Println(err.Param()) + fmt.Println() + } // from here you can create your own error messages in whatever language you wish return @@ -304,41 +311,81 @@ func validateStruct() { // save user to database } + +// UserStructLevelValidation contains custom struct level validations that don't always +// make sense at the field validation level. For Example this function validates that either +// FirstName or LastName exist; could have done that with a custom field validation but then +// would have had to add it to both fields duplicating the logic + overhead, this way it's +// only validated once. +// +// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way +// hooks right into validator and you can combine with validation tags and still have a +// common error output format. +func UserStructLevelValidation(sl validator.StructLevel) { + + user := sl.Current().Interface().(User) + + if len(user.FirstName) == 0 && len(user.LastName) == 0 { + sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "") + sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "") + } + + // plus can to more, even with different tag than "fnameorlname" +} ``` Benchmarks ------ -###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.5.3 darwin/amd64 +###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.6.3 darwin/amd64 ```go -PASS -BenchmarkFieldSuccess-8 20000000 118 ns/op 0 B/op 0 allocs/op -BenchmarkFieldFailure-8 2000000 758 ns/op 432 B/op 4 allocs/op -BenchmarkFieldDiveSuccess-8 500000 2471 ns/op 464 B/op 28 allocs/op -BenchmarkFieldDiveFailure-8 500000 3172 ns/op 896 B/op 32 allocs/op -BenchmarkFieldCustomTypeSuccess-8 5000000 300 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-8 2000000 775 ns/op 432 B/op 4 allocs/op -BenchmarkFieldOrTagSuccess-8 1000000 1122 ns/op 4 B/op 1 allocs/op -BenchmarkFieldOrTagFailure-8 1000000 1167 ns/op 448 B/op 6 allocs/op -BenchmarkStructLevelValidationSuccess-8 3000000 548 ns/op 160 B/op 5 allocs/op -BenchmarkStructLevelValidationFailure-8 3000000 558 ns/op 160 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-8 2000000 623 ns/op 36 B/op 3 allocs/op -BenchmarkStructSimpleCustomTypeFailure-8 1000000 1381 ns/op 640 B/op 9 allocs/op -BenchmarkStructPartialSuccess-8 1000000 1036 ns/op 272 B/op 9 allocs/op -BenchmarkStructPartialFailure-8 1000000 1734 ns/op 730 B/op 14 allocs/op -BenchmarkStructExceptSuccess-8 2000000 888 ns/op 250 B/op 7 allocs/op -BenchmarkStructExceptFailure-8 1000000 1036 ns/op 272 B/op 9 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-8 2000000 773 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossFieldFailure-8 1000000 1487 ns/op 536 B/op 9 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 1000000 1261 ns/op 112 B/op 7 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 2055 ns/op 576 B/op 12 allocs/op -BenchmarkStructSimpleSuccess-8 3000000 519 ns/op 4 B/op 1 allocs/op -BenchmarkStructSimpleFailure-8 1000000 1429 ns/op 640 B/op 9 allocs/op -BenchmarkStructSimpleSuccessParallel-8 10000000 146 ns/op 4 B/op 1 allocs/op -BenchmarkStructSimpleFailureParallel-8 2000000 551 ns/op 640 B/op 9 allocs/op -BenchmarkStructComplexSuccess-8 500000 3269 ns/op 244 B/op 15 allocs/op -BenchmarkStructComplexFailure-8 200000 8436 ns/op 3609 B/op 60 allocs/op -BenchmarkStructComplexSuccessParallel-8 1000000 1024 ns/op 244 B/op 15 allocs/op -BenchmarkStructComplexFailureParallel-8 500000 3536 ns/op 3609 B/op 60 allocs/op +BenchmarkFieldSuccess-8 10000000 147 ns/op 0 B/op 0 allocs/op +BenchmarkFieldSuccessParallel-8 30000000 42.5 ns/op 0 B/op 0 allocs/op +BenchmarkFieldFailure-8 3000000 417 ns/op 192 B/op 4 allocs/op +BenchmarkFieldFailureParallel-8 10000000 140 ns/op 192 B/op 4 allocs/op +BenchmarkFieldDiveSuccess-8 2000000 876 ns/op 201 B/op 11 allocs/op +BenchmarkFieldDiveSuccessParallel-8 5000000 277 ns/op 201 B/op 11 allocs/op +BenchmarkFieldDiveFailure-8 1000000 1185 ns/op 396 B/op 16 allocs/op +BenchmarkFieldDiveFailureParallel-8 3000000 402 ns/op 397 B/op 16 allocs/op +BenchmarkFieldCustomTypeSuccess-8 5000000 321 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeSuccessParallel-8 20000000 104 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-8 3000000 416 ns/op 192 B/op 4 allocs/op +BenchmarkFieldCustomTypeFailureParallel-8 10000000 150 ns/op 192 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-8 1000000 1119 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagSuccessParallel-8 3000000 462 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagFailure-8 2000000 715 ns/op 208 B/op 5 allocs/op +BenchmarkFieldOrTagFailureParallel-8 3000000 436 ns/op 208 B/op 5 allocs/op +BenchmarkStructLevelValidationSuccess-8 3000000 399 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationSuccessParallel-8 20000000 140 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationFailure-8 2000000 749 ns/op 288 B/op 8 allocs/op +BenchmarkStructLevelValidationFailureParallel-8 5000000 296 ns/op 288 B/op 8 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-8 2000000 673 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 213 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeFailure-8 1000000 1056 ns/op 392 B/op 9 allocs/op +BenchmarkStructSimpleCustomTypeFailureParallel-8 3000000 450 ns/op 408 B/op 10 allocs/op +BenchmarkStructPartialSuccess-8 2000000 789 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialSuccessParallel-8 5000000 307 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialFailure-8 1000000 1105 ns/op 464 B/op 11 allocs/op +BenchmarkStructPartialFailureParallel-8 5000000 493 ns/op 464 B/op 11 allocs/op +BenchmarkStructExceptSuccess-8 1000000 1212 ns/op 480 B/op 12 allocs/op +BenchmarkStructExceptSuccessParallel-8 10000000 282 ns/op 240 B/op 5 allocs/op +BenchmarkStructExceptFailure-8 1000000 1004 ns/op 448 B/op 10 allocs/op +BenchmarkStructExceptFailureParallel-8 5000000 452 ns/op 448 B/op 10 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-8 2000000 656 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldSuccessParallel-8 5000000 211 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldFailure-8 2000000 968 ns/op 288 B/op 8 allocs/op +BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 406 ns/op 288 B/op 8 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 1000000 1000 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 5000000 334 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 1324 ns/op 304 B/op 9 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 3000000 520 ns/op 304 B/op 9 allocs/op +BenchmarkStructSimpleSuccess-8 3000000 534 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleSuccessParallel-8 10000000 144 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleFailure-8 1000000 1039 ns/op 392 B/op 9 allocs/op +BenchmarkStructSimpleFailureParallel-8 5000000 419 ns/op 392 B/op 9 allocs/op +BenchmarkStructComplexSuccess-8 500000 2678 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexSuccessParallel-8 2000000 874 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexFailure-8 200000 6342 ns/op 2833 B/op 53 allocs/op +BenchmarkStructComplexFailureParallel-8 1000000 2875 ns/op 2833 B/op 53 allocs/op ``` Complimentary Software diff --git a/doc.go b/doc.go index e44356d..dfc2a47 100644 --- a/doc.go +++ b/doc.go @@ -12,8 +12,8 @@ Why should I handle my own errors? Many reasons. We built an internationalized application and needed to know the field, and what validation failed so we could provide a localized error. - if fieldErr.Field == "Name" { - switch fieldErr.ErrorTag + if fieldErr.Field() == "Name" { + switch fieldErr.Tag() case "required": return "Translated string based on field + error" default: @@ -34,18 +34,20 @@ where err is always != nil: http://stackoverflow.com/a/29138676/3158232 https://github.com/go-playground/validator/issues/134 -Validator only returns nil or ValidationErrors as type error; so, in your code -all you need to do is check if the error returned is not nil, and if it's not -type cast it to type ValidationErrors like so err.(validator.ValidationErrors). +Validator only InvalidValidationError for bad validation input, nil or +ValidationErrors as type error; so, in your code all you need to do is check +if the error returned is not nil, and if it's not check if error is +InvalidValidationError ( if necessary, most of the time it isn't ) type cast +it to type ValidationErrors like so err.(validator.ValidationErrors). -Custom Functions +Custom Validation Functions -Custom functions can be added. Example: +Custom Validation functions can be added. Example: // Structure - func customFunc(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + func customFunc(fl FielddLevel) bool { - if whatever { + if fl.Field().String() == "invalid" { return false } diff --git a/examples/custom/custom.go b/examples/custom/main.go similarity index 75% rename from examples/custom/custom.go rename to examples/custom/main.go index ee14bd2..724f57a 100644 --- a/examples/custom/custom.go +++ b/examples/custom/main.go @@ -6,7 +6,7 @@ import ( "fmt" "reflect" - "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v9" ) // DbBackedUser User struct @@ -15,31 +15,37 @@ type DbBackedUser struct { Age sql.NullInt64 `validate:"required"` } -func main() { +// use a single instance of Validate, it caches struct info +var validate *validator.Validate - config := &validator.Config{TagName: "validate"} +func main() { - validate := validator.New(config) + validate = validator.New() // register all sql.Null* types to use the ValidateValuer CustomTypeFunc validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) + // build object for validation x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} - errs := validate.Struct(x) - if errs != nil { - fmt.Printf("Errs:\n%+v\n", errs) + err := validate.Struct(x) + + if err != nil { + fmt.Printf("Err(s):\n%+v\n", err) } } // 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/examples/simple/main.go b/examples/simple/main.go new file mode 100644 index 0000000..9e3d106 --- /dev/null +++ b/examples/simple/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "fmt" + + "gopkg.in/go-playground/validator.v9" +) + +// 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:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla' + 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"` +} + +// use a single instance of Validate, it caches struct info +var validate *validator.Validate + +func main() { + + validate = validator.New() + + validateStruct() + validateVariable() +} + +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 ) + err := validate.Struct(user) + if err != nil { + + // this check is only needed when your code could produce + // an invalid value for validation such as interface with nil + // value most including myself do not usually have code like this. + if _, ok := err.(*validator.InvalidValidationError); ok { + fmt.Println(err) + return + } + + for _, err := range err.(validator.ValidationErrors) { + + fmt.Println(err.Namespace()) + fmt.Println(err.Field()) + fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or + fmt.Println(err.StructField()) // by passing alt name to ReportError like below + fmt.Println(err.Tag()) + fmt.Println(err.ActualTag()) + fmt.Println(err.Kind()) + fmt.Println(err.Type()) + fmt.Println(err.Value()) + fmt.Println(err.Param()) + fmt.Println() + } + + // from here you can create your own error messages in whatever language you wish + return + } + + // save user to database +} + +func validateVariable() { + + myEmail := "joeybloggs.gmail.com" + + errs := validate.Var(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 +} diff --git a/examples/simple/simple.go b/examples/simple/simple.go deleted file mode 100644 index d16cc83..0000000 --- a/examples/simple/simple.go +++ /dev/null @@ -1,155 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "reflect" - - sql "database/sql/driver" - - "gopkg.in/go-playground/validator.v8" -) - -// 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"} - - validate = validator.New(config) - - validateStruct() - validateField() -} - -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.(validator.ValidationErrors)["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 -} - -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 -} - -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 -} - -// 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() { - - config := &validator.Config{TagName: "validate"} - - validate2 = validator.New(config) - validate2.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) - - 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/examples/struct-level/struct_level.go b/examples/struct-level/main.go similarity index 56% rename from examples/struct-level/struct_level.go rename to examples/struct-level/main.go index 92526c9..87742fc 100644 --- a/examples/struct-level/struct_level.go +++ b/examples/struct-level/main.go @@ -2,9 +2,8 @@ package main import ( "fmt" - "reflect" - "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v9" ) // User contains user information @@ -25,41 +24,19 @@ type Address struct { Phone string `validate:"required"` } +// use a single instance of Validate, it caches struct info var validate *validator.Validate func main() { - config := &validator.Config{TagName: "validate"} + validate = validator.New() - validate = validator.New(config) + // register validation for 'User' + // NOTE: only have to register a non-pointer type for 'User', validator + // interanlly dereferences during it's type checks. validate.RegisterStructValidation(UserStructLevelValidation, User{}) - validateStruct() -} - -// UserStructLevelValidation contains custom struct level validations that don't always -// make sense at the field validation level. For Example this function validates that either -// FirstName or LastName exist; could have done that with a custom field validation but then -// would have had to add it to both fields duplicating the logic + overhead, this way it's -// only validated once. -// -// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way -// hooks right into validator and you can combine with validation tags and still have a -// common error output format. -func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) { - - user := structLevel.CurrentStruct.Interface().(User) - - if len(user.FirstName) == 0 && len(user.LastName) == 0 { - structLevel.ReportError(reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname") - structLevel.ReportError(reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname") - } - - // plus can to more, even with different tag than "fnameorlname" -} - -func validateStruct() { - + // build 'User' info, normally posted data etc... address := &Address{ Street: "Eavesdown Docks", Planet: "Persphone", @@ -76,20 +53,32 @@ func validateStruct() { Addresses: []*Address{address}, } - // returns nil or ValidationErrors ( map[string]*FieldError ) - errs := validate.Struct(user) - - if errs != nil { - - fmt.Println(errs) // output: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag - // Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag - err := errs.(validator.ValidationErrors)["User.FirstName"] - fmt.Println(err.Field) // output: FirstName - fmt.Println(err.Tag) // output: fnameorlname - fmt.Println(err.Kind) // output: string - fmt.Println(err.Type) // output: string - fmt.Println(err.Param) // output: - fmt.Println(err.Value) // output: + // returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError ) + err := validate.Struct(user) + if err != nil { + + // this check is only needed when your code could produce + // an invalid value for validation such as interface with nil + // value most including myself do not usually have code like this. + if _, ok := err.(*validator.InvalidValidationError); ok { + fmt.Println(err) + return + } + + for _, err := range err.(validator.ValidationErrors) { + + fmt.Println(err.Namespace()) + fmt.Println(err.Field()) + fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or + fmt.Println(err.StructField()) // by passing alt name to ReportError like below + fmt.Println(err.Tag()) + fmt.Println(err.ActualTag()) + fmt.Println(err.Kind()) + fmt.Println(err.Type()) + fmt.Println(err.Value()) + fmt.Println(err.Param()) + fmt.Println() + } // from here you can create your own error messages in whatever language you wish return @@ -97,3 +86,24 @@ func validateStruct() { // save user to database } + +// UserStructLevelValidation contains custom struct level validations that don't always +// make sense at the field validation level. For Example this function validates that either +// FirstName or LastName exist; could have done that with a custom field validation but then +// would have had to add it to both fields duplicating the logic + overhead, this way it's +// only validated once. +// +// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way +// hooks right into validator and you can combine with validation tags and still have a +// common error output format. +func UserStructLevelValidation(sl validator.StructLevel) { + + user := sl.Current().Interface().(User) + + if len(user.FirstName) == 0 && len(user.LastName) == 0 { + sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "") + sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "") + } + + // plus can to more, even with different tag than "fnameorlname" +}