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%
```
pull/256/head
joeybloggs 8 years ago
parent 852e5ff9f9
commit b0883e6ed8
  1. 245
      README.md
  2. 20
      doc.go
  3. 20
      examples/custom/main.go
  4. 101
      examples/simple/main.go
  5. 155
      examples/simple/simple.go
  6. 98
      examples/struct-level/main.go

@ -2,7 +2,7 @@ Package validator
================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">
[![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

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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"
}
Loading…
Cancel
Save