Merge branch 'i18n-custom-errors' into v9

pull/256/head v9.1.0
Dean Karn 8 years ago
commit 4b3eedc379
  1. 376
      README.md
  2. 16
      doc.go
  3. 72
      errors.go
  4. 129
      examples/translations/main.go
  5. 2
      struct_level.go
  6. 11
      translations.go
  7. 1301
      translations/en/en.go
  8. 580
      translations/en/en_test.go
  9. 16
      util.go
  10. 9
      validator.go
  11. 31
      validator_instance.go
  12. 147
      validator_test.go

@ -2,7 +2,7 @@ Package validator
================ ================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png"> <img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">
[![Join the chat at https://gitter.im/go-playground/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) [![Join the chat at https://gitter.im/go-playground/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/version-9.0.0-green.svg) ![Project status](https://img.shields.io/badge/version-9.1.0-green.svg)
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/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) [![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) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
@ -56,331 +56,65 @@ Please see http://godoc.org/gopkg.in/go-playground/validator.v9 for detailed usa
##### Examples: ##### Examples:
Struct & Field validation - [Simple](https://github.com/go-playground/validator/blob/v9/examples/simple/main.go)
```go - [Custom Field Types](https://github.com/go-playground/validator/blob/v9/examples/custom/main.go)
package main - [Struct Level](https://github.com/go-playground/validator/blob/v9/examples/struct-level/main.go)
- [Translations & Custom Errors](https://github.com/go-playground/validator/blob/v9/examples/translations/main.go)
import ( - [Gin upgrade and/or override validator](https://github.com/go-playground/validator/tree/v9/examples/gin-upgrading-overriding)
"fmt" - [wash - an example application putting it all together](https://github.com/bluesuncorp/wash)
"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
}
```
Custom Field Type
```go
package main
import (
"database/sql"
"database/sql/driver"
"fmt"
"reflect"
"gopkg.in/go-playground/validator.v9"
)
// DbBackedUser User struct
type DbBackedUser struct {
Name sql.NullString `validate:"required"`
Age sql.NullInt64 `validate:"required"`
}
// use a single instance of Validate, it caches struct info
var validate *validator.Validate
func main() {
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}}
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
}
```
Struct Level Validation
```go
package main
import (
"fmt"
"gopkg.in/go-playground/validator.v9"
)
// User contains user information
type User struct {
FirstName string `json:"fname"`
LastName string `json:"lname"`
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"`
}
// use a single instance of Validate, it caches struct info
var validate *validator.Validate
func main() {
validate = validator.New()
// 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{})
// build 'User' info, normally posted data etc...
address := &Address{
Street: "Eavesdown Docks",
Planet: "Persphone",
Phone: "none",
City: "Unknown",
}
user := &User{
FirstName: "",
LastName: "",
Age: 45,
Email: "Badger.Smith@gmail.com",
FavouriteColor: "#000",
Addresses: []*Address{address},
}
// 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
}
// 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 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.7 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.7 darwin/amd64
```go ```go
BenchmarkFieldSuccess-8 20000000 108 ns/op 0 B/op 0 allocs/op BenchmarkFieldSuccess-8 20000000 105 ns/op 0 B/op 0 allocs/op
BenchmarkFieldSuccessParallel-8 50000000 35.7 ns/op 0 B/op 0 allocs/op BenchmarkFieldSuccessParallel-8 50000000 35.1 ns/op 0 B/op 0 allocs/op
BenchmarkFieldFailure-8 5000000 320 ns/op 192 B/op 4 allocs/op BenchmarkFieldFailure-8 5000000 337 ns/op 208 B/op 4 allocs/op
BenchmarkFieldFailureParallel-8 20000000 113 ns/op 192 B/op 4 allocs/op BenchmarkFieldFailureParallel-8 20000000 120 ns/op 208 B/op 4 allocs/op
BenchmarkFieldDiveSuccess-8 2000000 726 ns/op 201 B/op 11 allocs/op BenchmarkFieldDiveSuccess-8 2000000 716 ns/op 201 B/op 11 allocs/op
BenchmarkFieldDiveSuccessParallel-8 10000000 263 ns/op 201 B/op 11 allocs/op BenchmarkFieldDiveSuccessParallel-8 10000000 253 ns/op 201 B/op 11 allocs/op
BenchmarkFieldDiveFailure-8 2000000 939 ns/op 396 B/op 16 allocs/op BenchmarkFieldDiveFailure-8 1000000 1060 ns/op 412 B/op 16 allocs/op
BenchmarkFieldDiveFailureParallel-8 5000000 382 ns/op 397 B/op 16 allocs/op BenchmarkFieldDiveFailureParallel-8 5000000 360 ns/op 413 B/op 16 allocs/op
BenchmarkFieldCustomTypeSuccess-8 5000000 268 ns/op 32 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccess-8 5000000 299 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeSuccessParallel-8 20000000 87.8 ns/op 32 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccessParallel-8 20000000 86.0 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-8 5000000 310 ns/op 192 B/op 4 allocs/op BenchmarkFieldCustomTypeFailure-8 5000000 341 ns/op 208 B/op 4 allocs/op
BenchmarkFieldCustomTypeFailureParallel-8 20000000 131 ns/op 192 B/op 4 allocs/op BenchmarkFieldCustomTypeFailureParallel-8 20000000 140 ns/op 208 B/op 4 allocs/op
BenchmarkFieldOrTagSuccess-8 2000000 889 ns/op 16 B/op 1 allocs/op BenchmarkFieldOrTagSuccess-8 2000000 893 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagSuccessParallel-8 5000000 418 ns/op 16 B/op 1 allocs/op BenchmarkFieldOrTagSuccessParallel-8 5000000 431 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagFailure-8 3000000 546 ns/op 208 B/op 5 allocs/op BenchmarkFieldOrTagFailure-8 2000000 563 ns/op 224 B/op 5 allocs/op
BenchmarkFieldOrTagFailureParallel-8 3000000 450 ns/op 208 B/op 5 allocs/op BenchmarkFieldOrTagFailureParallel-8 5000000 417 ns/op 224 B/op 5 allocs/op
BenchmarkStructLevelValidationSuccess-8 5000000 336 ns/op 32 B/op 2 allocs/op BenchmarkStructLevelValidationSuccess-8 5000000 339 ns/op 32 B/op 2 allocs/op
BenchmarkStructLevelValidationSuccessParallel-8 20000000 123 ns/op 32 B/op 2 allocs/op BenchmarkStructLevelValidationSuccessParallel-8 20000000 114 ns/op 32 B/op 2 allocs/op
BenchmarkStructLevelValidationFailure-8 2000000 611 ns/op 288 B/op 8 allocs/op BenchmarkStructLevelValidationFailure-8 2000000 630 ns/op 304 B/op 8 allocs/op
BenchmarkStructLevelValidationFailureParallel-8 5000000 298 ns/op 288 B/op 8 allocs/op BenchmarkStructLevelValidationFailureParallel-8 5000000 291 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-8 2000000 555 ns/op 32 B/op 2 allocs/op BenchmarkStructSimpleCustomTypeSuccess-8 3000000 540 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 197 ns/op 32 B/op 2 allocs/op BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 176 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeFailure-8 2000000 811 ns/op 392 B/op 9 allocs/op BenchmarkStructSimpleCustomTypeFailure-8 2000000 821 ns/op 424 B/op 9 allocs/op
BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 370 ns/op 408 B/op 10 allocs/op BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 336 ns/op 440 B/op 10 allocs/op
BenchmarkStructPartialSuccess-8 2000000 676 ns/op 256 B/op 6 allocs/op BenchmarkStructPartialSuccess-8 2000000 686 ns/op 256 B/op 6 allocs/op
BenchmarkStructPartialSuccessParallel-8 5000000 301 ns/op 256 B/op 6 allocs/op BenchmarkStructPartialSuccessParallel-8 5000000 282 ns/op 256 B/op 6 allocs/op
BenchmarkStructPartialFailure-8 1000000 1001 ns/op 464 B/op 11 allocs/op BenchmarkStructPartialFailure-8 2000000 931 ns/op 480 B/op 11 allocs/op
BenchmarkStructPartialFailureParallel-8 3000000 436 ns/op 464 B/op 11 allocs/op BenchmarkStructPartialFailureParallel-8 5000000 394 ns/op 480 B/op 11 allocs/op
BenchmarkStructExceptSuccess-8 1000000 1038 ns/op 480 B/op 12 allocs/op BenchmarkStructExceptSuccess-8 1000000 1017 ns/op 496 B/op 12 allocs/op
BenchmarkStructExceptSuccessParallel-8 10000000 281 ns/op 240 B/op 5 allocs/op BenchmarkStructExceptSuccessParallel-8 10000000 233 ns/op 240 B/op 5 allocs/op
BenchmarkStructExceptFailure-8 2000000 863 ns/op 448 B/op 10 allocs/op BenchmarkStructExceptFailure-8 2000000 864 ns/op 464 B/op 10 allocs/op
BenchmarkStructExceptFailureParallel-8 3000000 379 ns/op 448 B/op 10 allocs/op BenchmarkStructExceptFailureParallel-8 5000000 393 ns/op 464 B/op 10 allocs/op
BenchmarkStructSimpleCrossFieldSuccess-8 3000000 549 ns/op 72 B/op 3 allocs/op BenchmarkStructSimpleCrossFieldSuccess-8 3000000 552 ns/op 72 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 192 ns/op 72 B/op 3 allocs/op BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 202 ns/op 72 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldFailure-8 2000000 783 ns/op 288 B/op 8 allocs/op BenchmarkStructSimpleCrossFieldFailure-8 2000000 798 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 296 ns/op 288 B/op 8 allocs/op BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 356 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 837 ns/op 80 B/op 4 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 825 ns/op 80 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 5000000 284 ns/op 80 B/op 4 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 5000000 300 ns/op 80 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 1110 ns/op 304 B/op 9 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 1103 ns/op 320 B/op 9 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 3000000 416 ns/op 304 B/op 9 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 3000000 433 ns/op 320 B/op 9 allocs/op
BenchmarkStructSimpleSuccess-8 5000000 380 ns/op 0 B/op 0 allocs/op BenchmarkStructSimpleSuccess-8 5000000 360 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleSuccessParallel-8 20000000 114 ns/op 0 B/op 0 allocs/op BenchmarkStructSimpleSuccessParallel-8 20000000 110 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleFailure-8 2000000 760 ns/op 392 B/op 9 allocs/op BenchmarkStructSimpleFailure-8 2000000 783 ns/op 424 B/op 9 allocs/op
BenchmarkStructSimpleFailureParallel-8 5000000 353 ns/op 392 B/op 9 allocs/op BenchmarkStructSimpleFailureParallel-8 5000000 358 ns/op 424 B/op 9 allocs/op
BenchmarkStructComplexSuccess-8 1000000 2100 ns/op 128 B/op 8 allocs/op BenchmarkStructComplexSuccess-8 1000000 2120 ns/op 128 B/op 8 allocs/op
BenchmarkStructComplexSuccessParallel-8 2000000 662 ns/op 128 B/op 8 allocs/op BenchmarkStructComplexSuccessParallel-8 2000000 659 ns/op 128 B/op 8 allocs/op
BenchmarkStructComplexFailure-8 200000 5080 ns/op 2833 B/op 53 allocs/op BenchmarkStructComplexFailure-8 300000 5126 ns/op 3041 B/op 53 allocs/op
BenchmarkStructComplexFailureParallel-8 1000000 2159 ns/op 2833 B/op 53 allocs/op BenchmarkStructComplexFailureParallel-8 1000000 2261 ns/op 3041 B/op 53 allocs/op
``` ```
Complimentary Software Complimentary Software

@ -5,21 +5,7 @@ based on tags.
It can also handle Cross-Field and Cross-Struct validation for nested structs It can also handle Cross-Field and Cross-Struct validation for nested structs
and has the ability to dive into arrays and maps of any type. and has the ability to dive into arrays and maps of any type.
Why not a better error message? see more examples https://github.com/go-playground/validator/tree/v9/examples
Because this library intends for you to handle your own error messages.
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.Tag()
case "required":
return "Translated string based on field + error"
default:
return "Translated string based on field"
}
Validation Functions Return Type error Validation Functions Return Type error

@ -5,12 +5,16 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
"github.com/go-playground/universal-translator"
) )
const ( const (
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag" fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"
) )
type ValidationErrorsTranslations map[string]string
// InvalidValidationError describes an invalid argument passed to // InvalidValidationError describes an invalid argument passed to
// `Struct`, `StructExcept`, StructPartial` or `Field` // `Struct`, `StructExcept`, StructPartial` or `Field`
type InvalidValidationError struct { type InvalidValidationError struct {
@ -39,18 +43,40 @@ func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString("") buff := bytes.NewBufferString("")
var err *fieldError var fe *fieldError
for i := 0; i < len(ve); i++ { for i := 0; i < len(ve); i++ {
err = ve[i].(*fieldError) fe = ve[i].(*fieldError)
buff.WriteString(err.Error()) buff.WriteString(fe.Error())
buff.WriteString("\n") buff.WriteString("\n")
} }
return strings.TrimSpace(buff.String()) return strings.TrimSpace(buff.String())
} }
func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslations {
trans := make(ValidationErrorsTranslations)
var fe *fieldError
for i := 0; i < len(ve); i++ {
fe = ve[i].(*fieldError)
// // in case an Anonymous struct was used, ensure that the key
// // would be 'Username' instead of ".Username"
// if len(fe.ns) > 0 && fe.ns[:1] == "." {
// trans[fe.ns[1:]] = fe.Translate(ut)
// continue
// }
trans[fe.ns] = fe.Translate(ut)
}
return trans
}
// FieldError contains all functions to get error details // FieldError contains all functions to get error details
type FieldError interface { type FieldError interface {
@ -118,6 +144,13 @@ type FieldError interface {
// //
// // eg. time.Time's type is time.Time // // eg. time.Time's type is time.Time
Type() reflect.Type Type() reflect.Type
// returns the FieldError's translated error
// from the provided 'ut.Translator' and registered 'TranslationFunc'
//
// NOTE: is not registered translation can be found it returns the same
// as calling fe.Error()
Translate(ut ut.Translator) string
} }
// compile time interface checks // compile time interface checks
@ -128,6 +161,7 @@ var _ error = new(fieldError)
// with other properties that may be needed for error message creation // with other properties that may be needed for error message creation
// it complies with the FieldError interface // it complies with the FieldError interface
type fieldError struct { type fieldError struct {
v *Validate
tag string tag string
actualTag string actualTag string
ns string ns string
@ -166,8 +200,18 @@ func (fe *fieldError) StructNamespace() string {
// Field returns the fields name with the tag name taking precedence over the // Field returns the fields name with the tag name taking precedence over the
// fields actual name. // fields actual name.
func (fe *fieldError) Field() string { func (fe *fieldError) Field() string {
// return fe.field
return fe.ns[len(fe.ns)-int(fe.fieldLen):] return fe.ns[len(fe.ns)-int(fe.fieldLen):]
// // return fe.field
// fld := fe.ns[len(fe.ns)-int(fe.fieldLen):]
// log.Println("FLD:", fld)
// if len(fld) > 0 && fld[:1] == "." {
// return fld[1:]
// }
// return fld
} }
// returns the fields actual name from the struct, when able to determine. // returns the fields actual name from the struct, when able to determine.
@ -202,3 +246,23 @@ func (fe *fieldError) Type() reflect.Type {
func (fe *fieldError) Error() string { func (fe *fieldError) Error() string {
return fmt.Sprintf(fieldErrMsg, fe.ns, fe.Field(), fe.tag) return fmt.Sprintf(fieldErrMsg, fe.ns, fe.Field(), fe.tag)
} }
// Translate returns the FieldError's translated error
// from the provided 'ut.Translator' and registered 'TranslationFunc'
//
// NOTE: is not registered translation can be found it returns the same
// as calling fe.Error()
func (fe *fieldError) Translate(ut ut.Translator) string {
m, ok := fe.v.transTagFunc[ut]
if !ok {
return fe.Error()
}
fn, ok := m[fe.tag]
if !ok {
return fe.Error()
}
return fn(ut, fe)
}

@ -0,0 +1,129 @@
package main
import (
"fmt"
"github.com/go-playground/locales/en"
"github.com/go-playground/universal-translator"
"gopkg.in/go-playground/validator.v9"
en_translations "gopkg.in/go-playground/validator.v9/translations/en"
)
// 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 , it caches struct info
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
func main() {
// NOTE: ommitting allot of error checking for brevity
en := en.New()
uni = ut.New(en, en)
// this is usually know or extracted from http 'Accept-Language' header
// also see uni.FindTranslator(...)
trans, _ := uni.GetTranslator("en")
validate = validator.New()
en_translations.RegisterDefaultTranslations(validate, trans)
translateAll(trans)
translateIndividual(trans)
translateOverride(trans) // yep you can specify your own in whatever locale you want!
}
func translateAll(trans ut.Translator) {
type User struct {
Username string `validate:"required"`
Tagline string `validate:"required,lt=10"`
Tagline2 string `validate:"required,gt=1"`
}
user := User{
Username: "Joeybloggs",
Tagline: "This tagline is way too long.",
Tagline2: "1",
}
err := validate.Struct(user)
if err != nil {
// translate all error at once
errs := err.(validator.ValidationErrors)
// returns a map with key = namespace & value = translated error
// NOTICE: 2 errors are returned and you'll see something surprising
// translations are i18n aware!!!!
// eg. '10 characters' vs '1 character'
fmt.Println(errs.Translate(trans))
}
}
func translateIndividual(trans ut.Translator) {
type User struct {
Username string `validate:"required"`
}
var user User
err := validate.Struct(user)
if err != nil {
errs := err.(validator.ValidationErrors)
for _, e := range errs {
// can translate each error one at a time.
fmt.Println(e.Translate(trans))
}
}
}
func translateOverride(trans ut.Translator) {
validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required", fe.Field())
return t
})
type User struct {
Username string `validate:"required"`
}
var user User
err := validate.Struct(user)
if err != nil {
errs := err.(validator.ValidationErrors)
for _, e := range errs {
// can translate each error one at a time.
fmt.Println(e.Translate(trans))
}
}
}

@ -117,6 +117,7 @@ func (v *validate) ReportError(field interface{}, fieldName, structFieldName, ta
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v,
tag: tag, tag: tag,
actualTag: tag, actualTag: tag,
ns: v.str1, ns: v.str1,
@ -132,6 +133,7 @@ func (v *validate) ReportError(field interface{}, fieldName, structFieldName, ta
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v,
tag: tag, tag: tag,
actualTag: tag, actualTag: tag,
ns: v.str1, ns: v.str1,

@ -0,0 +1,11 @@
package validator
import "github.com/go-playground/universal-translator"
// TranslationFunc is the function type used to register or override
// custom translations
type TranslationFunc func(ut ut.Translator, fe FieldError) string
// RegisterTranslationsFunc allows for registering of translations
// for a 'ut.Translator' for use withing the 'TranslationFunc'
type RegisterTranslationsFunc func(ut ut.Translator) error

File diff suppressed because it is too large Load Diff

@ -0,0 +1,580 @@
package en
import (
"testing"
"time"
english "github.com/go-playground/locales/en"
"github.com/go-playground/universal-translator"
. "gopkg.in/go-playground/assert.v1"
"gopkg.in/go-playground/validator.v9"
)
func TestTranslations(t *testing.T) {
eng := english.New()
uni := ut.New(eng, eng)
trans, _ := uni.GetTranslator("en")
validate := validator.New()
err := RegisterDefaultTranslations(validate, trans)
Equal(t, err, nil)
type Inner struct {
EqCSFieldString string
NeCSFieldString string
GtCSFieldString string
GteCSFieldString string
LtCSFieldString string
LteCSFieldString string
}
type Test struct {
Inner Inner
RequiredString string `validate:"required"`
RequiredNumber int `validate:"required"`
RequiredMultiple []string `validate:"required"`
LenString string `validate:"len=1"`
LenNumber float64 `validate:"len=1113.00"`
LenMultiple []string `validate:"len=7"`
MinString string `validate:"min=1"`
MinNumber float64 `validate:"min=1113.00"`
MinMultiple []string `validate:"min=7"`
MaxString string `validate:"max=3"`
MaxNumber float64 `validate:"max=1113.00"`
MaxMultiple []string `validate:"max=7"`
EqString string `validate:"eq=3"`
EqNumber float64 `validate:"eq=2.33"`
EqMultiple []string `validate:"eq=7"`
NeString string `validate:"ne="`
NeNumber float64 `validate:"ne=0.00"`
NeMultiple []string `validate:"ne=0"`
LtString string `validate:"lt=3"`
LtNumber float64 `validate:"lt=5.56"`
LtMultiple []string `validate:"lt=2"`
LtTime time.Time `validate:"lt"`
LteString string `validate:"lte=3"`
LteNumber float64 `validate:"lte=5.56"`
LteMultiple []string `validate:"lte=2"`
LteTime time.Time `validate:"lte"`
GtString string `validate:"gt=3"`
GtNumber float64 `validate:"gt=5.56"`
GtMultiple []string `validate:"gt=2"`
GtTime time.Time `validate:"gt"`
GteString string `validate:"gte=3"`
GteNumber float64 `validate:"gte=5.56"`
GteMultiple []string `validate:"gte=2"`
GteTime time.Time `validate:"gte"`
EqFieldString string `validate:"eqfield=MaxString"`
EqCSFieldString string `validate:"eqcsfield=Inner.EqCSFieldString"`
NeCSFieldString string `validate:"necsfield=Inner.NeCSFieldString"`
GtCSFieldString string `validate:"gtcsfield=Inner.GtCSFieldString"`
GteCSFieldString string `validate:"gtecsfield=Inner.GteCSFieldString"`
LtCSFieldString string `validate:"ltcsfield=Inner.LtCSFieldString"`
LteCSFieldString string `validate:"ltecsfield=Inner.LteCSFieldString"`
NeFieldString string `validate:"nefield=EqFieldString"`
GtFieldString string `validate:"gtfield=MaxString"`
GteFieldString string `validate:"gtefield=MaxString"`
LtFieldString string `validate:"ltfield=MaxString"`
LteFieldString string `validate:"ltefield=MaxString"`
AlphaString string `validate:"alpha"`
AlphanumString string `validate:"alphanum"`
NumericString string `validate:"numeric"`
NumberString string `validate:"number"`
HexadecimalString string `validate:"hexadecimal"`
HexColorString string `validate:"hexcolor"`
RGBColorString string `validate:"rgb"`
RGBAColorString string `validate:"rgba"`
HSLColorString string `validate:"hsl"`
HSLAColorString string `validate:"hsla"`
Email string `validate:"email"`
URL string `validate:"url"`
URI string `validate:"uri"`
Base64 string `validate:"base64"`
Contains string `validate:"contains=purpose"`
ContainsAny string `validate:"containsany=!@#$"`
Excludes string `validate:"excludes=text"`
ExcludesAll string `validate:"excludesall=!@#$"`
ExcludesRune string `validate:"excludesrune=☻"`
ISBN string `validate:"isbn"`
ISBN10 string `validate:"isbn10"`
ISBN13 string `validate:"isbn13"`
UUID string `validate:"uuid"`
UUID3 string `validate:"uuid3"`
UUID4 string `validate:"uuid4"`
UUID5 string `validate:"uuid5"`
ASCII string `validate:"ascii"`
PrintableASCII string `validate:"printascii"`
MultiByte string `validate:"multibyte"`
DataURI string `validate:"datauri"`
Latitude string `validate:"latitude"`
Longitude string `validate:"longitude"`
SSN string `validate:"ssn"`
IP string `validate:"ip"`
IPv4 string `validate:"ipv4"`
IPv6 string `validate:"ipv6"`
CIDR string `validate:"cidr"`
CIDRv4 string `validate:"cidrv4"`
CIDRv6 string `validate:"cidrv6"`
TCPAddr string `validate:"tcp_addr"`
TCPAddrv4 string `validate:"tcp4_addr"`
TCPAddrv6 string `validate:"tcp6_addr"`
UDPAddr string `validate:"udp_addr"`
UDPAddrv4 string `validate:"udp4_addr"`
UDPAddrv6 string `validate:"udp6_addr"`
IPAddr string `validate:"ip_addr"`
IPAddrv4 string `validate:"ip4_addr"`
IPAddrv6 string `validate:"ip6_addr"`
UinxAddr string `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future
MAC string `validate:"mac"`
}
var test Test
test.Inner.EqCSFieldString = "1234"
test.Inner.GtCSFieldString = "1234"
test.Inner.GteCSFieldString = "1234"
test.MaxString = "1234"
test.MaxNumber = 2000
test.MaxMultiple = make([]string, 9)
test.LtString = "1234"
test.LtNumber = 6
test.LtMultiple = make([]string, 3)
test.LtTime = time.Now().Add(time.Hour * 24)
test.LteString = "1234"
test.LteNumber = 6
test.LteMultiple = make([]string, 3)
test.LteTime = time.Now().Add(time.Hour * 24)
test.LtFieldString = "12345"
test.LteFieldString = "12345"
test.LtCSFieldString = "1234"
test.LteCSFieldString = "1234"
test.AlphaString = "abc3"
test.AlphanumString = "abc3!"
test.NumericString = "12E.00"
test.NumberString = "12E"
test.Excludes = "this is some test text"
test.ExcludesAll = "This is Great!"
test.ExcludesRune = "Love it ☻"
test.ASCII = "カタカナ"
test.PrintableASCII = "カタカナ"
test.MultiByte = "1234feerf"
err = validate.Struct(test)
NotEqual(t, err, nil)
errs, ok := err.(validator.ValidationErrors)
Equal(t, ok, true)
tests := []struct {
ns string
expected string
}{
{
ns: "Test.MAC",
expected: "MAC must contain a valid MAC address",
},
{
ns: "Test.IPAddr",
expected: "IPAddr must be a resolvable IP address",
},
{
ns: "Test.IPAddrv4",
expected: "IPAddrv4 must be a resolvable IPv4 address",
},
{
ns: "Test.IPAddrv6",
expected: "IPAddrv6 must be a resolvable IPv6 address",
},
{
ns: "Test.UDPAddr",
expected: "UDPAddr must be a valid UDP address",
},
{
ns: "Test.UDPAddrv4",
expected: "UDPAddrv4 must be a valid IPv4 UDP address",
},
{
ns: "Test.UDPAddrv6",
expected: "UDPAddrv6 must be a valid IPv6 UDP address",
},
{
ns: "Test.TCPAddr",
expected: "TCPAddr must be a valid TCP address",
},
{
ns: "Test.TCPAddrv4",
expected: "TCPAddrv4 must be a valid IPv4 TCP address",
},
{
ns: "Test.TCPAddrv6",
expected: "TCPAddrv6 must be a valid IPv6 TCP address",
},
{
ns: "Test.CIDR",
expected: "CIDR must contain a valid CIDR notation",
},
{
ns: "Test.CIDRv4",
expected: "CIDRv4 must contain a valid CIDR notation for an IPv4 address",
},
{
ns: "Test.CIDRv6",
expected: "CIDRv6 must contain a valid CIDR notation for an IPv6 address",
},
{
ns: "Test.SSN",
expected: "SSN must be a valid SSN number",
},
{
ns: "Test.IP",
expected: "IP must be a valid IP address",
},
{
ns: "Test.IPv4",
expected: "IPv4 must be a valid IPv4 address",
},
{
ns: "Test.IPv6",
expected: "IPv6 must be a valid IPv6 address",
},
{
ns: "Test.DataURI",
expected: "DataURI must contain a valid Data URI",
},
{
ns: "Test.Latitude",
expected: "Latitude must contain valid latitude coordinates",
},
{
ns: "Test.Longitude",
expected: "Longitude must contain a valid longitude coordinates",
},
{
ns: "Test.MultiByte",
expected: "MultiByte must contain multibyte characters",
},
{
ns: "Test.ASCII",
expected: "ASCII must contain only ascii characters",
},
{
ns: "Test.PrintableASCII",
expected: "PrintableASCII must contain only printable ascii characters",
},
{
ns: "Test.UUID",
expected: "UUID must be a valid UUID",
},
{
ns: "Test.UUID3",
expected: "UUID3 must be a valid version 3 UUID",
},
{
ns: "Test.UUID4",
expected: "UUID4 must be a valid version 4 UUID",
},
{
ns: "Test.UUID5",
expected: "UUID5 must be a valid version 5 UUID",
},
{
ns: "Test.ISBN",
expected: "ISBN must be a valid ISBN number",
},
{
ns: "Test.ISBN10",
expected: "ISBN10 must be a valid ISBN-10 number",
},
{
ns: "Test.ISBN13",
expected: "ISBN13 must be a valid ISBN-13 number",
},
{
ns: "Test.Excludes",
expected: "Excludes cannot contain the text 'text'",
},
{
ns: "Test.ExcludesAll",
expected: "ExcludesAll cannot contain any of the following characters '!@#$'",
},
{
ns: "Test.ExcludesRune",
expected: "ExcludesRune cannot contain the following '☻'",
},
{
ns: "Test.ContainsAny",
expected: "ContainsAny must contain at least one of the following characters '!@#$'",
},
{
ns: "Test.Contains",
expected: "Contains must contain the text 'purpose'",
},
{
ns: "Test.Base64",
expected: "Base64 must be a valid Base64 string",
},
{
ns: "Test.Email",
expected: "Email must be a valid email address",
},
{
ns: "Test.URL",
expected: "URL must be a valid URL",
},
{
ns: "Test.URI",
expected: "URI must be a valid URI",
},
{
ns: "Test.RGBColorString",
expected: "RGBColorString must be a valid RGB color",
},
{
ns: "Test.RGBAColorString",
expected: "RGBAColorString must be a valid RGBA color",
},
{
ns: "Test.HSLColorString",
expected: "HSLColorString must be a valid HSL color",
},
{
ns: "Test.HSLAColorString",
expected: "HSLAColorString must be a valid HSLA color",
},
{
ns: "Test.HexadecimalString",
expected: "HexadecimalString must be a valid hexadecimal",
},
{
ns: "Test.HexColorString",
expected: "HexColorString must be a valid HEX color",
},
{
ns: "Test.NumberString",
expected: "NumberString must be a valid number",
},
{
ns: "Test.NumericString",
expected: "NumericString must be a valid numeric value",
},
{
ns: "Test.AlphanumString",
expected: "AlphanumString can only contain alphanumeric characters",
},
{
ns: "Test.AlphaString",
expected: "AlphaString can only contain alphabetic characters",
},
{
ns: "Test.LtFieldString",
expected: "LtFieldString must be less than MaxString",
},
{
ns: "Test.LteFieldString",
expected: "LteFieldString must be less than or equal to MaxString",
},
{
ns: "Test.GtFieldString",
expected: "GtFieldString must be greater than MaxString",
},
{
ns: "Test.GteFieldString",
expected: "GteFieldString must be greater than or equal to MaxString",
},
{
ns: "Test.NeFieldString",
expected: "NeFieldString cannot be equal to EqFieldString",
},
{
ns: "Test.LtCSFieldString",
expected: "LtCSFieldString must be less than Inner.LtCSFieldString",
},
{
ns: "Test.LteCSFieldString",
expected: "LteCSFieldString must be less than or equal to Inner.LteCSFieldString",
},
{
ns: "Test.GtCSFieldString",
expected: "GtCSFieldString must be greater than Inner.GtCSFieldString",
},
{
ns: "Test.GteCSFieldString",
expected: "GteCSFieldString must be greater than or equal to Inner.GteCSFieldString",
},
{
ns: "Test.NeCSFieldString",
expected: "NeCSFieldString cannot be equal to Inner.NeCSFieldString",
},
{
ns: "Test.EqCSFieldString",
expected: "EqCSFieldString must be equal to Inner.EqCSFieldString",
},
{
ns: "Test.EqFieldString",
expected: "EqFieldString must be equal to MaxString",
},
{
ns: "Test.GteString",
expected: "GteString must be at least 3 characters in length",
},
{
ns: "Test.GteNumber",
expected: "GteNumber must be 5.56 or greater",
},
{
ns: "Test.GteMultiple",
expected: "GteMultiple must contain at least 2 items",
},
{
ns: "Test.GteTime",
expected: "GteTime must be greater than or equal to the current Date & Time",
},
{
ns: "Test.GtString",
expected: "GtString must be greater than 3 characters in length",
},
{
ns: "Test.GtNumber",
expected: "GtNumber must be greater than 5.56",
},
{
ns: "Test.GtMultiple",
expected: "GtMultiple must contain more than 2 items",
},
{
ns: "Test.GtTime",
expected: "GtTime must be greater than the current Date & Time",
},
{
ns: "Test.LteString",
expected: "LteString must be at maximum 3 characters in length",
},
{
ns: "Test.LteNumber",
expected: "LteNumber must be 5.56 or less",
},
{
ns: "Test.LteMultiple",
expected: "LteMultiple must contain at maximum 2 items",
},
{
ns: "Test.LteTime",
expected: "LteTime must be less than or equal to the current Date & Time",
},
{
ns: "Test.LtString",
expected: "LtString must be less than 3 characters in length",
},
{
ns: "Test.LtNumber",
expected: "LtNumber must be less than 5.56",
},
{
ns: "Test.LtMultiple",
expected: "LtMultiple must contain less than 2 items",
},
{
ns: "Test.LtTime",
expected: "LtTime must be less than the current Date & Time",
},
{
ns: "Test.NeString",
expected: "NeString should not be equal to ",
},
{
ns: "Test.NeNumber",
expected: "NeNumber should not be equal to 0.00",
},
{
ns: "Test.NeMultiple",
expected: "NeMultiple should not be equal to 0",
},
{
ns: "Test.EqString",
expected: "EqString is not equal to 3",
},
{
ns: "Test.EqNumber",
expected: "EqNumber is not equal to 2.33",
},
{
ns: "Test.EqMultiple",
expected: "EqMultiple is not equal to 7",
},
{
ns: "Test.MaxString",
expected: "MaxString must be a maximum of 3 characters in length",
},
{
ns: "Test.MaxNumber",
expected: "MaxNumber must be 1,113.00 or less",
},
{
ns: "Test.MaxMultiple",
expected: "MaxMultiple must contain at maximum 7 items",
},
{
ns: "Test.MinString",
expected: "MinString must be at least 1 character in length",
},
{
ns: "Test.MinNumber",
expected: "MinNumber must be 1,113.00 or greater",
},
{
ns: "Test.MinMultiple",
expected: "MinMultiple must contain at least 7 items",
},
{
ns: "Test.LenString",
expected: "LenString must be 1 character in length",
},
{
ns: "Test.LenNumber",
expected: "LenNumber must be equal to 1,113.00",
},
{
ns: "Test.LenMultiple",
expected: "LenMultiple must contain 7 items",
},
{
ns: "Test.RequiredString",
expected: "RequiredString is a required field",
},
{
ns: "Test.RequiredNumber",
expected: "RequiredNumber is a required field",
},
{
ns: "Test.RequiredMultiple",
expected: "RequiredMultiple is a required field",
},
}
for _, tt := range tests {
var fe validator.FieldError
for _, e := range errs {
if tt.ns == e.Namespace() {
fe = e
break
}
}
NotEqual(t, fe, nil)
Equal(t, tt.expected, fe.Translate(trans))
}
}

@ -6,22 +6,6 @@ import (
"strings" "strings"
) )
// import (
// "reflect"
// "strconv"
// "strings"
// )
// const (
// blank = ""
// namespaceSeparator = "."
// leftBracket = "["
// rightBracket = "]"
// restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
// restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
// restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
// )
// extractTypeInternal gets the actual underlying type of field value. // extractTypeInternal gets the actual underlying type of field value.
// It will dive into pointers, customTypes and return you the // It will dive into pointers, customTypes and return you the
// underlying value and it's kind. // underlying value and it's kind.

@ -37,7 +37,7 @@ func (v *validate) validateStruct(parent reflect.Value, current reflect.Value, t
cs = v.v.extractStructCache(current, typ.Name()) cs = v.v.extractStructCache(current, typ.Name())
} }
if len(ns) == 0 { if len(ns) == 0 && len(cs.name) != 0 {
ns = append(ns, cs.name...) ns = append(ns, cs.name...)
ns = append(ns, '.') ns = append(ns, '.')
@ -113,6 +113,7 @@ func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v,
tag: ct.aliasTag, tag: ct.aliasTag,
actualTag: ct.tag, actualTag: ct.tag,
ns: v.str1, ns: v.str1,
@ -129,6 +130,7 @@ func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v,
tag: ct.aliasTag, tag: ct.aliasTag,
actualTag: ct.tag, actualTag: ct.tag,
ns: v.str1, ns: v.str1,
@ -308,7 +310,7 @@ OUTER:
v.misc = append(v.misc, '|') v.misc = append(v.misc, '|')
v.misc = append(v.misc, ct.tag...) v.misc = append(v.misc, ct.tag...)
if ct.next == nil { if ct.next == nil || ct.next.typeof != typeOr { // ct.typeof != typeOr
// if we get here, no valid 'or' value and no more tags // if we get here, no valid 'or' value and no more tags
v.str1 = string(append(ns, cf.altName...)) v.str1 = string(append(ns, cf.altName...))
@ -323,6 +325,7 @@ OUTER:
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v,
tag: ct.aliasTag, tag: ct.aliasTag,
actualTag: ct.actualAliasTag, actualTag: ct.actualAliasTag,
ns: v.str1, ns: v.str1,
@ -342,6 +345,7 @@ OUTER:
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v,
tag: tVal, tag: tVal,
actualTag: tVal, actualTag: tVal,
ns: v.str1, ns: v.str1,
@ -381,6 +385,7 @@ OUTER:
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v,
tag: ct.aliasTag, tag: ct.aliasTag,
actualTag: ct.tag, actualTag: ct.tag,
ns: v.str1, ns: v.str1,

@ -7,6 +7,8 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/go-playground/universal-translator"
) )
const ( const (
@ -53,6 +55,7 @@ type Validate struct {
customFuncs map[reflect.Type]CustomTypeFunc customFuncs map[reflect.Type]CustomTypeFunc
aliases map[string]string aliases map[string]string
validations map[string]Func validations map[string]Func
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
tagCache *tagCache tagCache *tagCache
structCache *structCache structCache *structCache
} }
@ -189,6 +192,27 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{
v.hasCustomFuncs = true v.hasCustomFuncs = true
} }
func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) {
if v.transTagFunc == nil {
v.transTagFunc = make(map[ut.Translator]map[string]TranslationFunc)
}
if err = registerFn(trans); err != nil {
return
}
m, ok := v.transTagFunc[trans]
if !ok {
m = make(map[string]TranslationFunc)
v.transTagFunc[trans] = m
}
m[tag] = translationFn
return
}
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
// //
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
@ -333,8 +357,13 @@ func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) {
for _, key := range fields { for _, key := range fields {
vd.misc = append(vd.misc[0:0], name...) vd.misc = vd.misc[0:0]
if len(name) > 0 {
vd.misc = append(vd.misc, name...)
vd.misc = append(vd.misc, '.') vd.misc = append(vd.misc, '.')
}
vd.misc = append(vd.misc, key...) vd.misc = append(vd.misc, key...)
vd.includeExclude[string(vd.misc)] = struct{}{} vd.includeExclude[string(vd.misc)] = struct{}{}
} }

@ -11,6 +11,11 @@ import (
"time" "time"
. "gopkg.in/go-playground/assert.v1" . "gopkg.in/go-playground/assert.v1"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/fr"
"github.com/go-playground/locales/nl"
"github.com/go-playground/universal-translator"
) )
// NOTES: // NOTES:
@ -6413,3 +6418,145 @@ func TestRequired(t *testing.T) {
NotEqual(t, err, nil) NotEqual(t, err, nil)
AssertError(t, err.(ValidationErrors), "Test.Value", "Test.Value", "Value", "Value", "required") AssertError(t, err.(ValidationErrors), "Test.Value", "Test.Value", "Value", "Value", "required")
} }
func TestTranslations(t *testing.T) {
en := en.New()
uni := ut.New(en, en, fr.New())
trans, _ := uni.GetTranslator("en")
fr, _ := uni.GetTranslator("fr")
validate := New()
err := validate.RegisterTranslation("required", trans,
func(ut ut.Translator) (err error) {
// using this stype because multiple translation may have to be added for the full translation
if err = ut.Add("required", "{0} is a required field", false); err != nil {
return
}
return
}, func(ut ut.Translator, fe FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field())
if err != nil {
fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError))
return fe.(*fieldError).Error()
}
return t
})
Equal(t, err, nil)
err = validate.RegisterTranslation("required", fr,
func(ut ut.Translator) (err error) {
// using this stype because multiple translation may have to be added for the full translation
if err = ut.Add("required", "{0} est un champ obligatoire", false); err != nil {
return
}
return
}, func(ut ut.Translator, fe FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field())
if err != nil {
fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError))
return fe.(*fieldError).Error()
}
return t
})
Equal(t, err, nil)
type Test struct {
Value interface{} `validate:"required"`
}
var test Test
err = validate.Struct(test)
NotEqual(t, err, nil)
errs := err.(ValidationErrors)
Equal(t, len(errs), 1)
fe := errs[0]
Equal(t, fe.Tag(), "required")
Equal(t, fe.Namespace(), "Test.Value")
Equal(t, fe.Translate(trans), fmt.Sprintf("%s is a required field", fe.Field()))
Equal(t, fe.Translate(fr), fmt.Sprintf("%s est un champ obligatoire", fe.Field()))
nl := nl.New()
uni2 := ut.New(nl, nl)
trans2, _ := uni2.GetTranslator("nl")
Equal(t, fe.Translate(trans2), "Key: 'Test.Value' Error:Field validation for 'Value' failed on the 'required' tag")
terrs := errs.Translate(trans)
Equal(t, len(terrs), 1)
v, ok := terrs["Test.Value"]
Equal(t, ok, true)
Equal(t, v, fmt.Sprintf("%s is a required field", fe.Field()))
terrs = errs.Translate(fr)
Equal(t, len(terrs), 1)
v, ok = terrs["Test.Value"]
Equal(t, ok, true)
Equal(t, v, fmt.Sprintf("%s est un champ obligatoire", fe.Field()))
type Test2 struct {
Value string `validate:"gt=1"`
}
var t2 Test2
err = validate.Struct(t2)
NotEqual(t, err, nil)
errs = err.(ValidationErrors)
Equal(t, len(errs), 1)
fe = errs[0]
Equal(t, fe.Tag(), "gt")
Equal(t, fe.Namespace(), "Test2.Value")
Equal(t, fe.Translate(trans), "Key: 'Test2.Value' Error:Field validation for 'Value' failed on the 'gt' tag")
}
func TestTranslationErrors(t *testing.T) {
en := en.New()
uni := ut.New(en, en, fr.New())
trans, _ := uni.GetTranslator("en")
trans.Add("required", "{0} is a required field", false) // using translator outside of validator also
validate := New()
err := validate.RegisterTranslation("required", trans,
func(ut ut.Translator) (err error) {
// using this stype because multiple translation may have to be added for the full translation
if err = ut.Add("required", "{0} is a required field", false); err != nil {
return
}
return
}, func(ut ut.Translator, fe FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field())
if err != nil {
fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError))
return fe.(*fieldError).Error()
}
return t
})
NotEqual(t, err, nil)
Equal(t, err.Error(), "error: conflicting key 'required' rule 'Unknown' with text '{0} is a required field', value being ignored")
}

Loading…
Cancel
Save