diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..24b80ce --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @go-playground/validator-maintainers \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 0104499..40b774a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,6 @@ +- [ ] I have looked at the documentation [here](https://pkg.go.dev/github.com/go-playground/validator/v10#section-documentation) first? +- [ ] I have looked at the examples provided that may showcase my question [here](/_examples)? + ### Package version eg. v9, v10: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6bb0db3..d99afdb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,4 +4,4 @@ **Make sure that you've checked the boxes below before you submit PR:** - [ ] Tests exist or have been written that cover this particular change. -@go-playground/admins \ No newline at end of file +@go-playground/validator-maintainers \ No newline at end of file diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 0025ea0..acf873d 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -8,7 +8,7 @@ jobs: test: strategy: matrix: - go-version: [1.14.x, 1.15.x] + go-version: [1.15.x, 1.16.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.15.x' + if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.16.x' uses: shogo82148/actions-goveralls@v1 with: path-to-profile: profile.cov @@ -45,4 +45,4 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.31 + version: v1.39 diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..b809c4c --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,16 @@ +## Maintainers Guide + +### Semantic Versioning +Semantic versioning as defined [here](https://semver.org) must be strictly adhered to. + +### External Dependencies +Any new external dependencies MUST: +- Have a compatible LICENSE present. +- Be actively maintained. +- Be approved by @go-playground/admins + +### PR Merge Requirements +- Up-to-date branch. +- Passing tests and linting. +- CODEOWNERS approval. +- Tests that cover both the Happy and Unhappy paths. \ No newline at end of file diff --git a/Makefile b/Makefile index 19c91ed..164e8bb 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,11 @@ GOCMD=GO111MODULE=on go linters-install: @golangci-lint --version >/dev/null 2>&1 || { \ echo "installing linting tools..."; \ - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.21.0; \ + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.39.0; \ } lint: linters-install - $(PWD)/bin/golangci-lint run + golangci-lint run test: $(GOCMD) test -cover -race ./... diff --git a/README.md b/README.md index 04fbb3c..d64e5ae 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.4.1-green.svg) +![Project status](https://img.shields.io/badge/version-10.6.0-green.svg) [![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) @@ -27,11 +27,11 @@ Installation Use go get. - go get github.com/go-playground/validator/v10 + go get github.com/go-playground/validator Then import the validator package into your own code. - import "github.com/go-playground/validator/v10" + import "github.com/go-playground/validator" Error Return Value ------- @@ -53,7 +53,7 @@ validationErrors := err.(validator.ValidationErrors) Usage and documentation ------ -Please see https://godoc.org/github.com/go-playground/validator for detailed usage docs. +Please see https://pkg.go.dev/github.com/go-playground/validator/v10 for detailed usage docs. ##### Examples: @@ -295,5 +295,10 @@ How to Contribute Make a pull request... License ------- +------- Distributed under MIT License, please see license file within the code for more details. + +Maintainers +----------- +This project has grown large enough that more than one person is required to properly support the community. +If you are interested in becoming a maintainer please reach out to me https://github.com/deankarn \ No newline at end of file diff --git a/_examples/map-validation/main.go b/_examples/map-validation/main.go new file mode 100644 index 0000000..92e282f --- /dev/null +++ b/_examples/map-validation/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "github.com/go-playground/validator/v10" +) + +var validate *validator.Validate + +func main() { + validate = validator.New() + + validateMap() + validateNestedMap() +} + +func validateMap() { + user := map[string]interface{}{"name": "Arshiya Kiani", "email": "zytel3301@gmail.com"} + + // Every rule will be applied to the item of the data that the offset of rule is pointing to. + // So if you have a field "email": "omitempty,required,email", the validator will apply these + // rules to offset of email in user data + rules := map[string]interface{}{"name": "required,min=8,max=32", "email": "omitempty,required,email"} + + // ValidateMap will return map[string]error. + // The offset of every item in errs is the name of invalid field and the value + // is the message of error. If there was no error, ValidateMap method will + // return an EMPTY map of errors, not nil. If you want to check that + // if there was an error or not, you must check the length of the return value + errs := validate.ValidateMap(user, rules) + + if len(errs) > 0 { + fmt.Println(errs) + // The user is invalid + } + + // The user is valid +} + +func validateNestedMap() { + + data := map[string]interface{}{ + "name": "Arshiya Kiani", + "email": "zytel3301@gmail.com", + "details": map[string]interface{}{ + "family_members": map[string]interface{}{ + "father_name": "Micheal", + "mother_name": "Hannah", + }, + "salary": "1000", + }, + } + + // Rules must be set as the structure as the data itself. If you want to dive into the + // map, just declare its rules as a map + rules := map[string]interface{}{ + "name": "min=4,max=32", + "email": "required,email", + "details": map[string]interface{}{ + "family_members": map[string]interface{}{ + "father_name": "required,min=4,max=32", + "mother_name": "required,min=4,max=32", + }, + "salary": "number", + }, + } + + if len(validate.ValidateMap(data, rules)) == 0 { + // Data is valid + } + + // Data is invalid +} diff --git a/_examples/struct-level/main.go b/_examples/struct-level/main.go index 3231a81..6002a3b 100644 --- a/_examples/struct-level/main.go +++ b/_examples/struct-level/main.go @@ -44,7 +44,7 @@ func main() { // register validation for 'User' // NOTE: only have to register a non-pointer type for 'User', validator - // interanlly dereferences during it's type checks. + // internally dereferences during it's type checks. validate.RegisterStructValidation(UserStructLevelValidation, User{}) // build 'User' info, normally posted data etc... diff --git a/baked_in.go b/baked_in.go index 6ce762d..c46e6cd 100644 --- a/baked_in.go +++ b/baked_in.go @@ -18,6 +18,7 @@ import ( "unicode/utf8" "golang.org/x/crypto/sha3" + "golang.org/x/text/language" urn "github.com/leodido/go-urn" ) @@ -67,127 +68,131 @@ var ( // you can add, remove or even replace items to suite your needs, // or even disregard and use your own map if so desired. bakedInValidators = map[string]Func{ - "required": hasValue, - "required_if": requiredIf, - "required_unless": requiredUnless, - "required_with": requiredWith, - "required_with_all": requiredWithAll, - "required_without": requiredWithout, - "required_without_all": requiredWithoutAll, - "excluded_with": excludedWith, - "excluded_with_all": excludedWithAll, - "excluded_without": excludedWithout, - "excluded_without_all": excludedWithoutAll, - "isdefault": isDefault, - "len": hasLengthOf, - "min": hasMinOf, - "max": hasMaxOf, - "eq": isEq, - "ne": isNe, - "lt": isLt, - "lte": isLte, - "gt": isGt, - "gte": isGte, - "eqfield": isEqField, - "eqcsfield": isEqCrossStructField, - "necsfield": isNeCrossStructField, - "gtcsfield": isGtCrossStructField, - "gtecsfield": isGteCrossStructField, - "ltcsfield": isLtCrossStructField, - "ltecsfield": isLteCrossStructField, - "nefield": isNeField, - "gtefield": isGteField, - "gtfield": isGtField, - "ltefield": isLteField, - "ltfield": isLtField, - "fieldcontains": fieldContains, - "fieldexcludes": fieldExcludes, - "alpha": isAlpha, - "alphanum": isAlphanum, - "alphaunicode": isAlphaUnicode, - "alphanumunicode": isAlphanumUnicode, - "numeric": isNumeric, - "number": isNumber, - "hexadecimal": isHexadecimal, - "hexcolor": isHEXColor, - "rgb": isRGB, - "rgba": isRGBA, - "hsl": isHSL, - "hsla": isHSLA, - "e164": isE164, - "email": isEmail, - "url": isURL, - "uri": isURI, - "urn_rfc2141": isUrnRFC2141, // RFC 2141 - "file": isFile, - "base64": isBase64, - "base64url": isBase64URL, - "contains": contains, - "containsany": containsAny, - "containsrune": containsRune, - "excludes": excludes, - "excludesall": excludesAll, - "excludesrune": excludesRune, - "startswith": startsWith, - "endswith": endsWith, - "startsnotwith": startsNotWith, - "endsnotwith": endsNotWith, - "isbn": isISBN, - "isbn10": isISBN10, - "isbn13": isISBN13, - "eth_addr": isEthereumAddress, - "btc_addr": isBitcoinAddress, - "btc_addr_bech32": isBitcoinBech32Address, - "uuid": isUUID, - "uuid3": isUUID3, - "uuid4": isUUID4, - "uuid5": isUUID5, - "uuid_rfc4122": isUUIDRFC4122, - "uuid3_rfc4122": isUUID3RFC4122, - "uuid4_rfc4122": isUUID4RFC4122, - "uuid5_rfc4122": isUUID5RFC4122, - "ascii": isASCII, - "printascii": isPrintableASCII, - "multibyte": hasMultiByteCharacter, - "datauri": isDataURI, - "latitude": isLatitude, - "longitude": isLongitude, - "ssn": isSSN, - "ipv4": isIPv4, - "ipv6": isIPv6, - "ip": isIP, - "cidrv4": isCIDRv4, - "cidrv6": isCIDRv6, - "cidr": isCIDR, - "tcp4_addr": isTCP4AddrResolvable, - "tcp6_addr": isTCP6AddrResolvable, - "tcp_addr": isTCPAddrResolvable, - "udp4_addr": isUDP4AddrResolvable, - "udp6_addr": isUDP6AddrResolvable, - "udp_addr": isUDPAddrResolvable, - "ip4_addr": isIP4AddrResolvable, - "ip6_addr": isIP6AddrResolvable, - "ip_addr": isIPAddrResolvable, - "unix_addr": isUnixAddrResolvable, - "mac": isMAC, - "hostname": isHostnameRFC952, // RFC 952 - "hostname_rfc1123": isHostnameRFC1123, // RFC 1123 - "fqdn": isFQDN, - "unique": isUnique, - "oneof": isOneOf, - "html": isHTML, - "html_encoded": isHTMLEncoded, - "url_encoded": isURLEncoded, - "dir": isDir, - "json": isJSON, - "hostname_port": isHostnamePort, - "lowercase": isLowercase, - "uppercase": isUppercase, - "datetime": isDatetime, - "timezone": isTimeZone, - "iso3166_1_alpha2": isIso3166Alpha2, - "iso3166_1_alpha3": isIso3166Alpha3, - "iso3166_1_alpha_numeric": isIso3166AlphaNumeric, + "required": hasValue, + "required_if": requiredIf, + "required_unless": requiredUnless, + "required_with": requiredWith, + "required_with_all": requiredWithAll, + "required_without": requiredWithout, + "required_without_all": requiredWithoutAll, + "excluded_with": excludedWith, + "excluded_with_all": excludedWithAll, + "excluded_without": excludedWithout, + "excluded_without_all": excludedWithoutAll, + "isdefault": isDefault, + "len": hasLengthOf, + "min": hasMinOf, + "max": hasMaxOf, + "eq": isEq, + "ne": isNe, + "lt": isLt, + "lte": isLte, + "gt": isGt, + "gte": isGte, + "eqfield": isEqField, + "eqcsfield": isEqCrossStructField, + "necsfield": isNeCrossStructField, + "gtcsfield": isGtCrossStructField, + "gtecsfield": isGteCrossStructField, + "ltcsfield": isLtCrossStructField, + "ltecsfield": isLteCrossStructField, + "nefield": isNeField, + "gtefield": isGteField, + "gtfield": isGtField, + "ltefield": isLteField, + "ltfield": isLtField, + "fieldcontains": fieldContains, + "fieldexcludes": fieldExcludes, + "alpha": isAlpha, + "alphanum": isAlphanum, + "alphaunicode": isAlphaUnicode, + "alphanumunicode": isAlphanumUnicode, + "numeric": isNumeric, + "number": isNumber, + "hexadecimal": isHexadecimal, + "hexcolor": isHEXColor, + "rgb": isRGB, + "rgba": isRGBA, + "hsl": isHSL, + "hsla": isHSLA, + "e164": isE164, + "email": isEmail, + "url": isURL, + "uri": isURI, + "urn_rfc2141": isUrnRFC2141, // RFC 2141 + "file": isFile, + "base64": isBase64, + "base64url": isBase64URL, + "contains": contains, + "containsany": containsAny, + "containsrune": containsRune, + "excludes": excludes, + "excludesall": excludesAll, + "excludesrune": excludesRune, + "startswith": startsWith, + "endswith": endsWith, + "startsnotwith": startsNotWith, + "endsnotwith": endsNotWith, + "isbn": isISBN, + "isbn10": isISBN10, + "isbn13": isISBN13, + "eth_addr": isEthereumAddress, + "btc_addr": isBitcoinAddress, + "btc_addr_bech32": isBitcoinBech32Address, + "uuid": isUUID, + "uuid3": isUUID3, + "uuid4": isUUID4, + "uuid5": isUUID5, + "uuid_rfc4122": isUUIDRFC4122, + "uuid3_rfc4122": isUUID3RFC4122, + "uuid4_rfc4122": isUUID4RFC4122, + "uuid5_rfc4122": isUUID5RFC4122, + "ascii": isASCII, + "printascii": isPrintableASCII, + "multibyte": hasMultiByteCharacter, + "datauri": isDataURI, + "latitude": isLatitude, + "longitude": isLongitude, + "ssn": isSSN, + "ipv4": isIPv4, + "ipv6": isIPv6, + "ip": isIP, + "cidrv4": isCIDRv4, + "cidrv6": isCIDRv6, + "cidr": isCIDR, + "tcp4_addr": isTCP4AddrResolvable, + "tcp6_addr": isTCP6AddrResolvable, + "tcp_addr": isTCPAddrResolvable, + "udp4_addr": isUDP4AddrResolvable, + "udp6_addr": isUDP6AddrResolvable, + "udp_addr": isUDPAddrResolvable, + "ip4_addr": isIP4AddrResolvable, + "ip6_addr": isIP6AddrResolvable, + "ip_addr": isIPAddrResolvable, + "unix_addr": isUnixAddrResolvable, + "mac": isMAC, + "hostname": isHostnameRFC952, // RFC 952 + "hostname_rfc1123": isHostnameRFC1123, // RFC 1123 + "fqdn": isFQDN, + "unique": isUnique, + "oneof": isOneOf, + "html": isHTML, + "html_encoded": isHTMLEncoded, + "url_encoded": isURLEncoded, + "dir": isDir, + "json": isJSON, + "hostname_port": isHostnamePort, + "lowercase": isLowercase, + "uppercase": isUppercase, + "datetime": isDatetime, + "timezone": isTimeZone, + "iso3166_1_alpha2": isIso3166Alpha2, + "iso3166_1_alpha3": isIso3166Alpha3, + "iso3166_1_alpha_numeric": isIso3166AlphaNumeric, + "bcp47_language_tag": isBCP47LanguageTag, + "postcode_iso3166_alpha2": isPostcodeByIso3166Alpha2, + "postcode_iso3166_alpha2_field": isPostcodeByIso3166Alpha2Field, + "bic": isIsoBicFormat, } ) @@ -547,7 +552,7 @@ func isEthereumAddress(fl FieldLevel) bool { return false } - if ethaddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) { + if ethAddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) { return true } @@ -791,6 +796,9 @@ func isNeField(fl FieldLevel) bool { case reflect.Slice, reflect.Map, reflect.Array: return int64(field.Len()) != int64(currentField.Len()) + case reflect.Bool: + return field.Bool() != currentField.Bool() + case reflect.Struct: fieldType := field.Type() @@ -1033,6 +1041,9 @@ func isNeCrossStructField(fl FieldLevel) bool { case reflect.Slice, reflect.Map, reflect.Array: return int64(topField.Len()) != int64(field.Len()) + case reflect.Bool: + return topField.Bool() != field.Bool() + case reflect.Struct: fieldType := field.Type() @@ -1080,6 +1091,9 @@ func isEqCrossStructField(fl FieldLevel) bool { case reflect.Slice, reflect.Map, reflect.Array: return int64(topField.Len()) == int64(field.Len()) + case reflect.Bool: + return topField.Bool() == field.Bool() + case reflect.Struct: fieldType := field.Type() @@ -1127,6 +1141,9 @@ func isEqField(fl FieldLevel) bool { case reflect.Slice, reflect.Map, reflect.Array: return int64(field.Len()) == int64(currentField.Len()) + case reflect.Bool: + return field.Bool() == currentField.Bool() + case reflect.Struct: fieldType := field.Type() @@ -1190,6 +1207,47 @@ func isEq(fl FieldLevel) bool { panic(fmt.Sprintf("Bad field type %T", field.Interface())) } +// isPostcodeByIso3166Alpha2 validates by value which is country code in iso 3166 alpha 2 +// example: `postcode_iso3166_alpha2=US` +func isPostcodeByIso3166Alpha2(fl FieldLevel) bool { + field := fl.Field() + param := fl.Param() + + reg, found := postCodeRegexDict[param] + if !found { + return false + } + + return reg.MatchString(field.String()) +} + +// isPostcodeByIso3166Alpha2 validates by field which represents for a value of country code in iso 3166 alpha 2 +// example: `postcode_iso3166_alpha2_field=CountryCode` +func isPostcodeByIso3166Alpha2Field(fl FieldLevel) bool { + field := fl.Field() + params := parseOneOfParam2(fl.Param()) + + if len(params) != 1 { + return false + } + + currentField, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), params[0]) + if !found { + return false + } + + if kind != reflect.String { + panic(fmt.Sprintf("Bad field type %T", currentField.Interface())) + } + + reg, found := postCodeRegexDict[currentField.String()] + if !found { + return false + } + + return reg.MatchString(field.String()) +} + // IsBase64 is the validation function for validating if the current field's value is a valid base 64. func isBase64(fl FieldLevel) bool { return base64Regex.MatchString(fl.Field().String()) @@ -1330,7 +1388,7 @@ func isRGB(fl FieldLevel) bool { // IsHEXColor is the validation function for validating if the current field's value is a valid HEX color. func isHEXColor(fl FieldLevel) bool { - return hexcolorRegex.MatchString(fl.Field().String()) + return hexColorRegex.MatchString(fl.Field().String()) } // IsHexadecimal is the validation function for validating if the current field's value is a valid hexadecimal. @@ -1441,6 +1499,9 @@ func requireCheckFieldValue(fl FieldLevel, param string, value string, defaultNo case reflect.Slice, reflect.Map, reflect.Array: return int64(field.Len()) == asInt(value) + + case reflect.Bool: + return field.Bool() == asBool(value) } // default reflect.String: @@ -1544,7 +1605,7 @@ func requiredWithout(fl FieldLevel) bool { return true } -// RequiredWithoutAll is the validation function +// ExcludedWithoutAll is the validation function // The field under validation must not be present or is empty when all of the other specified fields are not present. func excludedWithoutAll(fl FieldLevel) bool { params := parseOneOfParam2(fl.Param()) @@ -2283,3 +2344,22 @@ func isIso3166AlphaNumeric(fl FieldLevel) bool { } return iso3166_1_alpha_numeric[code] } + +// isBCP47LanguageTag is the validation function for validating if the current field's value is a valid BCP 47 language tag, as parsed by language.Parse +func isBCP47LanguageTag(fl FieldLevel) bool { + field := fl.Field() + + if field.Kind() == reflect.String { + _, err := language.Parse(field.String()) + return err == nil + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// isIsoBicFormat is the validation function for validating if the current field's value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362 +func isIsoBicFormat(fl FieldLevel) bool { + bicString := fl.Field().String() + + return bicRegex.MatchString(bicString) +} diff --git a/doc.go b/doc.go index a816c20..eafad0d 100644 --- a/doc.go +++ b/doc.go @@ -1221,6 +1221,20 @@ see: https://www.iso.org/iso-3166-country-codes.html Usage: iso3166_1_alpha3 +BCP 47 Language Tag + +This validates that a string value is a valid BCP 47 language tag, as parsed by language.Parse. +More information on https://pkg.go.dev/golang.org/x/text/language + + Usage: bcp47_language_tag + +BIC (SWIFT code) + +This validates that a string value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362. +More information on https://www.iso.org/standard/60390.html + + Usage: bic + TimeZone This validates that a string value is a valid time zone based on the time zone database present on the system. @@ -1228,7 +1242,7 @@ Although empty value and Local value are allowed by time.LoadLocation golang fun More information on https://golang.org/pkg/time/#LoadLocation Usage: timezone - + Alias Validators and Tags diff --git a/errors.go b/errors.go index be5b9af..0771e13 100644 --- a/errors.go +++ b/errors.go @@ -82,7 +82,7 @@ func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslati // FieldError contains all functions to get error details type FieldError interface { - // returns the validation tag that failed. if the + // Tag returns the validation tag that failed. if the // validation was an alias, this will return the // alias name and not the underlying tag that failed. // @@ -90,7 +90,7 @@ type FieldError interface { // will return "iscolor" Tag() string - // returns the validation tag that failed, even if an + // ActualTag returns the validation tag that failed, even if an // alias the actual tag within the alias will be returned. // If an 'or' validation fails the entire or will be returned. // @@ -98,7 +98,7 @@ type FieldError interface { // will return "hexcolor|rgb|rgba|hsl|hsla" ActualTag() string - // returns the namespace for the field error, with the tag + // Namespace returns the namespace for the field error, with the tag // name taking precedence over the field's actual name. // // eg. JSON name "User.fname" @@ -109,7 +109,7 @@ type FieldError interface { // using validate.Field(...) as there is no way to extract it's name Namespace() string - // returns the namespace for the field error, with the field's + // StructNamespace returns the namespace for the field error, with the field's // actual name. // // eq. "User.FirstName" see Namespace for comparison @@ -118,24 +118,24 @@ type FieldError interface { // using validate.Field(...) as there is no way to extract its name StructNamespace() string - // returns the fields name with the tag name taking precedence over the + // Field returns the fields name with the tag name taking precedence over the // field's actual name. // // eq. JSON name "fname" // see StructField for comparison Field() string - // returns the field's actual name from the struct, when able to determine. + // StructField returns the field's actual name from the struct, when able to determine. // // eq. "FirstName" // see Field for comparison StructField() string - // returns the actual field's value in case needed for creating the error + // Value returns the actual field's value in case needed for creating the error // message Value() interface{} - // returns the param value, in string form for comparison; this will also + // Param returns the param value, in string form for comparison; this will also // help with generating an error message Param() string @@ -146,10 +146,10 @@ type FieldError interface { // Type returns the Field's reflect Type // - // // eg. time.Time's type is time.Time + // eg. time.Time's type is time.Time Type() reflect.Type - // returns the FieldError's translated error + // Translate returns the FieldError's translated error // from the provided 'ut.Translator' and registered 'TranslationFunc' // // NOTE: if no registered translator can be found it returns the same as @@ -221,7 +221,7 @@ func (fe *fieldError) Field() string { // return fld } -// returns the field's actual name from the struct, when able to determine. +// StructField returns the field's actual name from the struct, when able to determine. func (fe *fieldError) StructField() string { // return fe.structField return fe.structNs[len(fe.structNs)-int(fe.structfieldLen):] diff --git a/field_level.go b/field_level.go index f0e2a9a..ef35826 100644 --- a/field_level.go +++ b/field_level.go @@ -5,24 +5,25 @@ import "reflect" // FieldLevel contains all the information and helper functions // to validate a field type FieldLevel interface { - // returns the top level struct, if any + + // Top returns the top level struct, if any Top() reflect.Value - // returns the current fields parent struct, if any or + // Parent returns the current fields parent struct, if any or // the comparison value if called 'VarWithValue' Parent() reflect.Value - // returns current field for validation + // Field returns current field for validation Field() reflect.Value - // returns the field's name with the tag + // FieldName returns the field's name with the tag // name taking precedence over the fields actual name. FieldName() string - // returns the struct field's name + // StructFieldName returns the struct field's name StructFieldName() string - // returns param for validation against current field + // Param returns param for validation against current field Param() string // GetTag returns the current validations tag name @@ -33,7 +34,7 @@ type FieldLevel interface { // underlying value and it's kind. ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool) - // traverses the parent struct to retrieve a specific field denoted by the provided namespace + // GetStructFieldOK traverses the parent struct to retrieve a specific field denoted by the provided namespace // in the param and returns the field, field kind and whether is was successful in retrieving // the field at all. // @@ -49,7 +50,7 @@ type FieldLevel interface { // Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable. GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) - // traverses the parent struct to retrieve a specific field denoted by the provided namespace + // GetStructFieldOK2 traverses the parent struct to retrieve a specific field denoted by the provided namespace // in the param and returns the field, field kind, if it's a nullable type and whether is was successful in retrieving // the field at all. // @@ -57,7 +58,7 @@ type FieldLevel interface { // could not be retrieved because it didn't exist. GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool) - // GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for + // GetStructFieldOKAdvanced2 is the same as GetStructFieldOK except that it accepts the parent struct to start looking for // the field and namespace allowing more extensibility for validators. GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool) } @@ -107,12 +108,12 @@ func (v *validate) GetStructFieldOKAdvanced(val reflect.Value, namespace string) return current, kind, found } -// GetStructFieldOK returns Param returns param for validation against current field +// GetStructFieldOK2 returns Param returns param for validation against current field func (v *validate) GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool) { return v.getStructFieldOKInternal(v.slflParent, v.ct.param) } -// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for +// GetStructFieldOKAdvanced2 is the same as GetStructFieldOK except that it accepts the parent struct to start looking for // the field and namespace allowing more extensibility for validators. func (v *validate) GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool) { return v.getStructFieldOKInternal(val, namespace) diff --git a/go.mod b/go.mod index d457100..53c4820 100644 --- a/go.mod +++ b/go.mod @@ -8,4 +8,5 @@ require ( github.com/go-playground/universal-translator v0.17.0 github.com/leodido/go-urn v1.2.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/text v0.3.2 // indirect ) diff --git a/go.sum b/go.sum index 0152642..4b00cf6 100644 --- a/go.sum +++ b/go.sum @@ -10,17 +10,22 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/postcode_regexes.go b/postcode_regexes.go new file mode 100644 index 0000000..e7e7b68 --- /dev/null +++ b/postcode_regexes.go @@ -0,0 +1,173 @@ +package validator + +import "regexp" + +var postCodePatternDict = map[string]string{ + "GB": `^GIR[ ]?0AA|((AB|AL|B|BA|BB|BD|BH|BL|BN|BR|BS|BT|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}))|BFPO[ ]?\d{1,4}$`, + "JE": `^JE\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, + "GG": `^GY\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, + "IM": `^IM\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, + "US": `^\d{5}([ \-]\d{4})?$`, + "CA": `^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ ]?\d[ABCEGHJ-NPRSTV-Z]\d$`, + "DE": `^\d{5}$`, + "JP": `^\d{3}-\d{4}$`, + "FR": `^\d{2}[ ]?\d{3}$`, + "AU": `^\d{4}$`, + "IT": `^\d{5}$`, + "CH": `^\d{4}$`, + "AT": `^\d{4}$`, + "ES": `^\d{5}$`, + "NL": `^\d{4}[ ]?[A-Z]{2}$`, + "BE": `^\d{4}$`, + "DK": `^\d{4}$`, + "SE": `^\d{3}[ ]?\d{2}$`, + "NO": `^\d{4}$`, + "BR": `^\d{5}[\-]?\d{3}$`, + "PT": `^\d{4}([\-]\d{3})?$`, + "FI": `^\d{5}$`, + "AX": `^22\d{3}$`, + "KR": `^\d{3}[\-]\d{3}$`, + "CN": `^\d{6}$`, + "TW": `^\d{3}(\d{2})?$`, + "SG": `^\d{6}$`, + "DZ": `^\d{5}$`, + "AD": `^AD\d{3}$`, + "AR": `^([A-HJ-NP-Z])?\d{4}([A-Z]{3})?$`, + "AM": `^(37)?\d{4}$`, + "AZ": `^\d{4}$`, + "BH": `^((1[0-2]|[2-9])\d{2})?$`, + "BD": `^\d{4}$`, + "BB": `^(BB\d{5})?$`, + "BY": `^\d{6}$`, + "BM": `^[A-Z]{2}[ ]?[A-Z0-9]{2}$`, + "BA": `^\d{5}$`, + "IO": `^BBND 1ZZ$`, + "BN": `^[A-Z]{2}[ ]?\d{4}$`, + "BG": `^\d{4}$`, + "KH": `^\d{5}$`, + "CV": `^\d{4}$`, + "CL": `^\d{7}$`, + "CR": `^\d{4,5}|\d{3}-\d{4}$`, + "HR": `^\d{5}$`, + "CY": `^\d{4}$`, + "CZ": `^\d{3}[ ]?\d{2}$`, + "DO": `^\d{5}$`, + "EC": `^([A-Z]\d{4}[A-Z]|(?:[A-Z]{2})?\d{6})?$`, + "EG": `^\d{5}$`, + "EE": `^\d{5}$`, + "FO": `^\d{3}$`, + "GE": `^\d{4}$`, + "GR": `^\d{3}[ ]?\d{2}$`, + "GL": `^39\d{2}$`, + "GT": `^\d{5}$`, + "HT": `^\d{4}$`, + "HN": `^(?:\d{5})?$`, + "HU": `^\d{4}$`, + "IS": `^\d{3}$`, + "IN": `^\d{6}$`, + "ID": `^\d{5}$`, + "IL": `^\d{5}$`, + "JO": `^\d{5}$`, + "KZ": `^\d{6}$`, + "KE": `^\d{5}$`, + "KW": `^\d{5}$`, + "LA": `^\d{5}$`, + "LV": `^\d{4}$`, + "LB": `^(\d{4}([ ]?\d{4})?)?$`, + "LI": `^(948[5-9])|(949[0-7])$`, + "LT": `^\d{5}$`, + "LU": `^\d{4}$`, + "MK": `^\d{4}$`, + "MY": `^\d{5}$`, + "MV": `^\d{5}$`, + "MT": `^[A-Z]{3}[ ]?\d{2,4}$`, + "MU": `^(\d{3}[A-Z]{2}\d{3})?$`, + "MX": `^\d{5}$`, + "MD": `^\d{4}$`, + "MC": `^980\d{2}$`, + "MA": `^\d{5}$`, + "NP": `^\d{5}$`, + "NZ": `^\d{4}$`, + "NI": `^((\d{4}-)?\d{3}-\d{3}(-\d{1})?)?$`, + "NG": `^(\d{6})?$`, + "OM": `^(PC )?\d{3}$`, + "PK": `^\d{5}$`, + "PY": `^\d{4}$`, + "PH": `^\d{4}$`, + "PL": `^\d{2}-\d{3}$`, + "PR": `^00[679]\d{2}([ \-]\d{4})?$`, + "RO": `^\d{6}$`, + "RU": `^\d{6}$`, + "SM": `^4789\d$`, + "SA": `^\d{5}$`, + "SN": `^\d{5}$`, + "SK": `^\d{3}[ ]?\d{2}$`, + "SI": `^\d{4}$`, + "ZA": `^\d{4}$`, + "LK": `^\d{5}$`, + "TJ": `^\d{6}$`, + "TH": `^\d{5}$`, + "TN": `^\d{4}$`, + "TR": `^\d{5}$`, + "TM": `^\d{6}$`, + "UA": `^\d{5}$`, + "UY": `^\d{5}$`, + "UZ": `^\d{6}$`, + "VA": `^00120$`, + "VE": `^\d{4}$`, + "ZM": `^\d{5}$`, + "AS": `^96799$`, + "CC": `^6799$`, + "CK": `^\d{4}$`, + "RS": `^\d{6}$`, + "ME": `^8\d{4}$`, + "CS": `^\d{5}$`, + "YU": `^\d{5}$`, + "CX": `^6798$`, + "ET": `^\d{4}$`, + "FK": `^FIQQ 1ZZ$`, + "NF": `^2899$`, + "FM": `^(9694[1-4])([ \-]\d{4})?$`, + "GF": `^9[78]3\d{2}$`, + "GN": `^\d{3}$`, + "GP": `^9[78][01]\d{2}$`, + "GS": `^SIQQ 1ZZ$`, + "GU": `^969[123]\d([ \-]\d{4})?$`, + "GW": `^\d{4}$`, + "HM": `^\d{4}$`, + "IQ": `^\d{5}$`, + "KG": `^\d{6}$`, + "LR": `^\d{4}$`, + "LS": `^\d{3}$`, + "MG": `^\d{3}$`, + "MH": `^969[67]\d([ \-]\d{4})?$`, + "MN": `^\d{6}$`, + "MP": `^9695[012]([ \-]\d{4})?$`, + "MQ": `^9[78]2\d{2}$`, + "NC": `^988\d{2}$`, + "NE": `^\d{4}$`, + "VI": `^008(([0-4]\d)|(5[01]))([ \-]\d{4})?$`, + "VN": `^[0-9]{1,6}$`, + "PF": `^987\d{2}$`, + "PG": `^\d{3}$`, + "PM": `^9[78]5\d{2}$`, + "PN": `^PCRN 1ZZ$`, + "PW": `^96940$`, + "RE": `^9[78]4\d{2}$`, + "SH": `^(ASCN|STHL) 1ZZ$`, + "SJ": `^\d{4}$`, + "SO": `^\d{5}$`, + "SZ": `^[HLMS]\d{3}$`, + "TC": `^TKCA 1ZZ$`, + "WF": `^986\d{2}$`, + "XK": `^\d{5}$`, + "YT": `^976\d{2}$`, +} + +var postCodeRegexDict = map[string]*regexp.Regexp{} + +func init() { + for countryCode, pattern := range postCodePatternDict { + postCodeRegexDict[countryCode] = regexp.MustCompile(pattern) + } +} diff --git a/regexes.go b/regexes.go index b741f4e..9ecf4b1 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]{6})$" 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*\\)$" @@ -49,6 +49,7 @@ const ( hTMLEncodedRegexString = `&#[x]?([0-9a-fA-F]{2})|(>)|(<)|(")|(&)+[;]?` hTMLRegexString = `<[/]?([a-zA-Z]+).*?>` splitParamsRegexString = `'[^']*'|\S+` + bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$` ) var ( @@ -59,7 +60,7 @@ var ( numericRegex = regexp.MustCompile(numericRegexString) numberRegex = regexp.MustCompile(numberRegexString) hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString) - hexcolorRegex = regexp.MustCompile(hexcolorRegexString) + hexColorRegex = regexp.MustCompile(hexColorRegexString) rgbRegex = regexp.MustCompile(rgbRegexString) rgbaRegex = regexp.MustCompile(rgbaRegexString) hslRegex = regexp.MustCompile(hslRegexString) @@ -92,10 +93,11 @@ var ( btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32) btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32) ethAddressRegex = regexp.MustCompile(ethAddressRegexString) - ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) + ethAddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) uRLEncodedRegex = regexp.MustCompile(uRLEncodedRegexString) hTMLEncodedRegex = regexp.MustCompile(hTMLEncodedRegexString) hTMLRegex = regexp.MustCompile(hTMLRegexString) splitParamsRegex = regexp.MustCompile(splitParamsRegexString) + bicRegex = regexp.MustCompile(bicRegexString) ) diff --git a/struct_level.go b/struct_level.go index 57691ee..c0d89cf 100644 --- a/struct_level.go +++ b/struct_level.go @@ -23,18 +23,18 @@ func wrapStructLevelFunc(fn StructLevelFunc) StructLevelFuncCtx { // to validate a struct type StructLevel interface { - // returns the main validation object, in case one wants to call validations internally. + // Validator returns the main validation object, in case one wants to call validations internally. // this is so you don't have to use anonymous functions to get access to the validate // instance. Validator() *Validate - // returns the top level struct, if any + // Top returns the top level struct, if any Top() reflect.Value - // returns the current fields parent struct, if any + // Parent returns the current fields parent struct, if any Parent() reflect.Value - // returns the current struct. + // Current returns the current struct. Current() reflect.Value // ExtractType gets the actual underlying type of field value. @@ -42,7 +42,7 @@ type StructLevel interface { // underlying value and its kind. ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool) - // reports an error just by passing the field and tag information + // ReportError reports an error just by passing the field and tag information // // NOTES: // @@ -54,7 +54,7 @@ type StructLevel interface { // and process on the flip side it's up to you. ReportError(field interface{}, fieldName, structFieldName string, tag, param string) - // reports an error just by passing ValidationErrors + // ReportValidationErrors reports an error just by passing ValidationErrors // // NOTES: // diff --git a/translations/en/en.go b/translations/en/en.go index 1fdea95..3bbca51 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -16,7 +16,6 @@ import ( // 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 @@ -32,7 +31,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er { tag: "len", customRegisFunc: func(ut ut.Translator) (err error) { - if err = ut.Add("len-string", "{0} must be {1} in length", false); err != nil { return } @@ -61,10 +59,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er } return - }, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - var err error var t string @@ -123,7 +119,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er { tag: "min", customRegisFunc: func(ut ut.Translator) (err error) { - if err = ut.Add("min-string", "{0} must be at least {1} in length", false); err != nil { return } @@ -152,10 +147,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er } return - }, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - var err error var t string @@ -214,7 +207,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er { tag: "max", customRegisFunc: func(ut ut.Translator) (err error) { - if err = ut.Add("max-string", "{0} must be a maximum of {1} in length", false); err != nil { return } @@ -243,10 +235,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er } return - }, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - var err error var t string @@ -307,7 +297,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} is not equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { fmt.Printf("warning: error translating FieldError: %#v", fe) @@ -322,7 +311,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} should not be equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { fmt.Printf("warning: error translating FieldError: %#v", fe) @@ -335,7 +323,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er { tag: "lt", customRegisFunc: func(ut ut.Translator) (err error) { - if err = ut.Add("lt-string", "{0} must be less than {1} in length", false); err != nil { return } @@ -369,10 +356,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er } return - }, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - var err error var t string var f64 float64 @@ -380,7 +365,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er var kind reflect.Kind fn := func() (err error) { - if idx := strings.Index(fe.Param(), "."); idx != -1 { digits = uint64(len(fe.Param()[idx+1:])) } @@ -456,7 +440,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er { tag: "lte", customRegisFunc: func(ut ut.Translator) (err error) { - if err = ut.Add("lte-string", "{0} must be at maximum {1} in length", false); err != nil { return } @@ -492,7 +475,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return }, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - var err error var t string var f64 float64 @@ -500,7 +482,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er var kind reflect.Kind fn := func() (err error) { - if idx := strings.Index(fe.Param(), "."); idx != -1 { digits = uint64(len(fe.Param()[idx+1:])) } @@ -576,7 +557,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er { tag: "gt", customRegisFunc: func(ut ut.Translator) (err error) { - if err = ut.Add("gt-string", "{0} must be greater than {1} in length", false); err != nil { return } @@ -612,7 +592,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return }, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - var err error var t string var f64 float64 @@ -620,7 +599,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er var kind reflect.Kind fn := func() (err error) { - if idx := strings.Index(fe.Param(), "."); idx != -1 { digits = uint64(len(fe.Param()[idx+1:])) } @@ -696,7 +674,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er { tag: "gte", customRegisFunc: func(ut ut.Translator) (err error) { - if err = ut.Add("gte-string", "{0} must be at least {1} in length", false); err != nil { return } @@ -732,7 +709,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return }, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - var err error var t string var f64 float64 @@ -740,7 +716,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er var kind reflect.Kind fn := func() (err error) { - if idx := strings.Index(fe.Param(), "."); idx != -1 { digits = uint64(len(fe.Param()[idx+1:])) } @@ -818,7 +793,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -833,7 +807,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -848,7 +821,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} cannot be equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -863,7 +835,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be greater than {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -878,7 +849,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be greater than or equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -893,7 +863,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be less than {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -908,7 +877,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be less than or equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -923,7 +891,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} cannot be equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -938,7 +905,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be greater than {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -953,7 +919,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be greater than or equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -968,7 +933,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be less than {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -983,7 +947,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be less than or equal to {1}", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -1073,7 +1036,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must contain the text '{1}'", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -1088,7 +1050,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must contain at least one of the following characters '{1}'", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -1103,7 +1064,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} cannot contain the text '{1}'", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -1118,7 +1078,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} cannot contain any of the following characters '{1}'", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -1133,7 +1092,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} cannot contain the following '{1}'", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -1325,8 +1283,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er tag: "json", translation: "{0} must be a valid json string", override: false, - }, - { + }, + { tag: "lowercase", translation: "{0} must be a lowercase string", override: false, @@ -1341,7 +1299,34 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} does not match the {1} format", override: false, customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + return t + }, + }, + { + tag: "postcode_iso3166_alpha2", + translation: "{0} does not match postcode format of {1} country", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "postcode_iso3166_alpha2_field", + translation: "{0} does not match postcode format of country in {1} field", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) if err != nil { log.Printf("warning: error translating FieldError: %#v", fe) @@ -1356,17 +1341,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er 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) } @@ -1380,21 +1359,16 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er } 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) diff --git a/translations/en/en_test.go b/translations/en/en_test.go index 2b113c6..fd8df77 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_test.go @@ -11,7 +11,6 @@ import ( ) func TestTranslations(t *testing.T) { - eng := english.New() uni := ut.New(eng, eng) trans, _ := uni.GetTranslator("en") @@ -145,6 +144,9 @@ func TestTranslations(t *testing.T) { LowercaseString string `validate:"lowercase"` UppercaseString string `validate:"uppercase"` 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 @@ -656,6 +658,14 @@ func TestTranslations(t *testing.T) { ns: "Test.Datetime", expected: "Datetime does not match the 2006-01-02 format", }, + { + ns: "Test.PostCode", + expected: "PostCode does not match postcode format of SG country", + }, + { + ns: "Test.PostCodeByField", + expected: "PostCodeByField does not match postcode format of country in PostCodeCountry field", + }, } for _, tt := range tests { @@ -672,5 +682,4 @@ func TestTranslations(t *testing.T) { NotEqual(t, fe, nil) Equal(t, tt.expected, fe.Translate(trans)) } - } diff --git a/validator.go b/validator.go index f097f39..9569c0d 100644 --- a/validator.go +++ b/validator.go @@ -74,7 +74,7 @@ func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, cur } } - v.traverseField(ctx, parent, current.Field(f.idx), ns, structNs, f, f.cTags) + v.traverseField(ctx, current, current.Field(f.idx), ns, structNs, f, f.cTags) } } @@ -222,7 +222,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr structNs = append(append(structNs, cf.name...), '.') } - v.validateStruct(ctx, current, current, typ, ns, structNs, ct) + v.validateStruct(ctx, parent, current, typ, ns, structNs, ct) return } } diff --git a/validator_instance.go b/validator_instance.go index fe6a487..8e27707 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -29,6 +29,10 @@ const ( requiredWithAllTag = "required_with_all" requiredIfTag = "required_if" requiredUnlessTag = "required_unless" + excludedWithoutAllTag = "excluded_without_all" + excludedWithoutTag = "excluded_without" + excludedWithTag = "excluded_with" + excludedWithAllTag = "excluded_with_all" skipValidationTag = "-" diveTag = "dive" keysTag = "keys" @@ -111,7 +115,8 @@ 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: + case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag, + excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag: _ = v.registerValidation(k, wrapFunc(val), true, true) default: // no need to error check here, baked in will always be valid @@ -138,6 +143,33 @@ func (v *Validate) SetTagName(name string) { v.tagName = name } +// ValidateMapCtx validates a map using a map of validation rules and allows passing of contextual +// validation validation information via context.Context. +func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{}, rules map[string]interface{}) map[string]interface{} { + errs := make(map[string]interface{}) + for field, rule := range rules { + if reflect.ValueOf(rule).Kind() == reflect.Map && reflect.ValueOf(data[field]).Kind() == reflect.Map { + err := v.ValidateMapCtx(ctx, data[field].(map[string]interface{}), rule.(map[string]interface{})) + if len(err) > 0 { + errs[field] = err + } + } else if reflect.ValueOf(rule).Kind() == reflect.Map { + errs[field] = errors.New("The field: '" + field + "' is not a map to dive") + } else { + err := v.VarCtx(ctx, data[field], rule.(string)) + if err != nil { + errs[field] = err + } + } + } + return errs +} + +// ValidateMap validates map data form 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) +} + // RegisterTagNameFunc registers a function to get alternate names for StructFields. // // eg. to use the names which have been specified for JSON representations of structs, rather than normal Go field names: @@ -409,7 +441,10 @@ func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields . if len(flds) > 0 { vd.misc = append(vd.misc[0:0], name...) - vd.misc = append(vd.misc, '.') + // Don't append empty name for unnamed structs + if len(vd.misc) != 0 { + vd.misc = append(vd.misc, '.') + } for _, s := range flds { diff --git a/validator_test.go b/validator_test.go index 40abb67..8554e81 100644 --- a/validator_test.go +++ b/validator_test.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/go-playground/assert/v2" . "github.com/go-playground/assert/v2" "github.com/go-playground/locales/en" "github.com/go-playground/locales/fr" @@ -108,7 +109,6 @@ type TestSlice struct { } func AssertError(t *testing.T, err error, nsKey, structNsKey, field, structField, expectedTag string) { - errs := err.(ValidationErrors) found := false @@ -150,7 +150,6 @@ func AssertDeepError(t *testing.T, err error, nsKey, structNsKey, field, structF } func getError(err error, nsKey, structNsKey string) FieldError { - errs := err.(ValidationErrors) var fe FieldError @@ -170,7 +169,6 @@ type valuer struct { } func (v valuer) Value() (driver.Value, error) { - if v.Name == "errorme" { panic("SQL Driver Valuer error: some kind of error") // return nil, errors.New("some kind of error") @@ -189,7 +187,6 @@ type MadeUpCustomType struct { } func ValidateCustomType(field reflect.Value) interface{} { - if cust, ok := field.Interface().(MadeUpCustomType); ok { if len(cust.FirstName) == 0 || len(cust.LastName) == 0 { @@ -203,7 +200,6 @@ func ValidateCustomType(field reflect.Value) interface{} { } func OverrideIntTypeForSomeReason(field reflect.Value) interface{} { - if i, ok := field.Interface().(int); ok { if i == 1 { return "1" @@ -223,7 +219,6 @@ type CustomMadeUpStruct struct { } func ValidateValuerType(field reflect.Value) interface{} { - if valuer, ok := field.Interface().(driver.Valuer); ok { val, err := valuer.Value() @@ -261,7 +256,6 @@ type TestStruct struct { } func StructValidationTestStructSuccess(sl StructLevel) { - st := sl.Current().Interface().(TestStruct) if st.String != "good value" { @@ -270,7 +264,6 @@ func StructValidationTestStructSuccess(sl StructLevel) { } func StructValidationTestStruct(sl StructLevel) { - st := sl.Current().Interface().(TestStruct) if st.String != "bad value" { @@ -279,7 +272,6 @@ func StructValidationTestStruct(sl StructLevel) { } func StructValidationNoTestStructCustomName(sl StructLevel) { - st := sl.Current().Interface().(TestStruct) if st.String != "bad value" { @@ -288,7 +280,6 @@ func StructValidationNoTestStructCustomName(sl StructLevel) { } func StructValidationTestStructInvalid(sl StructLevel) { - st := sl.Current().Interface().(TestStruct) if st.String != "bad value" { @@ -297,7 +288,6 @@ func StructValidationTestStructInvalid(sl StructLevel) { } func StructValidationTestStructReturnValidationErrors(sl StructLevel) { - s := sl.Current().Interface().(TestStructReturnValidationErrors) errs := sl.Validator().Struct(s.Inner1.Inner2) @@ -309,7 +299,6 @@ func StructValidationTestStructReturnValidationErrors(sl StructLevel) { } func StructValidationTestStructReturnValidationErrors2(sl StructLevel) { - s := sl.Current().Interface().(TestStructReturnValidationErrors) errs := sl.Validator().Struct(s.Inner1.Inner2) @@ -337,7 +326,6 @@ type StructLevelInvalidErr struct { } func StructLevelInvalidError(sl StructLevel) { - top := sl.Top().Interface().(StructLevelInvalidErr) s := sl.Current().Interface().(StructLevelInvalidErr) @@ -359,7 +347,6 @@ func float64Ptr(v float64) *float64 { } func TestStructLevelInvalidError(t *testing.T) { - validate := New() validate.RegisterStructValidation(StructLevelInvalidError, StructLevelInvalidErr{}) @@ -383,7 +370,6 @@ func TestStructLevelInvalidError(t *testing.T) { } func TestNameNamespace(t *testing.T) { - type Inner2Namespace struct { String []string `validate:"dive,required" json:"JSONString"` } @@ -432,7 +418,6 @@ func TestNameNamespace(t *testing.T) { } func TestAnonymous(t *testing.T) { - validate := New() validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] @@ -500,7 +485,6 @@ func TestAnonymous(t *testing.T) { } func TestAnonymousSameStructDifferentTags(t *testing.T) { - validate := New() validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] @@ -545,7 +529,6 @@ func TestAnonymousSameStructDifferentTags(t *testing.T) { } func TestStructLevelReturnValidationErrors(t *testing.T) { - validate := New() validate.RegisterStructValidation(StructValidationTestStructReturnValidationErrors, TestStructReturnValidationErrors{}) @@ -575,7 +558,6 @@ func TestStructLevelReturnValidationErrors(t *testing.T) { } func TestStructLevelReturnValidationErrorsWithJSON(t *testing.T) { - validate := New() validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] @@ -632,7 +614,6 @@ func TestStructLevelReturnValidationErrorsWithJSON(t *testing.T) { } func TestStructLevelValidations(t *testing.T) { - v1 := New() v1.RegisterStructValidation(StructValidationTestStruct, TestStruct{}) @@ -666,7 +647,6 @@ func TestStructLevelValidations(t *testing.T) { } func TestAliasTags(t *testing.T) { - validate := New() validate.RegisterAlias("iscoloralias", "hexcolor|rgb|rgba|hsl|hsla") @@ -714,7 +694,6 @@ func TestAliasTags(t *testing.T) { } func TestNilValidator(t *testing.T) { - type TestStruct struct { Test string `validate:"required"` } @@ -724,7 +703,6 @@ func TestNilValidator(t *testing.T) { var val *Validate fn := func(fl FieldLevel) bool { - return fl.Parent().String() == fl.Field().String() } @@ -949,6 +927,38 @@ func TestStructPartial(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "Test", "required") + // Test for unnamed struct + testStruct := &TestStruct{ + String: "test", + } + unnamedStruct := struct { + String string `validate:"required" json:"StringVal"` + }{String: "test"} + composedUnnamedStruct := struct{ *TestStruct }{&TestStruct{String: "test"}} + + errs = validate.StructPartial(testStruct, "String") + Equal(t, errs, nil) + + errs = validate.StructPartial(unnamedStruct, "String") + Equal(t, errs, nil) + + errs = validate.StructPartial(composedUnnamedStruct, "TestStruct.String") + Equal(t, errs, nil) + + testStruct.String = "" + errs = validate.StructPartial(testStruct, "String") + NotEqual(t, errs, nil) + AssertError(t, errs, "TestStruct.String", "TestStruct.String", "String", "String", "required") + + unnamedStruct.String = "" + errs = validate.StructPartial(unnamedStruct, "String") + NotEqual(t, errs, nil) + AssertError(t, errs, "String", "String", "String", "String", "required") + + composedUnnamedStruct.String = "" + errs = validate.StructPartial(composedUnnamedStruct, "TestStruct.String") + NotEqual(t, errs, nil) + AssertError(t, errs, "TestStruct.String", "TestStruct.String", "String", "String", "required") } func TestCrossStructLteFieldValidation(t *testing.T) { @@ -1638,12 +1648,14 @@ func TestCrossStructNeFieldValidation(t *testing.T) { i := 1 j = 1 k = 1.543 + b := true arr := []string{"test"} s2 := "abcd" i2 := 1 j2 = 1 k2 = 1.543 + b2 := true arr2 := []string{"test"} arr3 := []string{"test", "test2"} now2 := now @@ -1664,6 +1676,10 @@ func TestCrossStructNeFieldValidation(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "necsfield") + errs = validate.VarWithValue(b2, b, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "necsfield") + errs = validate.VarWithValue(arr2, arr, "necsfield") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "necsfield") @@ -1802,6 +1818,7 @@ func TestCrossStructEqFieldValidation(t *testing.T) { i := 1 j = 1 k = 1.543 + b := true arr := []string{"test"} var j2 uint64 @@ -1810,6 +1827,7 @@ func TestCrossStructEqFieldValidation(t *testing.T) { i2 := 1 j2 = 1 k2 = 1.543 + b2 := true arr2 := []string{"test"} arr3 := []string{"test", "test2"} now2 := now @@ -1826,6 +1844,9 @@ func TestCrossStructEqFieldValidation(t *testing.T) { errs = validate.VarWithValue(k2, k, "eqcsfield") Equal(t, errs, nil) + errs = validate.VarWithValue(b2, b, "eqcsfield") + Equal(t, errs, nil) + errs = validate.VarWithValue(arr2, arr, "eqcsfield") Equal(t, errs, nil) @@ -1928,7 +1949,6 @@ func TestCrossStructEqFieldValidation(t *testing.T) { } func TestCrossNamespaceFieldValidation(t *testing.T) { - type SliceStruct struct { Name string } @@ -2166,7 +2186,6 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { } func TestExistsValidation(t *testing.T) { - jsonText := "{ \"truthiness2\": true }" type Thing struct { @@ -2197,7 +2216,6 @@ func TestExistsValidation(t *testing.T) { } func TestSQLValue2Validation(t *testing.T) { - validate := New() validate.RegisterCustomTypeFunc(ValidateValuerType, valuer{}, (*driver.Valuer)(nil), sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) @@ -2248,7 +2266,6 @@ func TestSQLValue2Validation(t *testing.T) { } func TestSQLValueValidation(t *testing.T) { - validate := New() validate.RegisterCustomTypeFunc(ValidateValuerType, (*driver.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) @@ -2926,7 +2943,6 @@ func TestUnixAddrValidation(t *testing.T) { } func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) { - validate := New() var m map[string]string @@ -3004,7 +3020,6 @@ func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) { } func TestDatePtrValidationIssueValidation(t *testing.T) { - type Test struct { LastViewed *time.Time Reminder *time.Time @@ -3056,7 +3071,6 @@ func TestBadKeyValidation(t *testing.T) { } func TestInterfaceErrValidation(t *testing.T) { - var v2 interface{} = 1 var v1 interface{} = v2 @@ -3230,7 +3244,6 @@ func TestInterfaceErrValidation(t *testing.T) { } func TestMapDiveValidation(t *testing.T) { - validate := New() n := map[int]interface{}{0: nil} @@ -3353,7 +3366,6 @@ func TestMapDiveValidation(t *testing.T) { } func TestArrayDiveValidation(t *testing.T) { - validate := New() arr := []string{"ok", "", "ok"} @@ -4362,7 +4374,6 @@ func TestISBN10Validation(t *testing.T) { } func TestExcludesRuneValidation(t *testing.T) { - tests := []struct { Value string `validate:"excludesrune=☻"` Tag string @@ -4390,7 +4401,6 @@ func TestExcludesRuneValidation(t *testing.T) { } func TestExcludesAllValidation(t *testing.T) { - tests := []struct { Value string `validate:"excludesall=@!{}[]"` Tag string @@ -4436,7 +4446,6 @@ func TestExcludesAllValidation(t *testing.T) { } func TestExcludesValidation(t *testing.T) { - tests := []struct { Value string `validate:"excludes=@"` Tag string @@ -4464,7 +4473,6 @@ func TestExcludesValidation(t *testing.T) { } func TestContainsRuneValidation(t *testing.T) { - tests := []struct { Value string `validate:"containsrune=☻"` Tag string @@ -4492,7 +4500,6 @@ func TestContainsRuneValidation(t *testing.T) { } func TestContainsAnyValidation(t *testing.T) { - tests := []struct { Value string `validate:"containsany=@!{}[]"` Tag string @@ -4520,7 +4527,6 @@ func TestContainsAnyValidation(t *testing.T) { } func TestContainsValidation(t *testing.T) { - tests := []struct { Value string `validate:"contains=@"` Tag string @@ -4557,6 +4563,7 @@ func TestIsNeFieldValidation(t *testing.T) { i := 1 j = 1 k = 1.543 + b := true arr := []string{"test"} now := time.Now().UTC() @@ -4566,6 +4573,7 @@ func TestIsNeFieldValidation(t *testing.T) { i2 := 3 j2 = 2 k2 = 1.5434456 + b2 := false arr2 := []string{"test", "test2"} arr3 := []string{"test"} now2 := now @@ -4582,6 +4590,9 @@ func TestIsNeFieldValidation(t *testing.T) { errs = validate.VarWithValue(k2, k, "nefield") Equal(t, errs, nil) + errs = validate.VarWithValue(b2, b, "nefield") + Equal(t, errs, nil) + errs = validate.VarWithValue(arr2, arr, "nefield") Equal(t, errs, nil) @@ -4792,6 +4803,7 @@ func TestIsEqFieldValidation(t *testing.T) { i := 1 j = 1 k = 1.543 + b := true arr := []string{"test"} now := time.Now().UTC() @@ -4801,6 +4813,7 @@ func TestIsEqFieldValidation(t *testing.T) { i2 := 1 j2 = 1 k2 = 1.543 + b2 := true arr2 := []string{"test"} arr3 := []string{"test", "test2"} now2 := now @@ -4817,6 +4830,9 @@ func TestIsEqFieldValidation(t *testing.T) { errs = validate.VarWithValue(k2, k, "eqfield") Equal(t, errs, nil) + errs = validate.VarWithValue(b2, b, "eqfield") + Equal(t, errs, nil) + errs = validate.VarWithValue(arr2, arr, "eqfield") Equal(t, errs, nil) @@ -5096,7 +5112,6 @@ func TestOneOfValidation(t *testing.T) { } func TestBase64Validation(t *testing.T) { - validate := New() s := "dW5pY29ybg==" @@ -5200,7 +5215,6 @@ func TestFileValidation(t *testing.T) { } func TestEthereumAddressValidation(t *testing.T) { - validate := New() tests := []struct { @@ -5254,7 +5268,6 @@ func TestEthereumAddressValidation(t *testing.T) { } func TestBitcoinAddressValidation(t *testing.T) { - validate := New() tests := []struct { @@ -5364,7 +5377,6 @@ func TestBitcoinAddressValidation(t *testing.T) { } func TestBitcoinBech32AddressValidation(t *testing.T) { - validate := New() tests := []struct { @@ -5415,7 +5427,6 @@ func TestBitcoinBech32AddressValidation(t *testing.T) { } func TestNoStructLevelValidation(t *testing.T) { - type Inner struct { Test string `validate:"len=5"` } @@ -5447,7 +5458,6 @@ func TestNoStructLevelValidation(t *testing.T) { } func TestStructOnlyValidation(t *testing.T) { - type Inner struct { Test string `validate:"len=5"` } @@ -6513,7 +6523,6 @@ func TestValidateByTagAndValue(t *testing.T) { Equal(t, errs, nil) fn := func(fl FieldLevel) bool { - return fl.Parent().String() == fl.Field().String() } @@ -6531,9 +6540,7 @@ func TestValidateByTagAndValue(t *testing.T) { } func TestAddFunctions(t *testing.T) { - fn := func(fl FieldLevel) bool { - return true } @@ -6562,7 +6569,6 @@ func TestAddFunctions(t *testing.T) { } func TestChangeTag(t *testing.T) { - validate := New() validate.SetTagName("val") @@ -7209,8 +7215,7 @@ func TestIsLte(t *testing.T) { } func TestUrnRFC2141(t *testing.T) { - - var tests = []struct { + tests := []struct { param string expected bool }{ @@ -7286,8 +7291,7 @@ func TestUrnRFC2141(t *testing.T) { } func TestUrl(t *testing.T) { - - var tests = []struct { + tests := []struct { param string expected bool }{ @@ -7354,8 +7358,7 @@ func TestUrl(t *testing.T) { } func TestUri(t *testing.T) { - - var tests = []struct { + tests := []struct { param string expected bool }{ @@ -7421,7 +7424,6 @@ func TestUri(t *testing.T) { } func TestOrTag(t *testing.T) { - validate := New() s := "rgba(0,31,255,0.5)" @@ -7451,7 +7453,7 @@ func TestOrTag(t *testing.T) { Equal(t, errs, nil) s = "green" - errs = validate.Var(s, "eq=|eq=blue,rgb|rgba") //should fail on first validation block + errs = validate.Var(s, "eq=|eq=blue,rgb|rgba") // should fail on first validation block NotEqual(t, errs, nil) ve := errs.(ValidationErrors) Equal(t, len(ve), 1) @@ -7464,7 +7466,6 @@ func TestOrTag(t *testing.T) { v2 := New() v2.RegisterTagNameFunc(func(fld reflect.StructField) string { - name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { @@ -7489,7 +7490,6 @@ func TestOrTag(t *testing.T) { } func TestHsla(t *testing.T) { - validate := New() s := "hsla(360,100%,100%,1)" @@ -7536,7 +7536,6 @@ func TestHsla(t *testing.T) { } func TestHsl(t *testing.T) { - validate := New() s := "hsl(360,100%,50%)" @@ -7574,7 +7573,6 @@ func TestHsl(t *testing.T) { } func TestRgba(t *testing.T) { - validate := New() s := "rgba(0,31,255,0.5)" @@ -7620,7 +7618,6 @@ func TestRgba(t *testing.T) { } func TestRgb(t *testing.T) { - validate := New() s := "rgb(0,31,255)" @@ -7662,7 +7659,6 @@ func TestRgb(t *testing.T) { } func TestEmail(t *testing.T) { - validate := New() s := "test@mail.com" @@ -7730,7 +7726,6 @@ func TestEmail(t *testing.T) { } func TestHexColor(t *testing.T) { - validate := New() s := "#fff" @@ -7758,7 +7753,6 @@ func TestHexColor(t *testing.T) { } func TestHexadecimal(t *testing.T) { - validate := New() s := "ff0044" @@ -7785,7 +7779,6 @@ func TestHexadecimal(t *testing.T) { } func TestNumber(t *testing.T) { - validate := New() s := "1" @@ -7833,7 +7826,6 @@ func TestNumber(t *testing.T) { } func TestNumeric(t *testing.T) { - validate := New() s := "1" @@ -7876,7 +7868,6 @@ func TestNumeric(t *testing.T) { } func TestAlphaNumeric(t *testing.T) { - validate := New() s := "abcd123" @@ -7894,7 +7885,6 @@ func TestAlphaNumeric(t *testing.T) { } func TestAlpha(t *testing.T) { - validate := New() s := "abcd" @@ -7924,11 +7914,9 @@ func TestAlpha(t *testing.T) { errs = validate.Var(1, "alpha") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "alpha") - } func TestStructStringValidation(t *testing.T) { - validate := New() tSuccess := &TestString{ @@ -8010,7 +7998,6 @@ func TestStructStringValidation(t *testing.T) { } func TestStructInt32Validation(t *testing.T) { - type TestInt32 struct { Required int `validate:"required"` Len int `validate:"len=10"` @@ -8074,7 +8061,6 @@ func TestStructInt32Validation(t *testing.T) { } func TestStructUint64Validation(t *testing.T) { - validate := New() tSuccess := &TestUint64{ @@ -8114,7 +8100,6 @@ func TestStructUint64Validation(t *testing.T) { } func TestStructFloat64Validation(t *testing.T) { - validate := New() tSuccess := &TestFloat64{ @@ -8154,7 +8139,6 @@ func TestStructFloat64Validation(t *testing.T) { } func TestStructSliceValidation(t *testing.T) { - validate := New() tSuccess := &TestSlice{ @@ -8204,11 +8188,9 @@ func TestStructSliceValidation(t *testing.T) { _, ok := fe.Value().([]int) Equal(t, ok, true) - } func TestInvalidStruct(t *testing.T) { - validate := New() s := &SubTest{ @@ -8233,7 +8215,6 @@ func TestInvalidStruct(t *testing.T) { } func TestInvalidValidatorFunction(t *testing.T) { - validate := New() s := &SubTest{ @@ -8244,7 +8225,6 @@ func TestInvalidValidatorFunction(t *testing.T) { } func TestCustomFieldName(t *testing.T) { - validate := New() validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("schema"), ",", 2)[0] @@ -8288,7 +8268,6 @@ func TestCustomFieldName(t *testing.T) { } func TestMutipleRecursiveExtractStructCache(t *testing.T) { - validate := New() type Recursive struct { @@ -8305,7 +8284,6 @@ func TestMutipleRecursiveExtractStructCache(t *testing.T) { ptr := fmt.Sprintf("%p", sc) for i := 0; i < 100; i++ { - go func() { <-proceed sc := validate.extractStructCache(current, name) @@ -8318,7 +8296,6 @@ func TestMutipleRecursiveExtractStructCache(t *testing.T) { // Thanks @robbrockbank, see https://github.com/go-playground/validator/issues/249 func TestPointerAndOmitEmpty(t *testing.T) { - validate := New() type Test struct { @@ -8364,7 +8341,6 @@ func TestPointerAndOmitEmpty(t *testing.T) { } func TestRequired(t *testing.T) { - validate := New() validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] @@ -8388,7 +8364,6 @@ func TestRequired(t *testing.T) { } func TestBoolEqual(t *testing.T) { - validate := New() type Test struct { @@ -8416,16 +8391,13 @@ func TestTranslations(t *testing.T) { validate := New() err := validate.RegisterTranslation("required", trans, func(ut ut.Translator) (err error) { - // using this stype because multiple translation may have to be added for the full translation if err = ut.Add("required", "{0} is a required field", false); err != nil { return } return - }, func(ut ut.Translator, fe FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field()) if err != nil { fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError)) @@ -8438,16 +8410,13 @@ func TestTranslations(t *testing.T) { err = validate.RegisterTranslation("required", fr, func(ut ut.Translator) (err error) { - // using this stype because multiple translation may have to be added for the full translation if err = ut.Add("required", "{0} est un champ obligatoire", false); err != nil { return } return - }, func(ut ut.Translator, fe FieldError) string { - t, transErr := ut.T(fe.Tag(), fe.Field()) if transErr != nil { fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError)) @@ -8525,16 +8494,13 @@ func TestTranslationErrors(t *testing.T) { validate := New() err = validate.RegisterTranslation("required", trans, func(ut ut.Translator) (err error) { - // using this stype because multiple translation may have to be added for the full translation if err = ut.Add("required", "{0} is a required field", false); err != nil { return } return - }, func(ut ut.Translator, fe FieldError) string { - t, err := ut.T(fe.Tag(), fe.Field()) if err != nil { fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError)) @@ -8549,7 +8515,6 @@ func TestTranslationErrors(t *testing.T) { } func TestStructFiltered(t *testing.T) { - p1 := func(ns []byte) bool { if bytes.HasSuffix(ns, []byte("NoTag")) || bytes.HasSuffix(ns, []byte("Required")) { return false @@ -8715,7 +8680,6 @@ func TestStructFiltered(t *testing.T) { } func TestRequiredPtr(t *testing.T) { - type Test struct { Bool *bool `validate:"required"` } @@ -8849,7 +8813,6 @@ func TestAlphaUnicodeValidation(t *testing.T) { } func TestAlphanumericUnicodeValidation(t *testing.T) { - tests := []struct { param string expected bool @@ -8892,7 +8855,6 @@ func TestAlphanumericUnicodeValidation(t *testing.T) { } func TestArrayStructNamespace(t *testing.T) { - validate := New() validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] @@ -8921,7 +8883,6 @@ func TestArrayStructNamespace(t *testing.T) { } func TestMapStructNamespace(t *testing.T) { - validate := New() validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] @@ -9602,7 +9563,6 @@ func TestURLEncodedValidation(t *testing.T) { } func TestKeys(t *testing.T) { - type Test struct { Test1 map[string]string `validate:"gt=0,dive,keys,eq=testkey,endkeys,eq=testval" json:"test1"` Test2 map[int]int `validate:"gt=0,dive,keys,eq=3,endkeys,eq=4" json:"test2"` @@ -9771,7 +9731,6 @@ func TestKeysCustomValidation(t *testing.T) { } func TestKeyOrs(t *testing.T) { - type Test struct { Test1 map[string]string `validate:"gt=0,dive,keys,eq=testkey|eq=testkeyok,endkeys,eq=testval" json:"test1"` } @@ -10028,12 +9987,15 @@ func TestRequiredUnless(t *testing.T) { Field6 uint `validate:"required_unless=Field5 2" json:"field_6"` Field7 float32 `validate:"required_unless=Field6 0" json:"field_7"` Field8 float64 `validate:"required_unless=Field7 0.0" json:"field_8"` + Field9 bool `validate:"omitempty" json:"field_9"` + Field10 string `validate:"required_unless=Field9 true" json:"field_10"` }{ FieldE: "test", Field2: &fieldVal, Field3: map[string]string{"key": "val"}, Field4: "test", Field5: 2, + Field9: true, } validate := New() @@ -10053,6 +10015,8 @@ func TestRequiredUnless(t *testing.T) { Field5 string `validate:"required_unless=Field3 0" json:"field_5"` Field6 string `validate:"required_unless=Inner.Field test" json:"field_6"` Field7 string `validate:"required_unless=Inner2.Field test" json:"field_7"` + Field8 bool `validate:"omitempty" json:"field_8"` + Field9 string `validate:"required_unless=Field8 true" json:"field_9"` }{ Inner: &Inner{Field: &fieldVal}, FieldE: "test", @@ -10063,10 +10027,11 @@ func TestRequiredUnless(t *testing.T) { NotEqual(t, errs, nil) ve := errs.(ValidationErrors) - Equal(t, len(ve), 3) + Equal(t, len(ve), 4) AssertError(t, errs, "Field3", "Field3", "Field3", "Field3", "required_unless") AssertError(t, errs, "Field4", "Field4", "Field4", "Field4", "required_unless") AssertError(t, errs, "Field7", "Field7", "Field7", "Field7", "required_unless") + AssertError(t, errs, "Field9", "Field9", "Field9", "Field9", "required_unless") defer func() { if r := recover(); r == nil { @@ -10202,6 +10167,28 @@ func TestExcludedWith(t *testing.T) { name := fmt.Sprintf("Field%d", i) AssertError(t, errs, name, name, name, name, "excluded_with") } + + test3 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with=FieldE" json:"field_1"` + Field2 *string `validate:"excluded_with=FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_with=FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_with=FieldE" json:"field_4"` + Field5 string `validate:"excluded_with=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_with=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{FieldE: "populated"}, + Inner2: &Inner{FieldE: "populated"}, + FieldE: "populated", + } + + validate = New() + + errs = validate.Struct(test3) + Equal(t, errs, nil) } func TestExcludedWithout(t *testing.T) { @@ -10266,6 +10253,26 @@ func TestExcludedWithout(t *testing.T) { name := fmt.Sprintf("Field%d", i) AssertError(t, errs, name, name, name, name, "excluded_without") } + + test3 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without=Field" json:"field_1"` + Field2 *string `validate:"excluded_without=Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_without=Field" json:"field_3"` + Field4 interface{} `validate:"excluded_without=Field" json:"field_4"` + Field5 string `validate:"excluded_without=Inner.Field" json:"field_5"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: "populated", + } + + validate = New() + + errs = validate.Struct(test3) + Equal(t, errs, nil) } func TestExcludedWithAll(t *testing.T) { @@ -10334,6 +10341,29 @@ func TestExcludedWithAll(t *testing.T) { name := fmt.Sprintf("Field%d", i) AssertError(t, errs, name, name, name, name, "excluded_with_all") } + + test3 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with_all=FieldE Field" json:"field_1"` + Field2 *string `validate:"excluded_with_all=FieldE Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_with_all=FieldE Field" json:"field_3"` + Field4 interface{} `validate:"excluded_with_all=FieldE Field" json:"field_4"` + Field5 string `validate:"excluded_with_all=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_with_all=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{FieldE: "populated"}, + Inner2: &Inner{FieldE: "populated"}, + Field: "populated", + FieldE: "populated", + } + + validate = New() + + errs = validate.Struct(test3) + Equal(t, errs, nil) } func TestExcludedWithoutAll(t *testing.T) { @@ -10352,9 +10382,10 @@ func TestExcludedWithoutAll(t *testing.T) { Field2 *string `validate:"excluded_without_all=Field FieldE" json:"field_2"` Field3 map[string]string `validate:"excluded_without_all=Field FieldE" json:"field_3"` Field4 interface{} `validate:"excluded_without_all=Field FieldE" json:"field_4"` - Field5 string `validate:"excluded_without_all=Inner.Field Inner.Field2" json:"field_5"` + Field5 string `validate:"excluded_without_all=Inner.Field Inner2.Field" json:"field_5"` }{ Inner: &Inner{Field: &fieldVal}, + Inner2: &Inner{Field: &fieldVal}, Field: "populated", Field1: fieldVal, Field2: &fieldVal, @@ -10398,6 +10429,28 @@ func TestExcludedWithoutAll(t *testing.T) { name := fmt.Sprintf("Field%d", i) AssertError(t, errs, name, name, name, name, "excluded_without_all") } + + test3 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without_all=Field FieldE" json:"field_1"` + Field2 *string `validate:"excluded_without_all=Field FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_without_all=Field FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_without_all=Field FieldE" json:"field_4"` + Field5 string `validate:"excluded_without_all=Inner.Field Inner2.Field" json:"field_5"` + }{ + Inner: &Inner{Field: &fieldVal}, + Inner2: &Inner{Field: &fieldVal}, + Field: "populated", + FieldE: "populated", + } + + validate = New() + + errs = validate.Struct(test3) + Equal(t, errs, nil) } func TestRequiredWithAll(t *testing.T) { @@ -10455,7 +10508,6 @@ func TestRequiredWithAll(t *testing.T) { } func TestRequiredWithout(t *testing.T) { - type Inner struct { Field *string } @@ -10521,7 +10573,6 @@ func TestRequiredWithout(t *testing.T) { } func TestRequiredWithoutAll(t *testing.T) { - fieldVal := "test" test := struct { Field1 string `validate:"omitempty" json:"field_1"` @@ -10578,7 +10629,6 @@ func TestLookup(t *testing.T) { } func TestAbilityToValidateNils(t *testing.T) { - type TestStruct struct { Test *string `validate:"nil"` } @@ -10720,7 +10770,6 @@ func TestJSONValidation(t *testing.T) { } func Test_hostnameport_validator(t *testing.T) { - type Host struct { Addr string `validate:"hostname_port"` } @@ -10822,7 +10871,6 @@ func TestUppercaseValidation(t *testing.T) { PanicMatches(t, func() { _ = validate.Var(2, "uppercase") }, "Bad field type int") - } func TestDatetimeValidation(t *testing.T) { @@ -11033,3 +11081,199 @@ func TestDurationType(t *testing.T) { }) } } + +func TestBCP47LanguageTagValidation(t *testing.T) { + tests := []struct { + value string `validate:"bcp47_language_tag"` + tag string + expected bool + }{ + {"en-US", "bcp47_language_tag", true}, + {"en_GB", "bcp47_language_tag", true}, + {"es", "bcp47_language_tag", true}, + {"English", "bcp47_language_tag", false}, + {"ESES", "bcp47_language_tag", false}, + {"az-Cyrl-AZ", "bcp47_language_tag", true}, + {"en-029", "bcp47_language_tag", true}, + {"xog", "bcp47_language_tag", true}, + } + + 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 locale failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d locale failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "bcp47_language_tag" { + t.Fatalf("Index: %d locale failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(2, "bcp47_language_tag") + }, "Bad field type int") +} + +func TestBicIsoFormatValidation(t *testing.T) { + tests := []struct { + value string `validate:"bic"` + tag string + expected bool + }{ + {"SBICKEN1345", "bic", true}, + {"SBICKEN1", "bic", true}, + {"SBICKENY", "bic", true}, + {"SBICKEN1YYP", "bic", true}, + {"SBIC23NXXX", "bic", false}, + {"S23CKENXXXX", "bic", false}, + {"SBICKENXX", "bic", false}, + {"SBICKENXX9", "bic", false}, + {"SBICKEN13458", "bic", false}, + {"SBICKEN", "bic", 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 bic failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d bic failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "bic" { + t.Fatalf("Index: %d bic failed Error: %s", i, errs) + } + } + } + } +} + +func TestPostCodeByIso3166Alpha2(t *testing.T) { + tests := map[string][]struct { + value string + expected bool + }{ + "VN": { + {"ABC", false}, + {"700000", true}, + {"A1", false}, + }, + "GB": { + {"EC1A 1BB", true}, + {"CF10 1B1H", false}, + }, + "VI": { + {"00803", true}, + {"1234567", false}, + }, + "LC": { // not support regexp for post code + {"123456", false}, + }, + "XX": { // not support country + {"123456", false}, + }, + } + + validate := New() + + for cc, ccTests := range tests { + for i, test := range ccTests { + errs := validate.Var(test.value, fmt.Sprintf("postcode_iso3166_alpha2=%s", cc)) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d postcode_iso3166_alpha2=%s failed Error: %s", i, cc, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d postcode_iso3166_alpha2=%s failed Error: %s", i, cc, errs) + } + } + } + } +} + +func TestPostCodeByIso3166Alpha2Field(t *testing.T) { + tests := []struct { + Value string `validate:"postcode_iso3166_alpha2_field=CountryCode"` + CountryCode interface{} + expected bool + }{ + {"ABC", "VN", false}, + {"700000", "VN", true}, + {"A1", "VN", false}, + {"EC1A 1BB", "GB", true}, + {"CF10 1B1H", "GB", false}, + {"00803", "VI", true}, + {"1234567", "VI", false}, + {"123456", "LC", false}, // not support regexp for post code + {"123456", "XX", false}, // not support country + } + + validate := New() + + for i, test := range tests { + errs := validate.Struct(test) + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d postcode_iso3166_alpha2_field=CountryCode failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d postcode_iso3166_alpha2_field=CountryCode failed Error: %s", i, errs) + } + } + } +} + +func TestPostCodeByIso3166Alpha2Field_WrongField(t *testing.T) { + type test struct { + Value string `validate:"postcode_iso3166_alpha2_field=CountryCode"` + CountryCode1 interface{} + expected bool + } + + errs := New().Struct(test{"ABC", "VN", false}) + assert.NotEqual(t, nil, errs) +} + +func TestPostCodeByIso3166Alpha2Field_MissingParam(t *testing.T) { + type test struct { + Value string `validate:"postcode_iso3166_alpha2_field="` + CountryCode1 interface{} + expected bool + } + + errs := New().Struct(test{"ABC", "VN", false}) + assert.NotEqual(t, nil, errs) +} + +func TestPostCodeByIso3166Alpha2Field_InvalidKind(t *testing.T) { + type test struct { + Value string `validate:"postcode_iso3166_alpha2_field=CountryCode"` + CountryCode interface{} + expected bool + } + defer func() { _ = recover() }() + + _ = New().Struct(test{"ABC", 123, false}) + t.Errorf("Didn't panic as expected") +}