diff --git a/.travis.yml b/.travis.yml index b88bb9f..f6484f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: - - 1.13.4 + - 1.13.7 - tip matrix: allow_failures: @@ -25,5 +25,5 @@ script: - go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./... after_success: | - [ $TRAVIS_GO_VERSION = 1.13.4 ] && + [ $TRAVIS_GO_VERSION = 1.13.7 ] && goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN \ No newline at end of file diff --git a/README.md b/README.md index daf28f1..175de89 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ 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-10.1.0-green.svg) +![Project status](https://img.shields.io/badge/version-10.2.0-green.svg) [![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) -[![GoDoc](https://godoc.org/github.com/go-playground/validator?status.svg)](https://godoc.org/github.com/go-playground/validator) +[![GoDoc](https://godoc.org/github.com/go-playground/validator?status.svg)](https://pkg.go.dev/github.com/go-playground/validator/v10) ![License](https://img.shields.io/dub/l/vibe-d.svg) Package validator implements value validations for structs and individual fields based on tags. It has the following **unique** features: -- Cross Field and Cross Struct validations by using validation tags or custom validators. +- Cross Field and Cross Struct validations by using validation tags or custom validators. - Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated. -- Ability to dive into both map keys and values for validation +- Ability to dive into both map keys and values for validation - Handles type interface by determining it's underlying type prior to validation. - Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29) - Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs @@ -64,6 +64,145 @@ Please see https://godoc.org/github.com/go-playground/validator for detailed usa - [Gin upgrade and/or override validator](https://github.com/go-playground/validator/tree/v9/_examples/gin-upgrading-overriding) - [wash - an example application putting it all together](https://github.com/bluesuncorp/wash) +Baked-in Validations +------ + +### Fields: + +| Tag | Description | +| - | - | +| eqcsfield | Field Equals Another Field (relative)| +| eqfield | Field Equals Another Field | +| fieldcontains | NOT DOCUMENTED IN doc.go | +| fieldexcludes | NOT DOCUMENTED IN doc.go | +| gtcsfield | Field Greater Than Another Relative Field | +| gtecsfield | Field Greater Than or Equal To Another Relative Field | +| gtefield | Field Greater Than or Equal To Another Field | +| gtfield | Field Greater Than Another Field | +| ltcsfield | Less Than Another Relative Field | +| ltecsfield | Less Than or Equal To Another Relative Field | +| ltefield | Less Than or Equal To Another Field | +| ltfield | Less Than Another Field | +| necsfield | Field Does Not Equal Another Field (relative) | +| nefield | Field Does Not Equal Another Field | + +### Network: + +| Tag | Description | +| - | - | +| cidr | Classless Inter-Domain Routing CIDR | +| cidrv4 | Classless Inter-Domain Routing CIDRv4 | +| cidrv6 | Classless Inter-Domain Routing CIDRv6 | +| datauri | Data URL | +| fqdn | Full Qualified Domain Name (FQDN) | +| hostname | Hostname RFC 952 | +| hostname_port | HostPort | +| hostname_rfc1123 | Hostname RFC 1123 | +| ip | Internet Protocol Address IP | +| ip4_addr | Internet Protocol Address IPv4 | +| ip6_addr |Internet Protocol Address IPv6 | +| ip_addr | Internet Protocol Address IP | +| ipv4 | Internet Protocol Address IPv4 | +| ipv6 | Internet Protocol Address IPv6 | +| mac | Media Access Control Address MAC | +| tcp4_addr | Transmission Control Protocol Address TCPv4 | +| tcp6_addr | Transmission Control Protocol Address TCPv6 | +| tcp_addr | Transmission Control Protocol Address TCP | +| udp4_addr | User Datagram Protocol Address UDPv4 | +| udp6_addr | User Datagram Protocol Address UDPv6 | +| udp_addr | User Datagram Protocol Address UDP | +| unix_addr | Unix domain socket end point Address | +| uri | URI String | +| url | URL String | +| url_encoded | URL Encoded | +| urn_rfc2141 | Urn RFC 2141 String | + +### Strings: + +| Tag | Description | +| - | - | +| alpha | Alpha Only | +| alphanum | Alphanumeric | +| alphanumunicode | Alphanumeric Unicode | +| alphaunicode | Alpha Unicode | +| ascii | ASCII | +| contains | Contains | +| containsany | Contains Any | +| containsrune | Contains Rune | +| lowercase | Lowercase | +| multibyte | Multi-Byte Characters | +| number | NOT DOCUMENTED IN doc.go | +| numeric | Numeric | +| printascii | Printable ASCII | +| startswith | Starts With | +| uppercase | Uppercase | + +### Format: +| Tag | Description | +| - | - | +| base64 | Base64 String | +| base64url | Base64URL String | +| btc_addr | Bitcoin Address | +| btc_addr_bech32 | Bitcoin Bech32 Address (segwit) | +| datetime | Datetime | +| email | E-mail String +| eth_addr | Ethereum Address | +| hexadecimal | Hexadecimal String | +| hexcolor | Hexcolor String | +| hsl | HSL String | +| hsla | HSLA String | +| html | HTML Tags | +| html_encoded | HTML Encoded | +| isbn | International Standard Book Number | +| isbn10 | International Standard Book Number 10 | +| isbn13 | International Standard Book Number 13 | +| json | JSON | +| latitude | Latitude | +| longitude | Longitude | +| rgb | RGB String | +| rgba | RGBA String | +| ssn | Social Security Number SSN | +| uuid | Universally Unique Identifier UUID | +| uuid3 | Universally Unique Identifier UUID v3 | +| uuid3_rfc4122 | Universally Unique Identifier UUID v3 RFC4122 | +| uuid4 | Universally Unique Identifier UUID v4 | +| uuid4_rfc4122 | Universally Unique Identifier UUID v4 RFC4122 | +| uuid5 | Universally Unique Identifier UUID v5 | +| uuid5_rfc4122 | Universally Unique Identifier UUID v5 RFC4122 | +| uuid_rfc4122 | Universally Unique Identifier UUID RFC4122 | + +### Comparisons: +| Tag | Description | +| - | - | +| eq | Equals | +| gt | Greater than| +| gte |Greater than or equal | +| lt | Less Than | +| lte | Less Than or Equal | +| ne | Not Equal | + +### Other: +| Tag | Description | +| - | - | +| dir | Directory | +| e164 | NOT DOCUMENTED IN doc.go | +| endswith | Ends With | +| excludes | Excludes | +| excludesall | Excludes All | +| excludesrune | Excludes Rune | +| file | File path | +| isdefault | Is Default | +| len | Length | +| max | Maximum | +| min | Minimum | +| oneof | One Of | +| required | Required | +| required_with | Required With | +| required_with_all | Required With All | +| required_without | Required Without | +| required_without_all | Required Without All | +| unique | Unique | + Benchmarks ------ ###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64 diff --git a/baked_in.go b/baked_in.go index 43e4e10..36e8057 100644 --- a/baked_in.go +++ b/baked_in.go @@ -120,6 +120,8 @@ var ( "excludesrune": excludesRune, "startswith": startsWith, "endswith": endsWith, + "startsnotwith": startsNotWith, + "endsnotwith": endsNotWith, "isbn": isISBN, "isbn10": isISBN10, "isbn13": isISBN13, @@ -690,6 +692,16 @@ func endsWith(fl FieldLevel) bool { return strings.HasSuffix(fl.Field().String(), fl.Param()) } +// StartsNotWith is the validation function for validating that the field's value does not start with the text specified within the param. +func startsNotWith(fl FieldLevel) bool { + return !startsWith(fl) +} + +// EndsNotWith is the validation function for validating that the field's value does not end with the text specified within the param. +func endsNotWith(fl FieldLevel) bool { + return !endsWith(fl) +} + // FieldContains is the validation function for validating if the current field's value contains the field specified by the param's value. func fieldContains(fl FieldLevel) bool { field := fl.Field() diff --git a/doc.go b/doc.go index 291c629..4aba75f 100644 --- a/doc.go +++ b/doc.go @@ -620,6 +620,13 @@ This validates that a string value contains unicode alphanumeric characters only Usage: alphanumunicode +Number + +This validates that a string value contains number values only. +For integers or float it returns true. + + Usage: number + Numeric This validates that a string value contains a basic numeric value. @@ -814,6 +821,18 @@ This validates that a string value ends with the supplied string value Usage: endswith=goodbye +Does Not Start With + +This validates that a string value does not start with the supplied string value + + Usage: startsnotwith=hello + +Does Not End With + +This validates that a string value does not end with the supplied string value + + Usage: endsnotwith=goodbye + International Standard Book Number This validates that a string value contains a valid isbn10 or isbn13 value. diff --git a/errors.go b/errors.go index 46c24c9..86420b9 100644 --- a/errors.go +++ b/errors.go @@ -99,7 +99,7 @@ type FieldError interface { ActualTag() string // returns the namespace for the field error, with the tag - // name taking precedence over the fields actual name. + // name taking precedence over the field's actual name. // // eg. JSON name "User.fname" // @@ -109,29 +109,29 @@ type FieldError interface { // using validate.Field(...) as there is no way to extract it's name Namespace() string - // returns the namespace for the field error, with the fields + // returns the namespace for the field error, with the field's // actual name. // // eq. "User.FirstName" see Namespace for comparison // // NOTE: this field can be blank when validating a single primitive field - // using validate.Field(...) as there is no way to extract it's name + // using validate.Field(...) as there is no way to extract its name StructNamespace() string // returns the fields name with the tag name taking precedence over the - // fields actual name. + // field's actual name. // // eq. JSON name "fname" // see StructField for comparison Field() string - // returns the fields actual name from the struct, when able to determine. + // returns the field's actual name from the struct, when able to determine. // // eq. "FirstName" // see Field for comparison StructField() string - // returns the actual fields value in case needed for creating the error + // returns the actual field's value in case needed for creating the error // message Value() interface{} @@ -190,19 +190,19 @@ func (fe *fieldError) ActualTag() string { } // Namespace returns the namespace for the field error, with the tag -// name taking precedence over the fields actual name. +// name taking precedence over the field's actual name. func (fe *fieldError) Namespace() string { return fe.ns } -// StructNamespace returns the namespace for the field error, with the fields +// StructNamespace returns the namespace for the field error, with the field's // actual name. func (fe *fieldError) StructNamespace() string { return fe.structNs } -// Field returns the fields name with the tag name taking precedence over the -// fields actual name. +// Field returns the field's name with the tag name taking precedence over the +// field's actual name. func (fe *fieldError) Field() string { return fe.ns[len(fe.ns)-int(fe.fieldLen):] @@ -218,13 +218,13 @@ func (fe *fieldError) Field() string { // return fld } -// returns the fields actual name from the struct, when able to determine. +// returns the field's actual name from the struct, when able to determine. func (fe *fieldError) StructField() string { // return fe.structField return fe.structNs[len(fe.structNs)-int(fe.structfieldLen):] } -// Value returns the actual fields value in case needed for creating the error +// Value returns the actual field's value in case needed for creating the error // message func (fe *fieldError) Value() interface{} { return fe.value diff --git a/regexes.go b/regexes.go index 94a800c..188d1ad 100644 --- a/regexes.go +++ b/regexes.go @@ -36,7 +36,7 @@ const ( latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" sSNRegexString = `^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$` - hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952 + hostnameRegexStringRFC952 = `^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952 hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62})(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.') btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address diff --git a/translations/es/es.go b/translations/es/es.go new file mode 100644 index 0000000..2b87f10 --- /dev/null +++ b/translations/es/es.go @@ -0,0 +1,1375 @@ +package es + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "github.com/go-playground/locales" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" +) + +// 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} es un campo requerido", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} debe tener {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} debe ser igual a {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} debe contener {1}", false); err != nil { + return + } + if err = ut.AddCardinal("len-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-items-item", "{0} elementos", 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 + var kind reflect.Kind + + 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 + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch 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} debe tener al menos {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} debe ser {1} o más", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} debe contener al menos {1}", false); err != nil { + return + } + if err = ut.AddCardinal("min-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-items-item", "{0} elementos", 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 + var kind reflect.Kind + + 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 + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch 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} debe tener un máximo de {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} debe ser {1} o menos", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} debe contener como máximo {1}", false); err != nil { + return + } + if err = ut.AddCardinal("max-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-items-item", "{0} elementos", 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 + var kind reflect.Kind + + 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 + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch 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} no es igual a {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} no debería ser igual a {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} debe tener menos de {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} debe ser menos de {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} debe contener menos de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} elementos", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} debe ser antes de la fecha y hora actual", 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 + var kind reflect.Kind + + 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 + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch 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()) + goto END + } + + 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} debe tener un máximo de {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} debe ser {1} o menos", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} debe contener como máximo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} elementos", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} debe ser antes o durante la fecha y hora actual", 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 + var kind reflect.Kind + + 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 + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch 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()) + goto END + } + + 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} debe ser mayor que {1} en longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} debe ser mayor que {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} debe contener más de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} elementos", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} debe ser después de la fecha y hora actual", 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 + var kind reflect.Kind + + 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 + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch 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()) + goto END + } + + 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} debe tener al menos {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} debe ser {1} o mayor", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} debe contener al menos {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} elementos", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} debe ser después o durante la fecha y hora actuales", 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 + var kind reflect.Kind + + 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 + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch 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()) + goto END + } + + 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} debe ser igual a {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} debe ser igual a {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} no puede ser igual a {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} debe ser mayor que {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} debe ser mayor o igual a {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} debe ser menor que {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} debe ser menor o igual a {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} no puede ser igual a {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} debe ser mayor que {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} debe ser mayor o igual a {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} debe ser menor que {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} debe ser menor o igual a {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} sólo puede contener caracteres alfabéticos", + override: false, + }, + { + tag: "alphanum", + translation: "{0} sólo puede contener caracteres alfanuméricos", + override: false, + }, + { + tag: "numeric", + translation: "{0} debe ser un valor numérico válido", + override: false, + }, + { + tag: "number", + translation: "{0} debe ser un número válido", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} debe ser un hexadecimal válido", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} debe ser un color HEX válido", + override: false, + }, + { + tag: "rgb", + translation: "{0} debe ser un color RGB válido", + override: false, + }, + { + tag: "rgba", + translation: "{0} debe ser un color RGBA válido", + override: false, + }, + { + tag: "hsl", + translation: "{0} debe ser un color HSL válido", + override: false, + }, + { + tag: "hsla", + translation: "{0} debe ser un color HSL válido", + override: false, + }, + { + tag: "e164", + translation: "{0} debe ser un número de teléfono válido con formato E.164", + override: false, + }, + { + tag: "email", + translation: "{0} debe ser una dirección de correo electrónico válida", + override: false, + }, + { + tag: "url", + translation: "{0} debe ser un URL válido", + override: false, + }, + { + tag: "uri", + translation: "{0} debe ser una URI válida", + override: false, + }, + { + tag: "base64", + translation: "{0} debe ser una cadena de Base64 válida", + override: false, + }, + { + tag: "contains", + translation: "{0} debe contener el texto '{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} debe contener al menos uno de los siguientes caracteres '{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} no puede contener el texto '{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} no puede contener ninguno de los siguientes caracteres '{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} no puede contener lo siguiente '{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} debe ser un número ISBN válido", + override: false, + }, + { + tag: "isbn10", + translation: "{0} debe ser un número ISBN-10 válido", + override: false, + }, + { + tag: "isbn13", + translation: "{0} debe ser un número ISBN-13 válido", + override: false, + }, + { + tag: "uuid", + translation: "{0} debe ser un UUID válido", + override: false, + }, + { + tag: "uuid3", + translation: "{0} debe ser una versión válida 3 UUID", + override: false, + }, + { + tag: "uuid4", + translation: "{0} debe ser una versión válida 4 UUID", + override: false, + }, + { + tag: "uuid5", + translation: "{0} debe ser una versión válida 5 UUID", + override: false, + }, + { + tag: "ascii", + translation: "{0} debe contener sólo caracteres ascii", + override: false, + }, + { + tag: "printascii", + translation: "{0} debe contener sólo caracteres ASCII imprimibles", + override: false, + }, + { + tag: "multibyte", + translation: "{0} debe contener caracteres multibyte", + override: false, + }, + { + tag: "datauri", + translation: "{0} debe contener un URI de datos válido", + override: false, + }, + { + tag: "latitude", + translation: "{0} debe contener coordenadas de latitud válidas", + override: false, + }, + { + tag: "longitude", + translation: "{0} debe contener unas coordenadas de longitud válidas", + override: false, + }, + { + tag: "ssn", + translation: "{0} debe ser un número válido de SSN", + override: false, + }, + { + tag: "ipv4", + translation: "{0} debe ser una dirección IPv4 válida", + override: false, + }, + { + tag: "ipv6", + translation: "{0} debe ser una dirección IPv6 válida", + override: false, + }, + { + tag: "ip", + translation: "{0} debe ser una dirección IP válida", + override: false, + }, + { + tag: "cidr", + translation: "{0} debe contener una anotación válida del CIDR", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} debe contener una notación CIDR válida para una dirección IPv4", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} debe contener una notación CIDR válida para una dirección IPv6", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} debe ser una dirección TCP válida", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} debe ser una dirección IPv4 TCP válida", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} debe ser una dirección IPv6 TCP válida", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} debe ser una dirección UDP válida", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} debe ser una dirección IPv4 UDP válida", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} debe ser una dirección IPv6 UDP válida", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} debe ser una dirección IP resoluble", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} debe ser una dirección IPv4 resoluble", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} debe ser una dirección IPv6 resoluble", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} debe ser una dirección UNIX resoluble", + override: false, + }, + { + tag: "mac", + translation: "{0} debe contener una dirección MAC válida", + override: false, + }, + { + tag: "unique", + translation: "{0} debe contener valores únicos", + override: false, + }, + { + tag: "iscolor", + translation: "{0} debe ser un color válido", + override: false, + }, + { + tag: "oneof", + translation: "{0} debe ser uno de [{1}]", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + s, 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 s + }, + }, + } + + 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/es/es_test.go b/translations/es/es_test.go new file mode 100644 index 0000000..070e3a8 --- /dev/null +++ b/translations/es/es_test.go @@ -0,0 +1,652 @@ +package es + +import ( + "testing" + "time" + + spanish "github.com/go-playground/locales/es" + ut "github.com/go-playground/universal-translator" + . "github.com/go-playground/assert/v2" + "github.com/go-playground/validator/v10" +) + +func TestTranslations(t *testing.T) { + + spa := spanish.New() + uni := ut.New(spa, spa) + trans, _ := uni.GetTranslator("es") + + 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"` + IsColor string `validate:"iscolor"` + StrPtrMinLen *string `validate:"min=10"` + StrPtrMaxLen *string `validate:"max=1"` + StrPtrLen *string `validate:"len=2"` + StrPtrLt *string `validate:"lt=1"` + StrPtrLte *string `validate:"lte=1"` + StrPtrGt *string `validate:"gt=10"` + StrPtrGte *string `validate:"gte=10"` + OneOfString string `validate:"oneof=red green"` + OneOfInt int `validate:"oneof=5 63"` + UniqueSlice []string `validate:"unique"` + UniqueArray [3]string `validate:"unique"` + UniqueMap map[string]string `validate:"unique"` + } + + 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" + + s := "toolong" + test.StrPtrMaxLen = &s + test.StrPtrLen = &s + + test.UniqueSlice = []string{"1234", "1234"} + test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"} + + 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.IsColor", + expected: "IsColor debe ser un color válido", + }, + { + ns: "Test.MAC", + expected: "MAC debe contener una dirección MAC válida", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr debe ser una dirección IP resoluble", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 debe ser una dirección IPv4 resoluble", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 debe ser una dirección IPv6 resoluble", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr debe ser una dirección UDP válida", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 debe ser una dirección IPv4 UDP válida", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 debe ser una dirección IPv6 UDP válida", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr debe ser una dirección TCP válida", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 debe ser una dirección IPv4 TCP válida", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 debe ser una dirección IPv6 TCP válida", + }, + { + ns: "Test.CIDR", + expected: "CIDR debe contener una anotación válida del CIDR", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 debe contener una notación CIDR válida para una dirección IPv4", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 debe contener una notación CIDR válida para una dirección IPv6", + }, + { + ns: "Test.SSN", + expected: "SSN debe ser un número válido de SSN", + }, + { + ns: "Test.IP", + expected: "IP debe ser una dirección IP válida", + }, + { + ns: "Test.IPv4", + expected: "IPv4 debe ser una dirección IPv4 válida", + }, + { + ns: "Test.IPv6", + expected: "IPv6 debe ser una dirección IPv6 válida", + }, + { + ns: "Test.DataURI", + expected: "DataURI debe contener un URI de datos válido", + }, + { + ns: "Test.Latitude", + expected: "Latitude debe contener coordenadas de latitud válidas", + }, + { + ns: "Test.Longitude", + expected: "Longitude debe contener unas coordenadas de longitud válidas", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte debe contener caracteres multibyte", + }, + { + ns: "Test.ASCII", + expected: "ASCII debe contener sólo caracteres ascii", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII debe contener sólo caracteres ASCII imprimibles", + }, + { + ns: "Test.UUID", + expected: "UUID debe ser un UUID válido", + }, + { + ns: "Test.UUID3", + expected: "UUID3 debe ser una versión válida 3 UUID", + }, + { + ns: "Test.UUID4", + expected: "UUID4 debe ser una versión válida 4 UUID", + }, + { + ns: "Test.UUID5", + expected: "UUID5 debe ser una versión válida 5 UUID", + }, + { + ns: "Test.ISBN", + expected: "ISBN debe ser un número ISBN válido", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 debe ser un número ISBN-10 válido", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 debe ser un número ISBN-13 válido", + }, + { + ns: "Test.Excludes", + expected: "Excludes no puede contener el texto 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll no puede contener ninguno de los siguientes caracteres '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune no puede contener lo siguiente '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny debe contener al menos uno de los siguientes caracteres '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains debe contener el texto 'purpose'", + }, + { + ns: "Test.Base64", + expected: "Base64 debe ser una cadena de Base64 válida", + }, + { + ns: "Test.Email", + expected: "Email debe ser una dirección de correo electrónico válida", + }, + { + ns: "Test.URL", + expected: "URL debe ser un URL válido", + }, + { + ns: "Test.URI", + expected: "URI debe ser una URI válida", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString debe ser un color RGB válido", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString debe ser un color RGBA válido", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString debe ser un color HSL válido", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString debe ser un color HSL válido", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString debe ser un hexadecimal válido", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString debe ser un color HEX válido", + }, + { + ns: "Test.NumberString", + expected: "NumberString debe ser un número válido", + }, + { + ns: "Test.NumericString", + expected: "NumericString debe ser un valor numérico válido", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString sólo puede contener caracteres alfanuméricos", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString sólo puede contener caracteres alfabéticos", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString debe ser menor que MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString debe ser menor o igual a MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString debe ser mayor que MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString debe ser mayor o igual a MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString no puede ser igual a EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString debe ser menor que Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString debe ser menor o igual a Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString debe ser mayor que Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString debe ser mayor o igual a Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString no puede ser igual a Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString debe ser igual a Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString debe ser igual a MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString debe tener al menos 3 caracteres de longitud", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber debe ser 5,56 o mayor", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple debe contener al menos 2 elementos", + }, + { + ns: "Test.GteTime", + expected: "GteTime debe ser después o durante la fecha y hora actuales", + }, + { + ns: "Test.GtString", + expected: "GtString debe ser mayor que 3 caracteres en longitud", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber debe ser mayor que 5,56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple debe contener más de 2 elementos", + }, + { + ns: "Test.GtTime", + expected: "GtTime debe ser después de la fecha y hora actual", + }, + { + ns: "Test.LteString", + expected: "LteString debe tener un máximo de 3 caracteres de longitud", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber debe ser 5,56 o menos", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple debe contener como máximo 2 elementos", + }, + { + ns: "Test.LteTime", + expected: "LteTime debe ser antes o durante la fecha y hora actual", + }, + { + ns: "Test.LtString", + expected: "LtString debe tener menos de 3 caracteres de longitud", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber debe ser menos de 5,56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple debe contener menos de 2 elementos", + }, + { + ns: "Test.LtTime", + expected: "LtTime debe ser antes de la fecha y hora actual", + }, + { + ns: "Test.NeString", + expected: "NeString no debería ser igual a ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber no debería ser igual a 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple no debería ser igual a 0", + }, + { + ns: "Test.EqString", + expected: "EqString no es igual a 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber no es igual a 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple no es igual a 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString debe tener un máximo de 3 caracteres de longitud", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber debe ser 1.113,00 o menos", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple debe contener como máximo 7 elementos", + }, + { + ns: "Test.MinString", + expected: "MinString debe tener al menos 1 carácter de longitud", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber debe ser 1.113,00 o más", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple debe contener al menos 7 elementos", + }, + { + ns: "Test.LenString", + expected: "LenString debe tener 1 carácter de longitud", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber debe ser igual a 1.113,00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple debe contener 7 elementos", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString es un campo requerido", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber es un campo requerido", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple es un campo requerido", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen debe tener al menos 10 caracteres de longitud", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen debe tener un máximo de 1 carácter de longitud", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen debe tener 2 caracteres de longitud", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt debe tener menos de 1 carácter de longitud", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte debe tener un máximo de 1 carácter de longitud", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt debe ser mayor que 10 caracteres en longitud", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte debe tener al menos 10 caracteres de longitud", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString debe ser uno de [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt debe ser uno de [5 63]", + }, + { + ns: "Test.UniqueSlice", + expected: "UniqueSlice debe contener valores únicos", + }, + { + ns: "Test.UniqueArray", + expected: "UniqueArray debe contener valores únicos", + }, + { + ns: "Test.UniqueMap", + expected: "UniqueMap debe contener valores únicos", + }, + } + + 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/translations/zh/zh.go b/translations/zh/zh.go index c3032b7..6b6cce1 100644 --- a/translations/zh/zh.go +++ b/translations/zh/zh.go @@ -1308,6 +1308,36 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return s }, }, + { + tag: "json", + translation: "{0}必须是一个JSON字符串", + override: false, + }, + { + tag: "lowercase", + translation: "{0}必须是小写字母", + override: false, + }, + { + tag: "uppercase", + translation: "{0}必须是大写字母", + override: false, + }, + { + tag: "datetime", + translation: "{0}的格式必须是{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("警告: 翻译字段错误: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, } for _, t := range translations { diff --git a/translations/zh/zh_test.go b/translations/zh/zh_test.go index 26a21ae..d106e0d 100644 --- a/translations/zh/zh_test.go +++ b/translations/zh/zh_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" zhongwen "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -138,6 +138,10 @@ func TestTranslations(t *testing.T) { StrPtrGte *string `validate:"gte=10"` OneOfString string `validate:"oneof=red green"` OneOfInt int `validate:"oneof=5 63"` + JsonString string `validate:"json"` + LowercaseString string `validate:"lowercase"` + UppercaseString string `validate:"uppercase"` + Datetime string `validate:"datetime=2006-01-02"` } var test Test @@ -184,6 +188,13 @@ func TestTranslations(t *testing.T) { test.StrPtrMaxLen = &s test.StrPtrLen = &s + test.JsonString = "{\"foo\":\"bar\",}" + + test.LowercaseString = "ABCDEFG" + test.UppercaseString = "abcdefg" + + test.Datetime = "20060102" + err = validate.Struct(test) NotEqual(t, err, nil) @@ -614,6 +625,22 @@ func TestTranslations(t *testing.T) { ns: "Test.OneOfInt", expected: "OneOfInt必须是[5 63]中的一个", }, + { + ns: "Test.JsonString", + expected: "JsonString必须是一个JSON字符串", + }, + { + ns: "Test.LowercaseString", + expected: "LowercaseString必须是小写字母", + }, + { + ns: "Test.UppercaseString", + expected: "UppercaseString必须是大写字母", + }, + { + ns: "Test.Datetime", + expected: "Datetime的格式必须是2006-01-02", + }, } for _, tt := range tests { diff --git a/translations/zh_tw/zh_tw.go b/translations/zh_tw/zh_tw.go index cf6f933..391a010 100644 --- a/translations/zh_tw/zh_tw.go +++ b/translations/zh_tw/zh_tw.go @@ -1304,6 +1304,21 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return s }, }, + { + tag: "datetime", + translation: "{0}與{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("警告: 翻譯欄位錯誤: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, } for _, t := range translations { diff --git a/translations/zh_tw/zh_tw_test.go b/translations/zh_tw/zh_tw_test.go index a4463a2..cf60d1d 100644 --- a/translations/zh_tw/zh_tw_test.go +++ b/translations/zh_tw/zh_tw_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" zhongwen "github.com/go-playground/locales/zh_Hant_TW" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -138,6 +138,7 @@ func TestTranslations(t *testing.T) { StrPtrGte *string `validate:"gte=10"` OneOfString string `validate:"oneof=red green"` OneOfInt int `validate:"oneof=5 63"` + Datetime string `validate:"datetime=2006-01-02"` } var test Test @@ -184,6 +185,8 @@ func TestTranslations(t *testing.T) { test.StrPtrMaxLen = &s test.StrPtrLen = &s + test.Datetime = "2008-Feb-01" + err = validate.Struct(test) NotEqual(t, err, nil) @@ -614,6 +617,10 @@ func TestTranslations(t *testing.T) { ns: "Test.OneOfInt", expected: "OneOfInt必須是[5 63]中的一個", }, + { + ns: "Test.Datetime", + expected: "Datetime與2006-01-02格式不匹配", + }, } for _, tt := range tests { diff --git a/validator_test.go b/validator_test.go index deea780..e76a3cd 100644 --- a/validator_test.go +++ b/validator_test.go @@ -7896,6 +7896,11 @@ func TestHostnameRFC952Validation(t *testing.T) { {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, {"2001:cdba:0:0:0:0:3257:9652", false}, {"2001:cdba::3257:9652", false}, + {"example..........com", false}, + {"1234", false}, + {"abc1234", true}, + {"example. com", false}, + {"ex ample.com", false}, } validate := New()