diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d1ca1e8..d6bfd55 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -8,7 +8,7 @@ jobs: test: strategy: matrix: - go-version: [1.15.x, 1.16.x] + go-version: [1.17.x, 1.18.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -32,7 +32,7 @@ jobs: run: go test -race -covermode=atomic -coverprofile="profile.cov" ./... - name: Send Coverage - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.16.x' + if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.18.x' uses: shogo82148/actions-goveralls@v1 with: path-to-profile: profile.cov @@ -41,8 +41,11 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/setup-go@v3 + with: + go-version: 1.16 + - uses: actions/checkout@v3 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: - version: v1.41.1 + version: v1.45.2 diff --git a/README.md b/README.md index f56cff1..0201880 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,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-10.9.0-green.svg) +![Project status](https://img.shields.io/badge/version-10.10.1-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) @@ -130,7 +130,7 @@ Baked-in Validations | contains | Contains | | containsany | Contains Any | | containsrune | Contains Rune | -| endsnotwith | Ends With | +| endsnotwith | Ends Not With | | endswith | Ends With | | excludes | Excludes | | excludesall | Excludes All | @@ -153,6 +153,7 @@ Baked-in Validations | bcp47_language_tag | Language tag (BCP 47) | | btc_addr | Bitcoin Address | | btc_addr_bech32 | Bitcoin Bech32 Address (segwit) | +| credit_card | Credit Card Number | | datetime | Datetime | | e164 | e164 formatted phone number | | email | E-mail String @@ -189,6 +190,8 @@ Baked-in Validations | uuid5 | Universally Unique Identifier UUID v5 | | uuid5_rfc4122 | Universally Unique Identifier UUID v5 RFC4122 | | uuid_rfc4122 | Universally Unique Identifier UUID RFC4122 | +| semver | Semantic Versioning 2.0.0 | +| ulid | Universally Unique Lexicographically Sortable Identifier ULID | ### Comparisons: | Tag | Description | @@ -217,6 +220,8 @@ Baked-in Validations | required_with_all | Required With All | | required_without | Required Without | | required_without_all | Required Without All | +| excluded_if | Excluded If | +| excluded_unless | Excluded Unless | | excluded_with | Excluded With | | excluded_with_all | Excluded With All | | excluded_without | Excluded Without | diff --git a/baked_in.go b/baked_in.go index f5fd239..bc638f2 100644 --- a/baked_in.go +++ b/baked_in.go @@ -75,6 +75,8 @@ var ( "required_with_all": requiredWithAll, "required_without": requiredWithout, "required_without_all": requiredWithoutAll, + "excluded_if": excludedIf, + "excluded_unless": excludedUnless, "excluded_with": excludedWith, "excluded_with_all": excludedWithAll, "excluded_without": excludedWithout, @@ -148,6 +150,7 @@ var ( "uuid3_rfc4122": isUUID3RFC4122, "uuid4_rfc4122": isUUID4RFC4122, "uuid5_rfc4122": isUUID5RFC4122, + "ulid": isULID, "ascii": isASCII, "printascii": isPrintableASCII, "multibyte": hasMultiByteCharacter, @@ -198,6 +201,9 @@ var ( "postcode_iso3166_alpha2": isPostcodeByIso3166Alpha2, "postcode_iso3166_alpha2_field": isPostcodeByIso3166Alpha2Field, "bic": isIsoBicFormat, + "semver": isSemverFormat, + "dns_rfc1035_label": isDnsRFC1035LabelFormat, + "credit_card": isCreditCard, } ) @@ -498,6 +504,11 @@ func isUUIDRFC4122(fl FieldLevel) bool { return uUIDRFC4122Regex.MatchString(fl.Field().String()) } +// isULID is the validation function for validating if the field's value is a valid ULID. +func isULID(fl FieldLevel) bool { + return uLIDRegex.MatchString(fl.Field().String()) +} + // isISBN is the validation function for validating if the field's value is a valid v10 or v13 ISBN. func isISBN(fl FieldLevel) bool { return isISBN10(fl) || isISBN13(fl) @@ -808,12 +819,7 @@ func isNeField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return true - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) @@ -821,6 +827,10 @@ func isNeField(fl FieldLevel) bool { return !fieldTime.Equal(t) } + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return true + } } // default reflect.String: @@ -861,18 +871,18 @@ func isLteCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - fieldTime := field.Interface().(time.Time) - topTime := topField.Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) + topTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.Before(topTime) || fieldTime.Equal(topTime) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -909,18 +919,18 @@ func isLtCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - fieldTime := field.Interface().(time.Time) - topTime := topField.Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) + topTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.Before(topTime) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -956,18 +966,18 @@ func isGteCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - fieldTime := field.Interface().(time.Time) - topTime := topField.Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) + topTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.After(topTime) || fieldTime.Equal(topTime) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -1003,18 +1013,18 @@ func isGtCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - fieldTime := field.Interface().(time.Time) - topTime := topField.Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) + topTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.After(topTime) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -1053,18 +1063,18 @@ func isNeCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return true - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - t := field.Interface().(time.Time) - fieldTime := topField.Interface().(time.Time) + t := field.Convert(timeType).Interface().(time.Time) + fieldTime := topField.Convert(timeType).Interface().(time.Time) return !fieldTime.Equal(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return true + } } // default reflect.String: @@ -1103,18 +1113,18 @@ func isEqCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - t := field.Interface().(time.Time) - fieldTime := topField.Interface().(time.Time) + t := field.Convert(timeType).Interface().(time.Time) + fieldTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.Equal(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -1153,19 +1163,18 @@ func isEqField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.Equal(t) } + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String: @@ -1534,6 +1543,22 @@ func requiredIf(fl FieldLevel) bool { return hasValue(fl) } +// excludedIf is the validation function +// The field under validation must not be present or is empty only if all the other specified fields are equal to the value following with the specified field. +func excludedIf(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + if len(params)%2 != 0 { + panic(fmt.Sprintf("Bad param number for excluded_if %s", fl.FieldName())) + } + + for i := 0; i < len(params); i += 2 { + if !requireCheckFieldValue(fl, params[i], params[i+1], false) { + return false + } + } + return true +} + // requiredUnless is the validation function // The field under validation must be present and not empty only unless all the other specified fields are equal to the value following with the specified field. func requiredUnless(fl FieldLevel) bool { @@ -1550,6 +1575,21 @@ func requiredUnless(fl FieldLevel) bool { return hasValue(fl) } +// excludedUnless is the validation function +// The field under validation must not be present or is empty unless all the other specified fields are equal to the value following with the specified field. +func excludedUnless(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + if len(params)%2 != 0 { + panic(fmt.Sprintf("Bad param number for excluded_unless %s", fl.FieldName())) + } + for i := 0; i < len(params); i += 2 { + if !requireCheckFieldValue(fl, params[i], params[i+1], false) { + return true + } + } + return !hasValue(fl) +} + // excludedWith is the validation function // The field under validation must not be present or is empty if any of the other specified fields are present. func excludedWith(fl FieldLevel) bool { @@ -1669,18 +1709,18 @@ func isGteField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.After(t) || fieldTime.Equal(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String @@ -1716,18 +1756,18 @@ func isGtField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.After(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String @@ -1769,10 +1809,10 @@ func isGte(fl FieldLevel) bool { case reflect.Struct: - if field.Type() == timeType { + if field.Type().ConvertibleTo(timeType) { now := time.Now().UTC() - t := field.Interface().(time.Time) + t := field.Convert(timeType).Interface().(time.Time) return t.After(now) || t.Equal(now) } @@ -1815,9 +1855,9 @@ func isGt(fl FieldLevel) bool { return field.Float() > p case reflect.Struct: - if field.Type() == timeType { + if field.Type().ConvertibleTo(timeType) { - return field.Interface().(time.Time).After(time.Now().UTC()) + return field.Convert(timeType).Interface().(time.Time).After(time.Now().UTC()) } } @@ -1895,18 +1935,18 @@ func isLteField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.Before(t) || fieldTime.Equal(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String @@ -1942,18 +1982,18 @@ func isLtField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.Before(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String @@ -1995,10 +2035,10 @@ func isLte(fl FieldLevel) bool { case reflect.Struct: - if field.Type() == timeType { + if field.Type().ConvertibleTo(timeType) { now := time.Now().UTC() - t := field.Interface().(time.Time) + t := field.Convert(timeType).Interface().(time.Time) return t.Before(now) || t.Equal(now) } @@ -2042,9 +2082,9 @@ func isLt(fl FieldLevel) bool { case reflect.Struct: - if field.Type() == timeType { + if field.Type().ConvertibleTo(timeType) { - return field.Interface().(time.Time).Before(time.Now().UTC()) + return field.Convert(timeType).Interface().(time.Time).Before(time.Now().UTC()) } } @@ -2351,6 +2391,12 @@ func isIso3166AlphaNumeric(fl FieldLevel) bool { var code int switch field.Kind() { + case reflect.String: + i, err := strconv.Atoi(field.String()) + if err != nil { + return false + } + code = i % 1000 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: code = int(field.Int() % 1000) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: @@ -2407,3 +2453,56 @@ func isIsoBicFormat(fl FieldLevel) bool { return bicRegex.MatchString(bicString) } + +// isSemverFormat is the validation function for validating if the current field's value is a valid semver version, defined in Semantic Versioning 2.0.0 +func isSemverFormat(fl FieldLevel) bool { + semverString := fl.Field().String() + + return semverRegex.MatchString(semverString) +} + +// isDnsRFC1035LabelFormat is the validation function +// for validating if the current field's value is +// a valid dns RFC 1035 label, defined in RFC 1035. +func isDnsRFC1035LabelFormat(fl FieldLevel) bool { + val := fl.Field().String() + return dnsRegexRFC1035Label.MatchString(val) +} + +// isCreditCard is the validation function for validating if the current field's value is a valid credit card number +func isCreditCard(fl FieldLevel) bool { + val := fl.Field().String() + var creditCard bytes.Buffer + segments := strings.Split(val, " ") + for _, segment := range segments { + if len(segment) < 3 { + return false + } + creditCard.WriteString(segment) + } + + ccDigits := strings.Split(creditCard.String(), "") + size := len(ccDigits) + if size < 12 || size > 19 { + return false + } + + sum := 0 + for i, digit := range ccDigits { + value, err := strconv.Atoi(digit) + if err != nil { + return false + } + if size%2 == 0 && i%2 == 0 || size%2 == 1 && i%2 == 1 { + v := value * 2 + if v >= 10 { + sum += 1 + (v % 10) + } else { + sum += v + } + } else { + sum += value + } + } + return (sum % 10) == 0 +} diff --git a/doc.go b/doc.go index 8c25847..7341c67 100644 --- a/doc.go +++ b/doc.go @@ -349,6 +349,40 @@ Example: // require the field if the Field1 and Field2 is not present: Usage: required_without_all=Field1 Field2 +Excluded If + +The field under validation must not be present or not empty only if all +the other specified fields are equal to the value following the specified +field. For strings ensures value is not "". For slices, maps, pointers, +interfaces, channels and functions ensures the value is not nil. + + Usage: excluded_if + +Examples: + + // exclude the field if the Field1 is equal to the parameter given: + Usage: excluded_if=Field1 foobar + + // exclude the field if the Field1 and Field2 is equal to the value respectively: + Usage: excluded_if=Field1 foo Field2 bar + +Excluded Unless + +The field under validation must not be present or empty unless all +the other specified fields are equal to the value following the specified +field. For strings ensures value is not "". For slices, maps, pointers, +interfaces, channels and functions ensures the value is not nil. + + Usage: excluded_unless + +Examples: + + // exclude the field unless the Field1 is equal to the parameter given: + Usage: excluded_unless=Field1 foobar + + // exclude the field unless the Field1 and Field2 is equal to the value respectively: + Usage: excluded_unless=Field1 foo Field2 bar + Is Default This validates that the value is the default value and is almost the @@ -1007,6 +1041,12 @@ This validates that a string value contains a valid version 5 UUID. Uppercase U Usage: uuid5 +Universally Unique Lexicographically Sortable Identifier ULID + +This validates that a string value contains a valid ULID value. + + Usage: ulid + ASCII This validates that a string value contains only ASCII characters. @@ -1255,6 +1295,13 @@ More information on https://www.iso.org/standard/60390.html Usage: bic +RFC 1035 label + +This validates that a string value is a valid dns RFC 1035 label, defined in RFC 1035. +More information on https://datatracker.ietf.org/doc/html/rfc1035 + + Usage: dns_rfc1035_label + TimeZone This validates that a string value is a valid time zone based on the time zone database present on the system. @@ -1263,6 +1310,18 @@ More information on https://golang.org/pkg/time/#LoadLocation Usage: timezone +Semantic Version + +This validates that a string value is a valid semver version, defined in Semantic Versioning 2.0.0. +More information on https://semver.org/ + + Usage: semver + +Credit Card + +This validates that a string value contains a valid credit card number using Luhn algoritm. + + Usage: credit_card Alias Validators and Tags diff --git a/go.mod b/go.mod index ddecff6..e281ec0 100644 --- a/go.mod +++ b/go.mod @@ -11,9 +11,9 @@ require ( github.com/leodido/go-urn v1.2.1 github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/stretchr/testify v1.7.0 // indirect - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect - golang.org/x/text v0.3.6 + golang.org/x/text v0.3.7 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 5750093..a1c43c3 100644 --- a/go.sum +++ b/go.sum @@ -28,17 +28,19 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/regexes.go b/regexes.go index df00c4e..a8668f8 100644 --- a/regexes.go +++ b/regexes.go @@ -10,7 +10,7 @@ const ( numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$" numberRegexString = "^[0-9]+$" hexadecimalRegexString = "^(0[xX])?[0-9a-fA-F]+$" - hexColorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" + hexColorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$" rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$" rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" @@ -29,6 +29,7 @@ const ( uUID4RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" uUID5RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" uUIDRFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + uLIDRegexString = "^[A-HJKMNP-TV-Z0-9]{26}$" aSCIIRegexString = "^[\x00-\x7F]*$" printableASCIIRegexString = "^[\x20-\x7E]*$" multibyteRegexString = "[^\x00-\x7F]" @@ -36,12 +37,12 @@ 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 - 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 - btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 - btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + 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 + btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` @@ -51,6 +52,8 @@ const ( jWTRegexString = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$" splitParamsRegexString = `'[^']*'|\S+` bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$` + semverRegexString = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/ + dnsRegexStringRFC1035Label = "^[a-z]([-a-z0-9]*[a-z0-9]){0,62}$" ) var ( @@ -80,6 +83,7 @@ var ( uUID4RFC4122Regex = regexp.MustCompile(uUID4RFC4122RegexString) uUID5RFC4122Regex = regexp.MustCompile(uUID5RFC4122RegexString) uUIDRFC4122Regex = regexp.MustCompile(uUIDRFC4122RegexString) + uLIDRegex = regexp.MustCompile(uLIDRegexString) aSCIIRegex = regexp.MustCompile(aSCIIRegexString) printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString) multibyteRegex = regexp.MustCompile(multibyteRegexString) @@ -102,4 +106,6 @@ var ( jWTRegex = regexp.MustCompile(jWTRegexString) splitParamsRegex = regexp.MustCompile(splitParamsRegexString) bicRegex = regexp.MustCompile(bicRegexString) + semverRegex = regexp.MustCompile(semverRegexString) + dnsRegexRFC1035Label = regexp.MustCompile(dnsRegexStringRFC1035Label) ) diff --git a/translations/en/en.go b/translations/en/en.go index b95f7dd..ee05f91 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -28,6 +28,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} is a required field", override: false, }, + { + tag: "required_if", + translation: "{0} is a required field", + override: false, + }, { tag: "len", customRegisFunc: func(ut ut.Translator) (err error) { @@ -1136,6 +1141,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be a valid version 5 UUID", override: false, }, + { + tag: "ulid", + translation: "{0} must be a valid ULID", + override: false, + }, { tag: "ascii", translation: "{0} must contain only ascii characters", @@ -1341,6 +1351,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return t }, }, + { + tag: "boolean", + translation: "{0} must be a valid boolean value", + override: false, + }, } for _, t := range translations { diff --git a/translations/en/en_test.go b/translations/en/en_test.go index 0f20768..9cb6deb 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_test.go @@ -27,6 +27,7 @@ func TestTranslations(t *testing.T) { GteCSFieldString string LtCSFieldString string LteCSFieldString string + RequiredIf string } type Test struct { @@ -34,6 +35,7 @@ func TestTranslations(t *testing.T) { RequiredString string `validate:"required"` RequiredNumber int `validate:"required"` RequiredMultiple []string `validate:"required"` + RequiredIf string `validate:"required_if=Inner.RequiredIf abcd"` LenString string `validate:"len=1"` LenNumber float64 `validate:"len=1113.00"` LenMultiple []string `validate:"len=7"` @@ -103,6 +105,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -148,6 +151,7 @@ func TestTranslations(t *testing.T) { PostCode string `validate:"postcode_iso3166_alpha2=SG"` PostCodeCountry string PostCodeByField string `validate:"postcode_iso3166_alpha2_field=PostCodeCountry"` + BooleanString string `validate:"boolean"` } var test Test @@ -200,6 +204,9 @@ func TestTranslations(t *testing.T) { test.UniqueSlice = []string{"1234", "1234"} test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"} test.Datetime = "2008-Feb-01" + test.BooleanString = "A" + + test.Inner.RequiredIf = "abcd" err = validate.Struct(test) NotEqual(t, err, nil) @@ -323,6 +330,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 must be a valid version 5 UUID", }, + { + ns: "Test.ULID", + expected: "ULID must be a valid ULID", + }, { ns: "Test.ISBN", expected: "ISBN must be a valid ISBN number", @@ -587,6 +598,10 @@ func TestTranslations(t *testing.T) { ns: "Test.RequiredString", expected: "RequiredString is a required field", }, + { + ns: "Test.RequiredIf", + expected: "RequiredIf is a required field", + }, { ns: "Test.RequiredNumber", expected: "RequiredNumber is a required field", @@ -671,6 +686,10 @@ func TestTranslations(t *testing.T) { ns: "Test.PostCodeByField", expected: "PostCodeByField does not match postcode format of country in PostCodeCountry field", }, + { + ns: "Test.BooleanString", + expected: "BooleanString must be a valid boolean value", + }, } for _, tt := range tests { diff --git a/translations/es/es.go b/translations/es/es.go index 2b87f10..2635408 100644 --- a/translations/es/es.go +++ b/translations/es/es.go @@ -1178,6 +1178,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} debe ser una versión válida 5 UUID", override: false, }, + { + tag: "ulid", + translation: "{0} debe ser un ULID válido", + override: false, + }, { tag: "ascii", translation: "{0} debe contener sólo caracteres ascii", diff --git a/translations/es/es_test.go b/translations/es/es_test.go index 070e3a8..814f2ab 100644 --- a/translations/es/es_test.go +++ b/translations/es/es_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" 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" ) @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -312,6 +313,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 debe ser una versión válida 5 UUID", }, + { + ns: "Test.ULID", + expected: "ULID debe ser un ULID válido", + }, { ns: "Test.ISBN", expected: "ISBN debe ser un número ISBN válido", diff --git a/translations/fa/fa.go b/translations/fa/fa.go index fc21aa0..b7c100b 100644 --- a/translations/fa/fa.go +++ b/translations/fa/fa.go @@ -1136,6 +1136,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} باید یک UUID نسخه 5 معتبر باشد", override: false, }, + { + tag: "ulid", + translation: "{0} باید یک ULID معتبر باشد", + override: false, + }, { tag: "ascii", translation: "{0} باید فقط شامل کاراکترهای اسکی باشد", diff --git a/translations/fa/fa_test.go b/translations/fa/fa_test.go index 611f23e..1acb8cf 100644 --- a/translations/fa/fa_test.go +++ b/translations/fa/fa_test.go @@ -103,6 +103,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -322,6 +323,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 باید یک UUID نسخه 5 معتبر باشد", }, + { + ns: "Test.ULID", + expected: "ULID باید یک ULID معتبر باشد", + }, { ns: "Test.ISBN", expected: "ISBN باید یک شابک معتبر باشد", diff --git a/translations/fr/fr.go b/translations/fr/fr.go index ddebdf9..c455195 100644 --- a/translations/fr/fr.go +++ b/translations/fr/fr.go @@ -1173,6 +1173,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} doit être un UUID version 5 valid", override: false, }, + { + tag: "ulid", + translation: "{0} doit être une ULID valide", + override: false, + }, { tag: "ascii", translation: "{0} ne doit contenir que des caractères ascii", diff --git a/translations/fr/fr_test.go b/translations/fr/fr_test.go index c2c88d0..f1ba280 100644 --- a/translations/fr/fr_test.go +++ b/translations/fr/fr_test.go @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -306,6 +307,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 doit être un UUID version 5 valid", }, + { + ns: "Test.ULID", + expected: "ULID doit être une ULID valide", + }, { ns: "Test.ISBN", expected: "ISBN doit être un numéro ISBN valid", diff --git a/translations/id/id.go b/translations/id/id.go index 4e72fa4..5b74939 100644 --- a/translations/id/id.go +++ b/translations/id/id.go @@ -1136,6 +1136,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} harus berupa UUID versi 5 yang valid", override: false, }, + { + tag: "ulid", + translation: "{0} harus berupa ULID yang valid", + override: false, + }, { tag: "ascii", translation: "{0} hanya boleh berisi karakter ascii", diff --git a/translations/id/id_test.go b/translations/id/id_test.go index d579e58..1ea9944 100644 --- a/translations/id/id_test.go +++ b/translations/id/id_test.go @@ -323,6 +323,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 harus berupa UUID versi 5 yang valid", }, + { + ns: "Test.ULID", + expected: "ULID harus berupa ULID yang valid", + }, { ns: "Test.ISBN", expected: "ISBN harus berupa nomor ISBN yang valid", diff --git a/translations/it/it.go b/translations/it/it.go new file mode 100644 index 0000000..8abd67f --- /dev/null +++ b/translations/it/it.go @@ -0,0 +1,1244 @@ +package en + +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} è un campo obbligatorio", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("len-string", "{0} deve essere lungo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} deve essere uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} deve contenere {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} elementi", 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} deve essere lungo almeno {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} deve essere maggiore o uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} deve contenere almeno {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} elementi", 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} deve essere lungo al massimo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} deve essere minore o uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} deve contenere al massimo {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} elementi", 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} non è uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ne", + translation: "{0} deve essere diverso da {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "lt", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("lt-string", "{0} deve essere lungo meno di {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} deve essere minore di {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} deve contenere meno di {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} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} deve essere precedente alla Data/Ora corrente", 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} deve essere lungo al massimo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} deve essere minore o uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} deve contenere al massimo {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} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} deve essere uguale o precedente alla Data/Ora corrente", 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} deve essere lungo più di {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} deve essere maggiore di {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} deve contenere più di {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} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} deve essere successivo alla Data/Ora corrente", 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} deve essere lungo almeno {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} deve essere maggiore o uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} deve contenere almeno {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} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} deve essere uguale o successivo alla Data/Ora corrente", 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} deve essere uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "eqcsfield", + translation: "{0} deve essere uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "necsfield", + translation: "{0} deve essere diverso da {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "gtcsfield", + translation: "{0} deve essere maggiore di {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "gtecsfield", + translation: "{0} deve essere maggiore o uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ltcsfield", + translation: "{0} deve essere minore di {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ltecsfield", + translation: "{0} deve essere minore o uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "nefield", + translation: "{0} deve essere diverso da {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "gtfield", + translation: "{0} deve essere maggiore di {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "gtefield", + translation: "{0} deve essere maggiore o uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ltfield", + translation: "{0} deve essere minore di {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ltefield", + translation: "{0} deve essere minore o uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "alpha", + translation: "{0} può contenere solo caratteri alfabetici", + override: false, + }, + { + tag: "alphanum", + translation: "{0} può contenere solo caratteri alfanumerici", + override: false, + }, + { + tag: "numeric", + translation: "{0} deve essere un valore numerico valido", + override: false, + }, + { + tag: "number", + translation: "{0} deve essere un numero valido", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} deve essere un esadecimale valido", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} deve essere un colore HEX valido", + override: false, + }, + { + tag: "rgb", + translation: "{0} deve essere un colore RGB valido", + override: false, + }, + { + tag: "rgba", + translation: "{0} deve essere un colore RGBA valido", + override: false, + }, + { + tag: "hsl", + translation: "{0} deve essere un colore HSL valido", + override: false, + }, + { + tag: "hsla", + translation: "{0} deve essere un colore HSLA valido", + override: false, + }, + { + tag: "e164", + translation: "{0} deve essere un numero telefonico in formato E.164 valido", + override: false, + }, + { + tag: "email", + translation: "{0} deve essere un indirizzo email valido", + override: false, + }, + { + tag: "url", + translation: "{0} deve essere un URL valido", + override: false, + }, + { + tag: "uri", + translation: "{0} deve essere un URI valido", + override: false, + }, + { + tag: "base64", + translation: "{0} deve essere una stringa Base64 valida", + override: false, + }, + { + tag: "contains", + translation: "{0} deve contenere il testo '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "containsany", + translation: "{0} deve contenere almeno uno dei seguenti caratteri '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "excludes", + translation: "{0} non deve contenere il testo '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "excludesall", + translation: "{0} non deve contenere alcuno dei seguenti caratteri '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "excludesrune", + translation: "{0} non deve contenere '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "isbn", + translation: "{0} deve essere un numero ISBN valido", + override: false, + }, + { + tag: "isbn10", + translation: "{0} deve essere un numero ISBN-10 valido", + override: false, + }, + { + tag: "isbn13", + translation: "{0} deve essere un numero ISBN-13 valido", + override: false, + }, + { + tag: "uuid", + translation: "{0} deve essere un UUID valido", + override: false, + }, + { + tag: "uuid3", + translation: "{0} deve essere un UUID versione 3 valido", + override: false, + }, + { + tag: "uuid4", + translation: "{0} deve essere un UUID versione 4 valido", + override: false, + }, + { + tag: "uuid5", + translation: "{0} deve essere un UUID versione 5 valido", + override: false, + }, + { + tag: "ulid", + translation: "{0} deve essere un ULID valido", + override: false, + }, + { + tag: "ascii", + translation: "{0} deve contenere solo caratteri ascii", + override: false, + }, + { + tag: "printascii", + translation: "{0} deve contenere solo caratteri ascii stampabili", + override: false, + }, + { + tag: "multibyte", + translation: "{0} deve contenere caratteri multibyte", + override: false, + }, + { + tag: "datauri", + translation: "{0} deve contenere un Data URI valido", + override: false, + }, + { + tag: "latitude", + translation: "{0} deve contenere una latitudine valida", + override: false, + }, + { + tag: "longitude", + translation: "{0} deve contenere una longitudine valida", + override: false, + }, + { + tag: "ssn", + translation: "{0} deve essere un numero SSN valido", + override: false, + }, + { + tag: "ipv4", + translation: "{0} deve essere un indirizzo IPv4 valido", + override: false, + }, + { + tag: "ipv6", + translation: "{0} deve essere un indirizzo IPv6 valido", + override: false, + }, + { + tag: "ip", + translation: "{0} deve essere un indirizzo IP valido", + override: false, + }, + { + tag: "cidr", + translation: "{0} deve contenere una notazione CIDR valida", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} deve contenere una notazione CIDR per un indirizzo IPv4 valida", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} deve contenere una notazione CIDR per un indirizzo IPv6 valida", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} deve essere un indirizzo TCP valido", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} deve essere un indirizzo IPv4 TCP valido", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} deve essere un indirizzo IPv6 TCP valido", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} deve essere un indirizzo UDP valido", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} deve essere un indirizzo IPv4 UDP valido", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} deve essere un indirizzo IPv6 UDP valido", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} deve essere un indirizzo IP risolvibile", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} deve essere un indirizzo IPv4 risolvibile", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} deve essere un indirizzo IPv6 risolvibile", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} deve essere un indirizzo UNIX risolvibile", + override: false, + }, + { + tag: "mac", + translation: "{0} deve contenere un indirizzo MAC valido", + override: false, + }, + { + tag: "unique", + translation: "{0} deve contenere valori unici", + override: false, + }, + { + tag: "iscolor", + translation: "{0} deve essere un colore valido", + override: false, + }, + { + tag: "oneof", + translation: "{0} deve essere uno di [{1}]", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "json", + translation: "{0} deve essere una stringa json valida", + override: false, + }, + { + tag: "jwt", + translation: "{0} deve essere una stringa jwt valida", + override: false, + }, + { + tag: "lowercase", + translation: "{0} deve essere una stringa minuscola", + override: false, + }, + { + tag: "boolean", + translation: "{0} deve rappresentare un valore booleano", + override: false, + }, + { + tag: "uppercase", + translation: "{0} deve essere una stringa maiuscola", + override: false, + }, + { + tag: "startswith", + translation: "{0} deve iniziare con {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "startsnotwith", + translation: "{0} non deve iniziare con {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "endswith", + translation: "{0} deve terminare con {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "endsnotwith", + translation: "{0} non deve terminare con {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "datetime", + translation: "{0} non corrisponde al formato {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "postcode_iso3166_alpha2", + translation: "{0} non corrisponde al formato del codice postale dello stato {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "postcode_iso3166_alpha2_field", + translation: "{0} non corrisponde al formato del codice postale dello stato nel campo {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + } + + 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 +} + +func customTransFuncV1(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 +} diff --git a/translations/it/it_test.go b/translations/it/it_test.go new file mode 100644 index 0000000..301e8c1 --- /dev/null +++ b/translations/it/it_test.go @@ -0,0 +1,724 @@ +package en + +import ( + "testing" + "time" + + . "github.com/go-playground/assert/v2" + italian "github.com/go-playground/locales/it" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" +) + +func TestTranslations(t *testing.T) { + it := italian.New() + uni := ut.New(it, it) + 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"` + ULID string `validate:"ulid"` + 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"` + BooleanString string `validate:"boolean"` + JSONString string `validate:"json"` + JWTString string `validate:"jwt"` + LowercaseString string `validate:"lowercase"` + UppercaseString string `validate:"uppercase"` + StartsWithString string `validate:"startswith=foo"` + StartsNotWithString string `validate:"startsnotwith=foo"` + EndsWithString string `validate:"endswith=foo"` + EndsNotWithString string `validate:"endsnotwith=foo"` + Datetime string `validate:"datetime=2006-01-02"` + PostCode string `validate:"postcode_iso3166_alpha2=SG"` + PostCodeCountry string + PostCodeByField string `validate:"postcode_iso3166_alpha2_field=PostCodeCountry"` + } + + 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" + + test.LowercaseString = "ABCDEFG" + test.UppercaseString = "abcdefg" + test.StartsWithString = "hello" + test.StartsNotWithString = "foo-hello" + test.EndsWithString = "hello" + test.EndsNotWithString = "hello-foo" + + s := "toolong" + test.StrPtrMaxLen = &s + test.StrPtrLen = &s + + test.UniqueSlice = []string{"1234", "1234"} + test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"} + test.Datetime = "2008-Feb-01" + + 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 deve essere un colore valido", + }, + { + ns: "Test.MAC", + expected: "MAC deve contenere un indirizzo MAC valido", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr deve essere un indirizzo IP risolvibile", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 deve essere un indirizzo IPv4 risolvibile", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 deve essere un indirizzo IPv6 risolvibile", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr deve essere un indirizzo UDP valido", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 deve essere un indirizzo IPv4 UDP valido", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 deve essere un indirizzo IPv6 UDP valido", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr deve essere un indirizzo TCP valido", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 deve essere un indirizzo IPv4 TCP valido", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 deve essere un indirizzo IPv6 TCP valido", + }, + { + ns: "Test.CIDR", + expected: "CIDR deve contenere una notazione CIDR valida", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 deve contenere una notazione CIDR per un indirizzo IPv4 valida", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 deve contenere una notazione CIDR per un indirizzo IPv6 valida", + }, + { + ns: "Test.SSN", + expected: "SSN deve essere un numero SSN valido", + }, + { + ns: "Test.IP", + expected: "IP deve essere un indirizzo IP valido", + }, + { + ns: "Test.IPv4", + expected: "IPv4 deve essere un indirizzo IPv4 valido", + }, + { + ns: "Test.IPv6", + expected: "IPv6 deve essere un indirizzo IPv6 valido", + }, + { + ns: "Test.DataURI", + expected: "DataURI deve contenere un Data URI valido", + }, + { + ns: "Test.Latitude", + expected: "Latitude deve contenere una latitudine valida", + }, + { + ns: "Test.Longitude", + expected: "Longitude deve contenere una longitudine valida", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte deve contenere caratteri multibyte", + }, + { + ns: "Test.ASCII", + expected: "ASCII deve contenere solo caratteri ascii", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII deve contenere solo caratteri ascii stampabili", + }, + { + ns: "Test.UUID", + expected: "UUID deve essere un UUID valido", + }, + { + ns: "Test.UUID3", + expected: "UUID3 deve essere un UUID versione 3 valido", + }, + { + ns: "Test.UUID4", + expected: "UUID4 deve essere un UUID versione 4 valido", + }, + { + ns: "Test.UUID5", + expected: "UUID5 deve essere un UUID versione 5 valido", + }, + { + ns: "Test.ULID", + expected: "ULID deve essere un ULID valido", + }, + { + ns: "Test.ISBN", + expected: "ISBN deve essere un numero ISBN valido", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 deve essere un numero ISBN-10 valido", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 deve essere un numero ISBN-13 valido", + }, + { + ns: "Test.Excludes", + expected: "Excludes non deve contenere il testo 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll non deve contenere alcuno dei seguenti caratteri '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune non deve contenere '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny deve contenere almeno uno dei seguenti caratteri '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains deve contenere il testo 'purpose'", + }, + { + ns: "Test.Base64", + expected: "Base64 deve essere una stringa Base64 valida", + }, + { + ns: "Test.Email", + expected: "Email deve essere un indirizzo email valido", + }, + { + ns: "Test.URL", + expected: "URL deve essere un URL valido", + }, + { + ns: "Test.URI", + expected: "URI deve essere un URI valido", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString deve essere un colore RGB valido", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString deve essere un colore RGBA valido", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString deve essere un colore HSL valido", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString deve essere un colore HSLA valido", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString deve essere un esadecimale valido", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString deve essere un colore HEX valido", + }, + { + ns: "Test.NumberString", + expected: "NumberString deve essere un numero valido", + }, + { + ns: "Test.NumericString", + expected: "NumericString deve essere un valore numerico valido", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString può contenere solo caratteri alfanumerici", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString può contenere solo caratteri alfabetici", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString deve essere minore di MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString deve essere minore o uguale a MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString deve essere maggiore di MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString deve essere maggiore o uguale a MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString deve essere diverso da EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString deve essere minore di Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString deve essere minore o uguale a Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString deve essere maggiore di Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString deve essere maggiore o uguale a Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString deve essere diverso da Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString deve essere uguale a Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString deve essere uguale a MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString deve essere lungo almeno 3 caratteri", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber deve essere maggiore o uguale a 5,56", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple deve contenere almeno 2 elementi", + }, + { + ns: "Test.GteTime", + expected: "GteTime deve essere uguale o successivo alla Data/Ora corrente", + }, + { + ns: "Test.GtString", + expected: "GtString deve essere lungo più di 3 caratteri", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber deve essere maggiore di 5,56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple deve contenere più di 2 elementi", + }, + { + ns: "Test.GtTime", + expected: "GtTime deve essere successivo alla Data/Ora corrente", + }, + { + ns: "Test.LteString", + expected: "LteString deve essere lungo al massimo 3 caratteri", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber deve essere minore o uguale a 5,56", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple deve contenere al massimo 2 elementi", + }, + { + ns: "Test.LteTime", + expected: "LteTime deve essere uguale o precedente alla Data/Ora corrente", + }, + { + ns: "Test.LtString", + expected: "LtString deve essere lungo meno di 3 caratteri", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber deve essere minore di 5,56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple deve contenere meno di 2 elementi", + }, + { + ns: "Test.LtTime", + expected: "LtTime deve essere precedente alla Data/Ora corrente", + }, + { + ns: "Test.NeString", + expected: "NeString deve essere diverso da ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber deve essere diverso da 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple deve essere diverso da 0", + }, + { + ns: "Test.EqString", + expected: "EqString non è uguale a 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber non è uguale a 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple non è uguale a 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString deve essere lungo al massimo 3 caratteri", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber deve essere minore o uguale a 1.113,00", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple deve contenere al massimo 7 elementi", + }, + { + ns: "Test.MinString", + expected: "MinString deve essere lungo almeno 1 carattere", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber deve essere maggiore o uguale a 1.113,00", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple deve contenere almeno 7 elementi", + }, + { + ns: "Test.LenString", + expected: "LenString deve essere lungo 1 carattere", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber deve essere uguale a 1.113,00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple deve contenere 7 elementi", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString è un campo obbligatorio", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber è un campo obbligatorio", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple è un campo obbligatorio", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen deve essere lungo almeno 10 caratteri", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen deve essere lungo al massimo 1 carattere", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen deve essere lungo 2 caratteri", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt deve essere lungo meno di 1 carattere", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte deve essere lungo al massimo 1 carattere", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt deve essere lungo più di 10 caratteri", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte deve essere lungo almeno 10 caratteri", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString deve essere uno di [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt deve essere uno di [5 63]", + }, + { + ns: "Test.UniqueSlice", + expected: "UniqueSlice deve contenere valori unici", + }, + { + ns: "Test.UniqueArray", + expected: "UniqueArray deve contenere valori unici", + }, + { + ns: "Test.UniqueMap", + expected: "UniqueMap deve contenere valori unici", + }, + { + ns: "Test.BooleanString", + expected: "BooleanString deve rappresentare un valore booleano", + }, + { + ns: "Test.JSONString", + expected: "JSONString deve essere una stringa json valida", + }, + { + ns: "Test.JWTString", + expected: "JWTString deve essere una stringa jwt valida", + }, + { + ns: "Test.LowercaseString", + expected: "LowercaseString deve essere una stringa minuscola", + }, + { + ns: "Test.UppercaseString", + expected: "UppercaseString deve essere una stringa maiuscola", + }, + { + ns: "Test.StartsWithString", + expected: "StartsWithString deve iniziare con foo", + }, + { + ns: "Test.StartsNotWithString", + expected: "StartsNotWithString non deve iniziare con foo", + }, + { + ns: "Test.EndsWithString", + expected: "EndsWithString deve terminare con foo", + }, + { + ns: "Test.EndsNotWithString", + expected: "EndsNotWithString non deve terminare con foo", + }, + { + ns: "Test.Datetime", + expected: "Datetime non corrisponde al formato 2006-01-02", + }, + { + ns: "Test.PostCode", + expected: "PostCode non corrisponde al formato del codice postale dello stato SG", + }, + { + ns: "Test.PostCodeByField", + expected: "PostCodeByField non corrisponde al formato del codice postale dello stato nel campo PostCodeCountry", + }, + } + + 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/ja/ja.go b/translations/ja/ja.go index 7b887c0..1cc67a4 100644 --- a/translations/ja/ja.go +++ b/translations/ja/ja.go @@ -136,7 +136,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return } - if err = ut.Add("min-number", "{0}は{1}かより大きくなければなりません", false); err != nil { + if err = ut.Add("min-number", "{0}は{1}より大きくなければなりません", false); err != nil { return } @@ -227,7 +227,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return } - if err = ut.Add("max-number", "{0}は{1}かより小さくなければなりません", false); err != nil { + if err = ut.Add("max-number", "{0}は{1}より小さくなければなりません", false); err != nil { return } @@ -525,7 +525,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return } - if err = ut.Add("lte-number", "{0}は{1}かより小さくなければなりません", false); err != nil { + if err = ut.Add("lte-number", "{0}は{1}より小さくなければなりません", false); err != nil { return } @@ -765,7 +765,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return } - if err = ut.Add("gte-number", "{0}は{1}かより大きくなければなりません", false); err != nil { + if err = ut.Add("gte-number", "{0}は{1}より大きくなければなりません", false); err != nil { return } @@ -1229,6 +1229,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0}はバージョンが5の正しいUUIDでなければなりません", override: false, }, + { + tag: "ulid", + translation: "{0}は正しいULIDでなければなりません", + override: false, + }, { tag: "ascii", translation: "{0}はASCII文字のみを含まなければなりません", diff --git a/translations/ja/ja_test.go b/translations/ja/ja_test.go index eaa2743..7950251 100644 --- a/translations/ja/ja_test.go +++ b/translations/ja/ja_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" ja_locale "github.com/go-playground/locales/ja" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -306,6 +307,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5はバージョンが5の正しいUUIDでなければなりません", }, + { + ns: "Test.ULID", + expected: "ULIDは正しいULIDでなければなりません", + }, { ns: "Test.ISBN", expected: "ISBNは正しいISBN番号でなければなりません", @@ -448,7 +453,7 @@ func TestTranslations(t *testing.T) { }, { ns: "Test.GteNumber", - expected: "GteNumberは5.56かより大きくなければなりません", + expected: "GteNumberは5.56より大きくなければなりません", }, { ns: "Test.GteMultiple", @@ -480,7 +485,7 @@ func TestTranslations(t *testing.T) { }, { ns: "Test.LteNumber", - expected: "LteNumberは5.56かより小さくなければなりません", + expected: "LteNumberは5.56より小さくなければなりません", }, { ns: "Test.LteMultiple", @@ -536,7 +541,7 @@ func TestTranslations(t *testing.T) { }, { ns: "Test.MaxNumber", - expected: "MaxNumberは1,113.00かより小さくなければなりません", + expected: "MaxNumberは1,113.00より小さくなければなりません", }, { ns: "Test.MaxMultiple", @@ -548,7 +553,7 @@ func TestTranslations(t *testing.T) { }, { ns: "Test.MinNumber", - expected: "MinNumberは1,113.00かより大きくなければなりません", + expected: "MinNumberは1,113.00より大きくなければなりません", }, { ns: "Test.MinMultiple", diff --git a/translations/nl/nl.go b/translations/nl/nl.go index a64815d..ca7c554 100644 --- a/translations/nl/nl.go +++ b/translations/nl/nl.go @@ -1173,6 +1173,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} moet een geldige versie 5 UUID zijn", override: false, }, + { + tag: "ulid", + translation: "{0} moet een geldige ULID zijn", + override: false, + }, { tag: "ascii", translation: "{0} mag alleen ascii karakters bevatten", diff --git a/translations/nl/nl_test.go b/translations/nl/nl_test.go index 55f5048..137e0c4 100644 --- a/translations/nl/nl_test.go +++ b/translations/nl/nl_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" english "github.com/go-playground/locales/en" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -306,6 +307,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 moet een geldige versie 5 UUID zijn", }, + { + ns: "Test.ULID", + expected: "ULID moet een geldige ULID zijn", + }, { ns: "Test.ISBN", expected: "ISBN moet een geldig ISBN nummer zijn", diff --git a/translations/pt/pt.go b/translations/pt/pt.go index 9dbaf5f..4748331 100644 --- a/translations/pt/pt.go +++ b/translations/pt/pt.go @@ -1178,6 +1178,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} deve ser um UUID versão 5 válido", override: false, }, + { + tag: "ulid", + translation: "{0} deve ser um ULID válido", + override: false, + }, { tag: "ascii", translation: "{0} deve conter apenas caracteres ascii", diff --git a/translations/pt/pt_test.go b/translations/pt/pt_test.go index ceec15f..0422ab0 100644 --- a/translations/pt/pt_test.go +++ b/translations/pt/pt_test.go @@ -105,6 +105,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -321,6 +322,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 deve ser um UUID versão 5 válido", }, + { + ns: "Test.ULID", + expected: "ULID deve ser um ULID válido", + }, { ns: "Test.ISBN", expected: "ISBN deve ser um número de ISBN válido", diff --git a/translations/pt_BR/pt_BR.go b/translations/pt_BR/pt_BR.go index df4fc67..d6883aa 100644 --- a/translations/pt_BR/pt_BR.go +++ b/translations/pt_BR/pt_BR.go @@ -1173,6 +1173,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} deve ser um UUID versão 5 válido", override: false, }, + { + tag: "ulid", + translation: "{0} deve ser uma ULID válida", + override: false, + }, { tag: "ascii", translation: "{0} deve conter apenas caracteres ascii", @@ -1311,6 +1316,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return s }, }, + { + tag: "boolean", + translation: "{0} deve ser um valor booleano válido", + override: false, + }, } for _, t := range translations { diff --git a/translations/pt_BR/pt_BR_test.go b/translations/pt_BR/pt_BR_test.go index 32125f6..426f246 100644 --- a/translations/pt_BR/pt_BR_test.go +++ b/translations/pt_BR/pt_BR_test.go @@ -4,10 +4,10 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" brazilian_portuguese "github.com/go-playground/locales/pt_BR" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" - . "github.com/go-playground/assert/v2" ) func TestTranslations(t *testing.T) { @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -138,6 +139,7 @@ func TestTranslations(t *testing.T) { StrPtrGte *string `validate:"gte=10"` OneOfString string `validate:"oneof=red green"` OneOfInt int `validate:"oneof=5 63"` + BooleanString string `validate:"boolean"` } var test Test @@ -170,6 +172,7 @@ func TestTranslations(t *testing.T) { test.AlphanumString = "abc3!" test.NumericString = "12E.00" test.NumberString = "12E" + test.BooleanString = "A" test.Excludes = "este é um texto de teste" test.ExcludesAll = "Isso é Ótimo!" @@ -306,6 +309,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 deve ser um UUID versão 5 válido", }, + { + ns: "Test.ULID", + expected: "ULID deve ser uma ULID válida", + }, { ns: "Test.ISBN", expected: "ISBN deve ser um número ISBN válido", @@ -614,6 +621,10 @@ func TestTranslations(t *testing.T) { ns: "Test.OneOfInt", expected: "OneOfInt deve ser um de [5 63]", }, + { + ns: "Test.BooleanString", + expected: "BooleanString deve ser um valor booleano válido", + }, } for _, tt := range tests { diff --git a/translations/ru/ru.go b/translations/ru/ru.go index 7a12bd8..25400c5 100644 --- a/translations/ru/ru.go +++ b/translations/ru/ru.go @@ -1291,6 +1291,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} должен быть UUID 5 версии", override: false, }, + { + tag: "ulid", + translation: "{0} должен быть ULID", + override: false, + }, { tag: "ascii", translation: "{0} должен содержать только ascii символы", diff --git a/translations/ru/ru_test.go b/translations/ru/ru_test.go index e1a1069..c74f0d5 100644 --- a/translations/ru/ru_test.go +++ b/translations/ru/ru_test.go @@ -120,6 +120,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -340,6 +341,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 должен быть UUID 5 версии", }, + { + ns: "Test.ULID", + expected: "ULID должен быть ULID", + }, { ns: "Test.ISBN", expected: "ISBN должен быть ISBN номером", diff --git a/translations/tr/tr.go b/translations/tr/tr.go index 2709e4b..2e88a20 100644 --- a/translations/tr/tr.go +++ b/translations/tr/tr.go @@ -1173,6 +1173,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} geçerli bir sürüm 5 UUID olmalıdır", override: false, }, + { + tag: "ulid", + translation: "{0} geçerli bir ULID olmalıdır", + override: false, + }, { tag: "ascii", translation: "{0} yalnızca ascii karakterler içermelidir", diff --git a/translations/tr/tr_test.go b/translations/tr/tr_test.go index 7cfa5a4..b72329e 100644 --- a/translations/tr/tr_test.go +++ b/translations/tr/tr_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" turkish "github.com/go-playground/locales/tr" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -312,6 +313,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 geçerli bir sürüm 5 UUID olmalıdır", }, + { + ns: "Test.ULID", + expected: "ULID geçerli bir ULID olmalıdır", + }, { ns: "Test.ISBN", expected: "ISBN geçerli bir ISBN numarası olmalıdır", diff --git a/translations/zh/zh.go b/translations/zh/zh.go index 5fff6b4..80165d0 100644 --- a/translations/zh/zh.go +++ b/translations/zh/zh.go @@ -29,6 +29,36 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0}为必填字段", override: false, }, + { + tag: "required_if", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_unless", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_with", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_with_all", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_without", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_without_all", + translation: "{0}为必填字段", + override: false, + }, { tag: "len", customRegisFunc: func(ut ut.Translator) (err error) { @@ -1225,6 +1255,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0}必须是一个有效的V5 UUID", override: false, }, + { + tag: "ulid", + translation: "{0}必须是一个有效的ULID", + override: false, + }, { tag: "ascii", translation: "{0}必须只包含ascii字符", diff --git a/translations/zh/zh_test.go b/translations/zh/zh_test.go index 1d73c65..cf76590 100644 --- a/translations/zh/zh_test.go +++ b/translations/zh/zh_test.go @@ -109,6 +109,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -327,6 +328,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5必须是一个有效的V5 UUID", }, + { + ns: "Test.ULID", + expected: "ULID必须是一个有效的ULID", + }, { ns: "Test.ISBN", expected: "ISBN必须是一个有效的ISBN编号", diff --git a/translations/zh_tw/zh_tw.go b/translations/zh_tw/zh_tw.go index 391a010..c39a8d7 100644 --- a/translations/zh_tw/zh_tw.go +++ b/translations/zh_tw/zh_tw.go @@ -1166,6 +1166,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0}必須是一個有效的V5 UUID", override: false, }, + { + tag: "ulid", + translation: "{0}必須是一個有效的ULID", + override: false, + }, { tag: "ascii", translation: "{0}必須只包含ascii字元", diff --git a/translations/zh_tw/zh_tw_test.go b/translations/zh_tw/zh_tw_test.go index cf60d1d..129c51a 100644 --- a/translations/zh_tw/zh_tw_test.go +++ b/translations/zh_tw/zh_tw_test.go @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -309,6 +310,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5必須是一個有效的V5 UUID", }, + { + ns: "Test.ULID", + expected: "ULID必須是一個有效的ULID", + }, { ns: "Test.ISBN", expected: "ISBN必須是一個有效的ISBN編號", diff --git a/util.go b/util.go index 56420f4..36da855 100644 --- a/util.go +++ b/util.go @@ -82,7 +82,7 @@ BEGIN: fld := namespace var ns string - if typ != timeType { + if !typ.ConvertibleTo(timeType) { idx := strings.Index(namespace, namespaceSeparator) diff --git a/validator.go b/validator.go index 2a4fad0..80da095 100644 --- a/validator.go +++ b/validator.go @@ -164,7 +164,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr typ = current.Type() - if typ != timeType { + if !typ.ConvertibleTo(timeType) { if ct != nil { @@ -355,6 +355,10 @@ OUTER: v.ct = ct if ct.fn(ctx, v) { + if ct.isBlockEnd { + ct = ct.next + continue OUTER + } // drain rest of the 'or' values, then continue or leave for { @@ -368,6 +372,11 @@ OUTER: if ct.typeof != typeOr { continue OUTER } + + if ct.isBlockEnd { + ct = ct.next + continue OUTER + } } } diff --git a/validator_instance.go b/validator_instance.go index 973964f..316ffb0 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -33,6 +33,8 @@ const ( excludedWithoutTag = "excluded_without" excludedWithTag = "excluded_with" excludedWithAllTag = "excluded_with_all" + excludedIfTag = "excluded_if" + excludedUnlessTag = "excluded_unless" skipValidationTag = "-" diveTag = "dive" keysTag = "keys" @@ -120,7 +122,7 @@ func New() *Validate { switch k { // these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag, - excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag: + excludedIfTag, excludedUnlessTag, excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag: _ = v.registerValidation(k, wrapFunc(val), true, true) default: // no need to error check here, baked in will always be valid @@ -169,7 +171,7 @@ func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{ return errs } -// ValidateMap validates map data form a map of tags +// ValidateMap validates map data from a map of tags func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{} { return v.ValidateMapCtx(context.Background(), data, rules) } @@ -331,7 +333,7 @@ func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) { val = val.Elem() } - if val.Kind() != reflect.Struct || val.Type() == timeType { + if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) { return &InvalidValidationError{Type: reflect.TypeOf(s)} } @@ -376,7 +378,7 @@ func (v *Validate) StructFilteredCtx(ctx context.Context, s interface{}, fn Filt val = val.Elem() } - if val.Kind() != reflect.Struct || val.Type() == timeType { + if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) { return &InvalidValidationError{Type: reflect.TypeOf(s)} } @@ -424,7 +426,7 @@ func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields . val = val.Elem() } - if val.Kind() != reflect.Struct || val.Type() == timeType { + if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) { return &InvalidValidationError{Type: reflect.TypeOf(s)} } @@ -514,7 +516,7 @@ func (v *Validate) StructExceptCtx(ctx context.Context, s interface{}, fields .. val = val.Elem() } - if val.Kind() != reflect.Struct || val.Type() == timeType { + if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) { return &InvalidValidationError{Type: reflect.TypeOf(s)} } diff --git a/validator_test.go b/validator_test.go index f694203..8abe0ce 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4269,6 +4269,46 @@ func TestUUIDRFC4122Validation(t *testing.T) { } } +func TestULIDValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"01BX5ZZKBKACT-V9WEVGEMMVRZ", false}, + {"01bx5zzkbkactav9wevgemmvrz", false}, + {"a987Fbc9-4bed-3078-cf07-9141ba07c9f3xxx", false}, + {"01BX5ZZKBKACTAV9WEVGEMMVRZABC", false}, + {"01BX5ZZKBKACTAV9WEVGEMMVRZABC", false}, + {"0IBX5ZZKBKACTAV9WEVGEMMVRZ", false}, + {"O1BX5ZZKBKACTAV9WEVGEMMVRZ", false}, + {"01BX5ZZKBKACTAVLWEVGEMMVRZ", false}, + {"01BX5ZZKBKACTAV9WEVGEMMVRZ", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "ulid") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ULID failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ULID failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ulid" { + t.Fatalf("Index: %d ULID failed Error: %s", i, errs) + } + } + } + } +} + func TestISBNValidation(t *testing.T) { tests := []struct { param string @@ -4981,6 +5021,28 @@ func TestIsEqFieldValidation(t *testing.T) { Equal(t, errs, nil) } +func TestIsEqFieldValidationWithAliasTime(t *testing.T) { + var errs error + validate := New() + + type CustomTime time.Time + + type Test struct { + Start CustomTime `validate:"eqfield=End"` + End *time.Time + } + + now := time.Now().UTC() + + sv := &Test{ + Start: CustomTime(now), + End: &now, + } + + errs = validate.Struct(sv) + Equal(t, errs, nil) +} + func TestIsEqValidation(t *testing.T) { var errs error validate := New() @@ -9111,6 +9173,7 @@ func TestHostnameRFC1123Validation(t *testing.T) { {"test.example24.com.", false}, {"test24.example24.com.", false}, {"example.", false}, + {"test_example", false}, {"192.168.0.1", true}, {"email@example.com", false}, {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, @@ -9159,6 +9222,7 @@ func TestHostnameRFC1123AliasValidation(t *testing.T) { {"test.example24.com.", false}, {"test24.example24.com.", false}, {"example.", false}, + {"test_example", false}, {"192.168.0.1", true}, {"email@example.com", false}, {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, @@ -10635,6 +10699,145 @@ func TestRequiredWithoutAll(t *testing.T) { AssertError(t, errs, "Field2", "Field2", "Field2", "Field2", "required_without_all") } +func TestExcludedIf(t *testing.T) { + validate := New() + type Inner struct { + Field *string + } + + test1 := struct { + FieldE string `validate:"omitempty" json:"field_e"` + FieldER *string `validate:"excluded_if=FieldE test" json:"field_er"` + }{ + FieldE: "test", + } + errs := validate.Struct(test1) + Equal(t, errs, nil) + + test2 := struct { + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"excluded_if=FieldE test" json:"field_er"` + }{ + FieldE: "notest", + } + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "FieldER", "FieldER", "FieldER", "FieldER", "excluded_if") + + shouldError := "shouldError" + test3 := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + Field1 int `validate:"excluded_if=Inner.Field test" json:"field_1"` + }{ + Inner: &Inner{Field: &shouldError}, + } + errs = validate.Struct(test3) + NotEqual(t, errs, nil) + ve = errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "Field1", "Field1", "Field1", "Field1", "excluded_if") + + shouldPass := "test" + test4 := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + Field1 int `validate:"excluded_if=Inner.Field test" json:"field_1"` + }{ + Inner: &Inner{Field: &shouldPass}, + } + errs = validate.Struct(test4) + Equal(t, errs, nil) + + // Checks number of params in struct tag is correct + defer func() { + if r := recover(); r == nil { + t.Errorf("panicTest should have panicked!") + } + }() + fieldVal := "panicTest" + panicTest := struct { + Inner *Inner + Field1 string `validate:"excluded_if=Inner.Field" json:"field_1"` + }{ + Inner: &Inner{Field: &fieldVal}, + } + _ = validate.Struct(panicTest) +} + +func TestExcludedUnless(t *testing.T) { + validate := New() + type Inner struct { + Field *string + } + + fieldVal := "test" + test := struct { + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"` + }{ + FieldE: "notest", + FieldER: "filled", + } + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"` + }{ + FieldE: "test", + FieldER: "filled", + } + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "FieldER", "FieldER", "FieldER", "FieldER", "excluded_unless") + + shouldError := "test" + test3 := struct { + Inner *Inner + Field1 string `validate:"excluded_unless=Inner.Field test" json:"field_1"` + }{ + Inner: &Inner{Field: &shouldError}, + Field1: "filled", + } + errs = validate.Struct(test3) + NotEqual(t, errs, nil) + ve = errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "Field1", "Field1", "Field1", "Field1", "excluded_unless") + + shouldPass := "shouldPass" + test4 := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_unless=Inner.Field test" json:"field_1"` + }{ + Inner: &Inner{Field: &shouldPass}, + Field1: "filled", + } + errs = validate.Struct(test4) + Equal(t, errs, nil) + + // Checks number of params in struct tag is correct + defer func() { + if r := recover(); r == nil { + t.Errorf("panicTest should have panicked!") + } + }() + panicTest := struct { + Inner *Inner + Field1 string `validate:"excluded_unless=Inner.Field" json:"field_1"` + }{ + Inner: &Inner{Field: &fieldVal}, + } + _ = validate.Struct(panicTest) +} + func TestLookup(t *testing.T) { type Lookup struct { FieldA *string `json:"fieldA,omitempty" validate:"required_without=FieldB"` @@ -11053,12 +11256,15 @@ func TestIsIso3166Alpha3Validation(t *testing.T) { func TestIsIso3166AlphaNumericValidation(t *testing.T) { tests := []struct { - value int + value interface{} expected bool }{ {248, true}, + {"248", true}, {0, false}, {1, false}, + {"1", false}, + {"invalid_int", false}, } validate := New() @@ -11079,8 +11285,41 @@ func TestIsIso3166AlphaNumericValidation(t *testing.T) { } PanicMatches(t, func() { - _ = validate.Var("1", "iso3166_1_alpha_numeric") - }, "Bad field type string") + _ = validate.Var([]string{"1"}, "iso3166_1_alpha_numeric") + }, "Bad field type []string") +} + +func TestCountryCodeValidation(t *testing.T) { + tests := []struct { + value interface{} + expected bool + }{ + {248, true}, + {0, false}, + {1, false}, + {"POL", true}, + {"NO", true}, + {"248", true}, + {"1", false}, + {"0", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.value, "country_code") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d country_code failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d country_code failed Error: %s", i, errs) + } + } + } } func TestIsIso4217Validation(t *testing.T) { @@ -11307,6 +11546,123 @@ func TestBicIsoFormatValidation(t *testing.T) { } } +func TestSemverFormatValidation(t *testing.T) { + tests := []struct { + value string `validate:"semver"` + tag string + expected bool + }{ + {"1.2.3", "semver", true}, + {"10.20.30", "semver", true}, + {"1.1.2-prerelease+meta", "semver", true}, + {"1.1.2+meta", "semver", true}, + {"1.1.2+meta-valid", "semver", true}, + {"1.0.0-alpha", "semver", true}, + {"1.0.0-alpha.1", "semver", true}, + {"1.0.0-alpha.beta", "semver", true}, + {"1.0.0-alpha.beta.1", "semver", true}, + {"1.0.0-alpha0.valid", "semver", true}, + {"1.0.0-alpha.0valid", "semver", true}, + {"1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay", "semver", true}, + {"1.0.0-rc.1+build.1", "semver", true}, + {"1.0.0-rc.1+build.123", "semver", true}, + {"1.2.3-beta", "semver", true}, + {"1.2.3-DEV-SNAPSHOT", "semver", true}, + {"1.2.3-SNAPSHOT-123", "semver", true}, + {"2.0.0+build.1848", "semver", true}, + {"2.0.1-alpha.1227", "semver", true}, + {"1.0.0-alpha+beta", "semver", true}, + {"1.2.3----RC-SNAPSHOT.12.9.1--.12+788", "semver", true}, + {"1.2.3----R-S.12.9.1--.12+meta", "semver", true}, + {"1.2.3----RC-SNAPSHOT.12.9.1--.12", "semver", true}, + {"1.0.0+0.build.1-rc.10000aaa-kk-0.1", "semver", true}, + {"99999999999999999999999.999999999999999999.99999999999999999", "semver", true}, + {"1.0.0-0A.is.legal", "semver", true}, + {"1", "semver", false}, + {"1.2", "semver", false}, + {"1.2.3-0123", "semver", false}, + {"1.2.3-0123.0123", "semver", false}, + {"1.1.2+.123", "semver", false}, + {"+invalid", "semver", false}, + {"-invalid", "semver", false}, + {"-invalid+invalid", "semver", false}, + {"alpha", "semver", false}, + {"alpha.beta.1", "semver", false}, + {"alpha.1", "semver", false}, + {"1.0.0-alpha_beta", "semver", false}, + {"1.0.0-alpha_beta", "semver", false}, + {"1.0.0-alpha...1", "semver", false}, + {"01.1.1", "semver", false}, + {"1.01.1", "semver", false}, + {"1.1.01", "semver", false}, + {"1.2", "semver", false}, + {"1.2.Dev", "semver", false}, + {"1.2.3.Dev", "semver", false}, + {"1.2-SNAPSHOT", "semver", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.value, test.tag) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d semver failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d semver failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "semver" { + t.Fatalf("Index: %d semver failed Error: %s", i, errs) + } + } + } + } +} + +func TestRFC1035LabelFormatValidation(t *testing.T) { + tests := []struct { + value string `validate:"dns_rfc1035_label"` + tag string + expected bool + }{ + {"abc", "dns_rfc1035_label", true}, + {"abc-", "dns_rfc1035_label", false}, + {"abc-123", "dns_rfc1035_label", true}, + {"ABC", "dns_rfc1035_label", false}, + {"ABC-123", "dns_rfc1035_label", false}, + {"abc-abc", "dns_rfc1035_label", true}, + {"ABC-ABC", "dns_rfc1035_label", false}, + {"123-abc", "dns_rfc1035_label", false}, + {"", "dns_rfc1035_label", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.value, test.tag) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d dns_rfc1035_label failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d dns_rfc1035_label failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "dns_rfc1035_label" { + t.Fatalf("Index: %d dns_rfc1035_label failed Error: %s", i, errs) + } + } + } + } +} + func TestPostCodeByIso3166Alpha2(t *testing.T) { tests := map[string][]struct { value string @@ -11418,3 +11774,65 @@ func TestPostCodeByIso3166Alpha2Field_InvalidKind(t *testing.T) { _ = New().Struct(test{"ABC", 123, false}) t.Errorf("Didn't panic as expected") } + +func TestCreditCardFormatValidation(t *testing.T) { + tests := []struct { + value string `validate:"credit_card"` + tag string + expected bool + }{ + {"586824160825533338", "credit_card", true}, + {"586824160825533328", "credit_card", false}, + {"4624748233249780", "credit_card", true}, + {"4624748233349780", "credit_card", false}, + {"378282246310005", "credit_card", true}, + {"378282146310005", "credit_card", false}, + {"4624 7482 3324 9780", "credit_card", true}, + {"4624 7482 3324 9780", "credit_card", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.value, test.tag) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d credit_card failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d credit_card failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "credit_card" { + t.Fatalf("Index: %d credit_card failed Error: %s", i, errs) + } + } + } + } +} + +func TestMultiOrOperatorGroup(t *testing.T) { + tests := []struct { + Value int `validate:"eq=1|gte=5,eq=1|lt=7"` + expected bool + }{ + {1, true}, {2, false}, {5, true}, {6, true}, {8, false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Struct(test) + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d multi_group_of_OR_operators failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d multi_group_of_OR_operators should have errs", i) + } + } + } + }