diff --git a/README.md b/README.md index a38b4ec..948ecec 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Package validator ================ [![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) [![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) @@ -333,54 +333,54 @@ 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 ```go -BenchmarkFieldSuccess-8 20000000 108 ns/op 0 B/op 0 allocs/op -BenchmarkFieldSuccessParallel-8 50000000 35.7 ns/op 0 B/op 0 allocs/op -BenchmarkFieldFailure-8 5000000 320 ns/op 192 B/op 4 allocs/op -BenchmarkFieldFailureParallel-8 20000000 113 ns/op 192 B/op 4 allocs/op -BenchmarkFieldDiveSuccess-8 2000000 726 ns/op 201 B/op 11 allocs/op -BenchmarkFieldDiveSuccessParallel-8 10000000 263 ns/op 201 B/op 11 allocs/op -BenchmarkFieldDiveFailure-8 2000000 939 ns/op 396 B/op 16 allocs/op -BenchmarkFieldDiveFailureParallel-8 5000000 382 ns/op 397 B/op 16 allocs/op -BenchmarkFieldCustomTypeSuccess-8 5000000 268 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeSuccessParallel-8 20000000 87.8 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-8 5000000 310 ns/op 192 B/op 4 allocs/op -BenchmarkFieldCustomTypeFailureParallel-8 20000000 131 ns/op 192 B/op 4 allocs/op -BenchmarkFieldOrTagSuccess-8 2000000 889 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagSuccessParallel-8 5000000 418 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagFailure-8 3000000 546 ns/op 208 B/op 5 allocs/op -BenchmarkFieldOrTagFailureParallel-8 3000000 450 ns/op 208 B/op 5 allocs/op -BenchmarkStructLevelValidationSuccess-8 5000000 336 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationSuccessParallel-8 20000000 123 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationFailure-8 2000000 611 ns/op 288 B/op 8 allocs/op -BenchmarkStructLevelValidationFailureParallel-8 5000000 298 ns/op 288 B/op 8 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-8 2000000 555 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 197 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeFailure-8 2000000 811 ns/op 392 B/op 9 allocs/op -BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 370 ns/op 408 B/op 10 allocs/op -BenchmarkStructPartialSuccess-8 2000000 676 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialSuccessParallel-8 5000000 301 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialFailure-8 1000000 1001 ns/op 464 B/op 11 allocs/op -BenchmarkStructPartialFailureParallel-8 3000000 436 ns/op 464 B/op 11 allocs/op -BenchmarkStructExceptSuccess-8 1000000 1038 ns/op 480 B/op 12 allocs/op -BenchmarkStructExceptSuccessParallel-8 10000000 281 ns/op 240 B/op 5 allocs/op -BenchmarkStructExceptFailure-8 2000000 863 ns/op 448 B/op 10 allocs/op -BenchmarkStructExceptFailureParallel-8 3000000 379 ns/op 448 B/op 10 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-8 3000000 549 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 192 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldFailure-8 2000000 783 ns/op 288 B/op 8 allocs/op -BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 296 ns/op 288 B/op 8 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 837 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 5000000 284 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 1110 ns/op 304 B/op 9 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 3000000 416 ns/op 304 B/op 9 allocs/op -BenchmarkStructSimpleSuccess-8 5000000 380 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleSuccessParallel-8 20000000 114 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleFailure-8 2000000 760 ns/op 392 B/op 9 allocs/op -BenchmarkStructSimpleFailureParallel-8 5000000 353 ns/op 392 B/op 9 allocs/op -BenchmarkStructComplexSuccess-8 1000000 2100 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexSuccessParallel-8 2000000 662 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexFailure-8 200000 5080 ns/op 2833 B/op 53 allocs/op -BenchmarkStructComplexFailureParallel-8 1000000 2159 ns/op 2833 B/op 53 allocs/op +BenchmarkFieldSuccess-8 20000000 105 ns/op 0 B/op 0 allocs/op +BenchmarkFieldSuccessParallel-8 50000000 35.1 ns/op 0 B/op 0 allocs/op +BenchmarkFieldFailure-8 5000000 337 ns/op 208 B/op 4 allocs/op +BenchmarkFieldFailureParallel-8 20000000 120 ns/op 208 B/op 4 allocs/op +BenchmarkFieldDiveSuccess-8 2000000 716 ns/op 201 B/op 11 allocs/op +BenchmarkFieldDiveSuccessParallel-8 10000000 253 ns/op 201 B/op 11 allocs/op +BenchmarkFieldDiveFailure-8 1000000 1060 ns/op 412 B/op 16 allocs/op +BenchmarkFieldDiveFailureParallel-8 5000000 360 ns/op 413 B/op 16 allocs/op +BenchmarkFieldCustomTypeSuccess-8 5000000 299 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeSuccessParallel-8 20000000 86.0 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-8 5000000 341 ns/op 208 B/op 4 allocs/op +BenchmarkFieldCustomTypeFailureParallel-8 20000000 140 ns/op 208 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-8 2000000 893 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagSuccessParallel-8 5000000 431 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagFailure-8 2000000 563 ns/op 224 B/op 5 allocs/op +BenchmarkFieldOrTagFailureParallel-8 5000000 417 ns/op 224 B/op 5 allocs/op +BenchmarkStructLevelValidationSuccess-8 5000000 339 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationSuccessParallel-8 20000000 114 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationFailure-8 2000000 630 ns/op 304 B/op 8 allocs/op +BenchmarkStructLevelValidationFailureParallel-8 5000000 291 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-8 3000000 540 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 176 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeFailure-8 2000000 821 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 336 ns/op 440 B/op 10 allocs/op +BenchmarkStructPartialSuccess-8 2000000 686 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialSuccessParallel-8 5000000 282 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialFailure-8 2000000 931 ns/op 480 B/op 11 allocs/op +BenchmarkStructPartialFailureParallel-8 5000000 394 ns/op 480 B/op 11 allocs/op +BenchmarkStructExceptSuccess-8 1000000 1017 ns/op 496 B/op 12 allocs/op +BenchmarkStructExceptSuccessParallel-8 10000000 233 ns/op 240 B/op 5 allocs/op +BenchmarkStructExceptFailure-8 2000000 864 ns/op 464 B/op 10 allocs/op +BenchmarkStructExceptFailureParallel-8 5000000 393 ns/op 464 B/op 10 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-8 3000000 552 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 202 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldFailure-8 2000000 798 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 356 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 825 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 5000000 300 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 1103 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 3000000 433 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleSuccess-8 5000000 360 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleSuccessParallel-8 20000000 110 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleFailure-8 2000000 783 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleFailureParallel-8 5000000 358 ns/op 424 B/op 9 allocs/op +BenchmarkStructComplexSuccess-8 1000000 2120 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexSuccessParallel-8 2000000 659 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexFailure-8 300000 5126 ns/op 3041 B/op 53 allocs/op +BenchmarkStructComplexFailureParallel-8 1000000 2261 ns/op 3041 B/op 53 allocs/op ``` Complimentary Software diff --git a/errors.go b/errors.go index 5b13259..7b6a556 100644 --- a/errors.go +++ b/errors.go @@ -63,6 +63,14 @@ func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslati 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) } @@ -192,8 +200,18 @@ func (fe *fieldError) StructNamespace() string { // Field returns the fields name with the tag name taking precedence over the // fields actual name. func (fe *fieldError) Field() string { - // return fe.field + 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. @@ -236,7 +254,7 @@ func (fe *fieldError) Error() string { // as calling fe.Error() func (fe *fieldError) Translate(ut ut.Translator) string { - m, ok := fe.v.transTagFunc[ut.Locale()] + m, ok := fe.v.transTagFunc[ut] if !ok { return fe.Error() } diff --git a/examples/translations/main.go b/examples/translations/main.go new file mode 100644 index 0000000..9e3d106 --- /dev/null +++ b/examples/translations/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/translations/en/en.go b/translations/en/en.go new file mode 100644 index 0000000..bc6b3ac --- /dev/null +++ b/translations/en/en.go @@ -0,0 +1,1301 @@ +package en + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "github.com/go-playground/locales" + "github.com/go-playground/universal-translator" + "gopkg.in/go-playground/validator.v9" +) + +// RegisterDefaultTranslations registers a set of default translations +// for all built in tag's in validator; you may add your own as desired. +func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (err error) { + + translations := []struct { + tag string + translation string + override bool + customRegisFunc validator.RegisterTranslationsFunc + customTransFunc validator.TranslationFunc + }{ + { + tag: "required", + translation: "{0} is a required field", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} must be {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} must be equal to {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} must contain {1}", false); err != nil { + return + } + if err = ut.AddCardinal("len-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + return + + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + var err error + var t string + + var digits uint64 + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err := strconv.ParseFloat(fe.Param(), 64) + if err != nil { + goto END + } + + switch fe.Kind() { + case reflect.String: + + var c string + + c, err = ut.C("len-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("len-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + c, err = ut.C("len-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("len-items", fe.Field(), c) + + default: + t, err = ut.T("len-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "min", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("min-string", "{0} must be at least {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} must be {1} or greater", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} must contain at least {1}", false); err != nil { + return + } + if err = ut.AddCardinal("min-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + return + + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + var err error + var t string + + var digits uint64 + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err := strconv.ParseFloat(fe.Param(), 64) + if err != nil { + goto END + } + + switch fe.Kind() { + case reflect.String: + + var c string + + c, err = ut.C("min-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("min-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + c, err = ut.C("min-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("min-items", fe.Field(), c) + + default: + t, err = ut.T("min-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "max", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("max-string", "{0} must be a maximum of {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} must be {1} or less", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} must contain at maximum {1}", false); err != nil { + return + } + if err = ut.AddCardinal("max-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + return + + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + var err error + var t string + + var digits uint64 + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err := strconv.ParseFloat(fe.Param(), 64) + if err != nil { + goto END + } + + switch fe.Kind() { + case reflect.String: + + var c string + + c, err = ut.C("max-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("max-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + c, err = ut.C("max-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("max-items", fe.Field(), c) + + default: + t, err = ut.T("max-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "eq", + translation: "{0} is not equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + fmt.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ne", + translation: "{0} should not be equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + fmt.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "lt", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("lt-string", "{0} must be less than {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} must be less than {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} must contain less than {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} must be less than the current Date & Time", false); err != nil { + return + } + + return + + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + var err error + var t string + var f64 float64 + var digits uint64 + + fn := func() (err error) { + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err = strconv.ParseFloat(fe.Param(), 64) + + return + } + + switch fe.Kind() { + case reflect.String: + + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("lt-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lt-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("lt-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lt-items", fe.Field(), c) + + case reflect.Struct: + if fe.Type() != reflect.TypeOf(time.Time{}) { + err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + } + + t, err = ut.T("lt-datetime", fe.Field()) + + default: + err = fn() + if err != nil { + goto END + } + + t, err = ut.T("lt-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "lte", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("lte-string", "{0} must be at maximum {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} must be {1} or less", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} must contain at maximum {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} must be less than or equal to the current Date & Time", false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + var err error + var t string + var f64 float64 + var digits uint64 + + fn := func() (err error) { + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err = strconv.ParseFloat(fe.Param(), 64) + + return + } + + switch fe.Kind() { + case reflect.String: + + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("lte-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lte-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("lte-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lte-items", fe.Field(), c) + + case reflect.Struct: + if fe.Type() != reflect.TypeOf(time.Time{}) { + err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + } + + t, err = ut.T("lte-datetime", fe.Field()) + + default: + err = fn() + if err != nil { + goto END + } + + t, err = ut.T("lte-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gt", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("gt-string", "{0} must be greater than {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} must be greater than {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} must contain more than {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} must be greater than the current Date & Time", false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + var err error + var t string + var f64 float64 + var digits uint64 + + fn := func() (err error) { + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err = strconv.ParseFloat(fe.Param(), 64) + + return + } + + switch fe.Kind() { + case reflect.String: + + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("gt-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gt-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("gt-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gt-items", fe.Field(), c) + + case reflect.Struct: + if fe.Type() != reflect.TypeOf(time.Time{}) { + err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + } + + t, err = ut.T("gt-datetime", fe.Field()) + + default: + err = fn() + if err != nil { + goto END + } + + t, err = ut.T("gt-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gte", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("gte-string", "{0} must be at least {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} must be {1} or greater", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} must contain at least {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} must be greater than or equal to the current Date & Time", false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + var err error + var t string + var f64 float64 + var digits uint64 + + fn := func() (err error) { + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err = strconv.ParseFloat(fe.Param(), 64) + + return + } + + switch fe.Kind() { + case reflect.String: + + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("gte-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gte-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("gte-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gte-items", fe.Field(), c) + + case reflect.Struct: + if fe.Type() != reflect.TypeOf(time.Time{}) { + err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) + } + + t, err = ut.T("gte-datetime", fe.Field()) + + default: + err = fn() + if err != nil { + goto END + } + + t, err = ut.T("gte-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "eqfield", + translation: "{0} must be equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "eqcsfield", + translation: "{0} must be equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "necsfield", + translation: "{0} cannot be equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtcsfield", + translation: "{0} must be greater than {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtecsfield", + translation: "{0} must be greater than or equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltcsfield", + translation: "{0} must be less than {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltecsfield", + translation: "{0} must be less than or equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "nefield", + translation: "{0} cannot be equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtfield", + translation: "{0} must be greater than {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtefield", + translation: "{0} must be greater than or equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltfield", + translation: "{0} must be less than {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltefield", + translation: "{0} must be less than or equal to {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "alpha", + translation: "{0} can only contain alphabetic characters", + override: false, + }, + { + tag: "alphanum", + translation: "{0} can only contain alphanumeric characters", + override: false, + }, + { + tag: "numeric", + translation: "{0} must be a valid numeric value", + override: false, + }, + { + tag: "number", + translation: "{0} must be a valid number", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} must be a valid hexadecimal", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} must be a valid HEX color", + override: false, + }, + { + tag: "rgb", + translation: "{0} must be a valid RGB color", + override: false, + }, + { + tag: "rgba", + translation: "{0} must be a valid RGBA color", + override: false, + }, + { + tag: "hsl", + translation: "{0} must be a valid HSL color", + override: false, + }, + { + tag: "hsla", + translation: "{0} must be a valid HSLA color", + override: false, + }, + { + tag: "email", + translation: "{0} must be a valid email address", + override: false, + }, + { + tag: "url", + translation: "{0} must be a valid URL", + override: false, + }, + { + tag: "uri", + translation: "{0} must be a valid URI", + override: false, + }, + { + tag: "base64", + translation: "{0} must be a valid Base64 string", + override: false, + }, + { + tag: "contains", + translation: "{0} must contain the text '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "containsany", + translation: "{0} must contain at least one of the following characters '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "excludes", + translation: "{0} cannot contain the text '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "excludesall", + translation: "{0} cannot contain any of the following characters '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "excludesrune", + translation: "{0} cannot contain the following '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "isbn", + translation: "{0} must be a valid ISBN number", + override: false, + }, + { + tag: "isbn10", + translation: "{0} must be a valid ISBN-10 number", + override: false, + }, + { + tag: "isbn13", + translation: "{0} must be a valid ISBN-13 number", + override: false, + }, + { + tag: "uuid", + translation: "{0} must be a valid UUID", + override: false, + }, + { + tag: "uuid3", + translation: "{0} must be a valid version 3 UUID", + override: false, + }, + { + tag: "uuid4", + translation: "{0} must be a valid version 4 UUID", + override: false, + }, + { + tag: "uuid5", + translation: "{0} must be a valid version 5 UUID", + override: false, + }, + { + tag: "ascii", + translation: "{0} must contain only ascii characters", + override: false, + }, + { + tag: "printascii", + translation: "{0} must contain only printable ascii characters", + override: false, + }, + { + tag: "multibyte", + translation: "{0} must contain multibyte characters", + override: false, + }, + { + tag: "datauri", + translation: "{0} must contain a valid Data URI", + override: false, + }, + { + tag: "latitude", + translation: "{0} must contain valid latitude coordinates", + override: false, + }, + { + tag: "longitude", + translation: "{0} must contain a valid longitude coordinates", + override: false, + }, + { + tag: "ssn", + translation: "{0} must be a valid SSN number", + override: false, + }, + { + tag: "ipv4", + translation: "{0} must be a valid IPv4 address", + override: false, + }, + { + tag: "ipv6", + translation: "{0} must be a valid IPv6 address", + override: false, + }, + { + tag: "ip", + translation: "{0} must be a valid IP address", + override: false, + }, + { + tag: "cidr", + translation: "{0} must contain a valid CIDR notation", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} must contain a valid CIDR notation for an IPv4 address", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} must contain a valid CIDR notation for an IPv6 address", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} must be a valid TCP address", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} must be a valid IPv4 TCP address", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} must be a valid IPv6 TCP address", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} must be a valid UDP address", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} must be a valid IPv4 UDP address", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} must be a valid IPv6 UDP address", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} must be a resolvable IP address", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} must be a resolvable IPv4 address", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} must be a resolvable IPv6 address", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} must be a resolvable UNIX address", + override: false, + }, + { + tag: "mac", + translation: "{0} must contain a valid MAC address", + override: false, + }, + } + + for _, t := range translations { + + if t.customTransFunc != nil && t.customRegisFunc != nil { + + err = v.RegisterTranslation(t.tag, trans, t.customRegisFunc, t.customTransFunc) + + } else if t.customTransFunc != nil && t.customRegisFunc == nil { + + err = v.RegisterTranslation(t.tag, trans, registrationFunc(t.tag, t.translation, t.override), t.customTransFunc) + + } else if t.customTransFunc == nil && t.customRegisFunc != nil { + + err = v.RegisterTranslation(t.tag, trans, t.customRegisFunc, translateFunc) + + } else { + err = v.RegisterTranslation(t.tag, trans, registrationFunc(t.tag, t.translation, t.override), translateFunc) + } + + if err != nil { + return + } + } + + return +} + +func registrationFunc(tag string, translation string, override bool) validator.RegisterTranslationsFunc { + + return func(ut ut.Translator) (err error) { + + if err = ut.Add(tag, translation, override); err != nil { + return + } + + return + + } + +} + +func translateFunc(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t +} diff --git a/translations/en/en_test.go b/translations/en/en_test.go new file mode 100644 index 0000000..05453fe --- /dev/null +++ b/translations/en/en_test.go @@ -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)) + } + +} diff --git a/util.go b/util.go index ee9874e..7711428 100644 --- a/util.go +++ b/util.go @@ -6,22 +6,6 @@ import ( "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. // It will dive into pointers, customTypes and return you the // underlying value and it's kind. diff --git a/validator.go b/validator.go index 960bd7b..6622b98 100644 --- a/validator.go +++ b/validator.go @@ -37,7 +37,7 @@ func (v *validate) validateStruct(parent reflect.Value, current reflect.Value, t 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, '.') @@ -310,7 +310,7 @@ OUTER: v.misc = append(v.misc, '|') 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 v.str1 = string(append(ns, cf.altName...)) diff --git a/validator_instance.go b/validator_instance.go index fd380b2..5d7ba3e 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -55,7 +55,7 @@ type Validate struct { customFuncs map[reflect.Type]CustomTypeFunc aliases map[string]string validations map[string]Func - transTagFunc map[string]map[string]TranslationFunc // map[]map[]TranslationFunc + transTagFunc map[ut.Translator]map[string]TranslationFunc // map[]map[]TranslationFunc tagCache *tagCache structCache *structCache } @@ -192,20 +192,20 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ v.hasCustomFuncs = true } -func (v *Validate) RegisterTranslation(tag string, ut ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) { +func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) { if v.transTagFunc == nil { - v.transTagFunc = make(map[string]map[string]TranslationFunc) + v.transTagFunc = make(map[ut.Translator]map[string]TranslationFunc) } - if err = registerFn(ut); err != nil { + if err = registerFn(trans); err != nil { return } - m, ok := v.transTagFunc[ut.Locale()] + m, ok := v.transTagFunc[trans] if !ok { m = make(map[string]TranslationFunc) - v.transTagFunc[ut.Locale()] = m + v.transTagFunc[trans] = m } m[tag] = translationFn @@ -357,8 +357,13 @@ func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) { for _, key := range fields { - vd.misc = append(vd.misc[0:0], name...) - vd.misc = append(vd.misc, '.') + 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, key...) vd.includeExclude[string(vd.misc)] = struct{}{} }