Merge branch 'master' into master

pull/976/head
JahanviAggarwal 2 years ago committed by GitHub
commit a65040ae3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .github/workflows/workflow.yml
  2. 1
      .gitignore
  3. 24
      README.md
  4. 76
      _examples/struct-level/main.go
  5. 310
      baked_in.go
  6. 6
      country_codes.go
  7. 342
      doc.go
  8. 5
      errors.go
  9. 23
      go.mod
  10. 67
      go.sum
  11. 2
      non-standard/validators/notblank.go
  12. 2
      non-standard/validators/notblank_test.go
  13. 10
      regexes.go
  14. 15
      translations/en/en.go
  15. 11
      translations/en/en_test.go
  16. 5
      translations/es/es.go
  17. 1
      translations/es/es_test.go
  18. 5
      translations/it/it.go
  19. 122
      translations/ja/ja.go
  20. 81
      translations/ja/ja_test.go
  21. 1399
      translations/lv/lv.go
  22. 709
      translations/lv/lv_test.go
  23. 7
      translations/pt_BR/pt_BR.go
  24. 12
      translations/pt_BR/pt_BR_test.go
  25. 1
      validator.go
  26. 3
      validator_instance.go
  27. 574
      validator_test.go

@ -8,7 +8,7 @@ jobs:
test: test:
strategy: strategy:
matrix: matrix:
go-version: [1.17.x, 1.18.x] go-version: [1.19.x]
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
@ -32,7 +32,7 @@ jobs:
run: go test -race -covermode=atomic -coverprofile="profile.cov" ./... run: go test -race -covermode=atomic -coverprofile="profile.cov" ./...
- name: Send Coverage - name: Send Coverage
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.18.x' if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.19.x'
uses: shogo82148/actions-goveralls@v1 uses: shogo82148/actions-goveralls@v1
with: with:
path-to-profile: profile.cov path-to-profile: profile.cov
@ -43,9 +43,9 @@ jobs:
steps: steps:
- uses: actions/setup-go@v3 - uses: actions/setup-go@v3
with: with:
go-version: 1.18.x go-version: 1.19.x
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v3
with: with:
version: v1.45.2 version: v1.50.1

1
.gitignore vendored

@ -28,3 +28,4 @@ _testmain.go
*.txt *.txt
cover.html cover.html
README.html README.html
.idea

@ -1,7 +1,7 @@
Package validator Package validator
================= =================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) <img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v10/logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-10.11.0-green.svg) ![Project status](https://img.shields.io/badge/version-10.12.0-green.svg)
[![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator) [![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) [![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) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
@ -73,8 +73,8 @@ Baked-in Validations
| - | - | | - | - |
| eqcsfield | Field Equals Another Field (relative)| | eqcsfield | Field Equals Another Field (relative)|
| eqfield | Field Equals Another Field | | eqfield | Field Equals Another Field |
| fieldcontains | NOT DOCUMENTED IN doc.go | | fieldcontains | Check the indicated characters are present in the Field |
| fieldexcludes | NOT DOCUMENTED IN doc.go | | fieldexcludes | Check the indicated characters are not present in the field |
| gtcsfield | Field Greater Than Another Relative Field | | gtcsfield | Field Greater Than Another Relative Field |
| gtecsfield | Field Greater Than or Equal To Another Relative Field | | gtecsfield | Field Greater Than or Equal To Another Relative Field |
| gtefield | Field Greater Than or Equal To Another Field | | gtefield | Field Greater Than or Equal To Another Field |
@ -114,6 +114,7 @@ Baked-in Validations
| unix_addr | Unix domain socket end point Address | | unix_addr | Unix domain socket end point Address |
| uri | URI String | | uri | URI String |
| url | URL String | | url | URL String |
| http_url | HTTP URL String |
| url_encoded | URL Encoded | | url_encoded | URL Encoded |
| urn_rfc2141 | Urn RFC 2141 String | | urn_rfc2141 | Urn RFC 2141 String |
@ -137,7 +138,7 @@ Baked-in Validations
| excludesrune | Excludes Rune | | excludesrune | Excludes Rune |
| lowercase | Lowercase | | lowercase | Lowercase |
| multibyte | Multi-Byte Characters | | multibyte | Multi-Byte Characters |
| number | NOT DOCUMENTED IN doc.go | | number | Number |
| numeric | Numeric | | numeric | Numeric |
| printascii | Printable ASCII | | printascii | Printable ASCII |
| startsnotwith | Starts Not With | | startsnotwith | Starts Not With |
@ -149,11 +150,14 @@ Baked-in Validations
| - | - | | - | - |
| base64 | Base64 String | | base64 | Base64 String |
| base64url | Base64URL String | | base64url | Base64URL String |
| base64rawurl | Base64RawURL String |
| bic | Business Identifier Code (ISO 9362) | | bic | Business Identifier Code (ISO 9362) |
| bcp47_language_tag | Language tag (BCP 47) | | bcp47_language_tag | Language tag (BCP 47) |
| btc_addr | Bitcoin Address | | btc_addr | Bitcoin Address |
| btc_addr_bech32 | Bitcoin Bech32 Address (segwit) | | btc_addr_bech32 | Bitcoin Bech32 Address (segwit) |
| credit_card | Credit Card Number | | credit_card | Credit Card Number |
| mongodb | MongoDB ObjectID |
| cron | Cron |
| datetime | Datetime | | datetime | Datetime |
| e164 | e164 formatted phone number | | e164 | e164 formatted phone number |
| email | E-mail String | email | E-mail String
@ -176,6 +180,7 @@ Baked-in Validations
| jwt | JSON Web Token (JWT) | | jwt | JSON Web Token (JWT) |
| latitude | Latitude | | latitude | Latitude |
| longitude | Longitude | | longitude | Longitude |
| luhn_checksum | Luhn Algorithm Checksum (for strings and (u)int) |
| postcode_iso3166_alpha2 | Postcode | | postcode_iso3166_alpha2 | Postcode |
| postcode_iso3166_alpha2_field | Postcode | | postcode_iso3166_alpha2_field | Postcode |
| rgb | RGB String | | rgb | RGB String |
@ -202,22 +207,27 @@ Baked-in Validations
| tiger192 | TIGER192 hash | | tiger192 | TIGER192 hash |
| semver | Semantic Versioning 2.0.0 | | semver | Semantic Versioning 2.0.0 |
| ulid | Universally Unique Lexicographically Sortable Identifier ULID | | ulid | Universally Unique Lexicographically Sortable Identifier ULID |
| cve | Common Vulnerabilities and Exposures Identifier (CVE id) |
### Comparisons: ### Comparisons:
| Tag | Description | | Tag | Description |
| - | - | | - | - |
| eq | Equals | | eq | Equals |
| eq_ignore_case | Equals ignoring case |
| gt | Greater than| | gt | Greater than|
| gte | Greater than or equal | | gte | Greater than or equal |
| lt | Less Than | | lt | Less Than |
| lte | Less Than or Equal | | lte | Less Than or Equal |
| ne | Not Equal | | ne | Not Equal |
| ne_ignore_case | Not Equal ignoring case |
### Other: ### Other:
| Tag | Description | | Tag | Description |
| - | - | | - | - |
| dir | Directory | | dir | Existing Directory |
| file | File path | | dirpath | Directory Path |
| file | Existing File |
| filepath | File Path |
| isdefault | Is Default | | isdefault | Is Default |
| len | Length | | len | Length |
| max | Maximum | | max | Maximum |

@ -1,6 +1,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
@ -8,6 +9,36 @@ import (
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
) )
type validationError struct {
Namespace string `json:"namespace"` // can differ when a custom TagNameFunc is registered or
Field string `json:"field"` // by passing alt name to ReportError like below
StructNamespace string `json:"structNamespace"`
StructField string `json:"structField"`
Tag string `json:"tag"`
ActualTag string `json:"actualTag"`
Kind string `json:"kind"`
Type string `json:"type"`
Value string `json:"value"`
Param string `json:"param"`
Message string `json:"message"`
}
type Gender uint
const (
Male Gender = iota + 1
Female
Intersex
)
func (gender Gender) String() string {
terms := []string{"Male", "Female", "Intersex"}
if gender < Male || gender > Intersex {
return "unknown"
}
return terms[gender]
}
// User contains user information // User contains user information
type User struct { type User struct {
FirstName string `json:"fname"` FirstName string `json:"fname"`
@ -16,6 +47,7 @@ type User struct {
Email string `json:"e-mail" validate:"required,email"` Email string `json:"e-mail" validate:"required,email"`
FavouriteColor string `validate:"hexcolor|rgb|rgba"` FavouriteColor string `validate:"hexcolor|rgb|rgba"`
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
Gender Gender `json:"gender" validate:"required,gender_custom_validation"`
} }
// Address houses a users address information // Address houses a users address information
@ -47,6 +79,17 @@ func main() {
// internally dereferences during it's type checks. // internally dereferences during it's type checks.
validate.RegisterStructValidation(UserStructLevelValidation, User{}) validate.RegisterStructValidation(UserStructLevelValidation, User{})
// register a custom validation for user genre on a line
// validates that an enum is within the interval
err := validate.RegisterValidation("gender_custom_validation", func(fl validator.FieldLevel) bool {
value := fl.Field().Interface().(Gender)
return value.String() != "unknown"
})
if err != nil {
fmt.Println(err)
return
}
// build 'User' info, normally posted data etc... // build 'User' info, normally posted data etc...
address := &Address{ address := &Address{
Street: "Eavesdown Docks", Street: "Eavesdown Docks",
@ -65,7 +108,7 @@ func main() {
} }
// returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError ) // returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError )
err := validate.Struct(user) err = validate.Struct(user)
if err != nil { if err != nil {
// this check is only needed when your code could produce // this check is only needed when your code could produce
@ -77,18 +120,27 @@ func main() {
} }
for _, err := range err.(validator.ValidationErrors) { for _, err := range err.(validator.ValidationErrors) {
e := validationError{
Namespace: err.Namespace(),
Field: err.Field(),
StructNamespace: err.StructNamespace(),
StructField: err.StructField(),
Tag: err.Tag(),
ActualTag: err.ActualTag(),
Kind: fmt.Sprintf("%v", err.Kind()),
Type: fmt.Sprintf("%v", err.Type()),
Value: fmt.Sprintf("%v", err.Value()),
Param: err.Param(),
Message: err.Error(),
}
indent, err := json.MarshalIndent(e, "", " ")
if err != nil {
fmt.Println(err)
panic(err)
}
fmt.Println(err.Namespace()) // can differ when a custom TagNameFunc is registered or fmt.Println(string(indent))
fmt.Println(err.Field()) // by passing alt name to ReportError like below
fmt.Println(err.StructNamespace())
fmt.Println(err.StructField())
fmt.Println(err.Tag())
fmt.Println(err.ActualTag())
fmt.Println(err.Kind())
fmt.Println(err.Type())
fmt.Println(err.Value())
fmt.Println(err.Param())
fmt.Println()
} }
// from here you can create your own error messages in whatever language you wish // from here you can create your own error messages in whatever language you wish

@ -7,6 +7,7 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/fs"
"net" "net"
"net/url" "net/url"
"os" "os"
@ -14,13 +15,14 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"unicode/utf8" "unicode/utf8"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
"golang.org/x/text/language" "golang.org/x/text/language"
urn "github.com/leodido/go-urn" "github.com/leodido/go-urn"
) )
// Func accepts a FieldLevel interface for all validation needs. The return // Func accepts a FieldLevel interface for all validation needs. The return
@ -87,7 +89,9 @@ var (
"min": hasMinOf, "min": hasMinOf,
"max": hasMaxOf, "max": hasMaxOf,
"eq": isEq, "eq": isEq,
"eq_ignore_case": isEqIgnoreCase,
"ne": isNe, "ne": isNe,
"ne_ignore_case": isNeIgnoreCase,
"lt": isLt, "lt": isLt,
"lte": isLte, "lte": isLte,
"gt": isGt, "gt": isGt,
@ -122,11 +126,14 @@ var (
"e164": isE164, "e164": isE164,
"email": isEmail, "email": isEmail,
"url": isURL, "url": isURL,
"http_url": isHttpURL,
"uri": isURI, "uri": isURI,
"urn_rfc2141": isUrnRFC2141, // RFC 2141 "urn_rfc2141": isUrnRFC2141, // RFC 2141
"file": isFile, "file": isFile,
"filepath": isFilePath,
"base64": isBase64, "base64": isBase64,
"base64url": isBase64URL, "base64url": isBase64URL,
"base64rawurl": isBase64RawURL,
"contains": contains, "contains": contains,
"containsany": containsAny, "containsany": containsAny,
"containsrune": containsRune, "containsrune": containsRune,
@ -141,6 +148,7 @@ var (
"isbn10": isISBN10, "isbn10": isISBN10,
"isbn13": isISBN13, "isbn13": isISBN13,
"eth_addr": isEthereumAddress, "eth_addr": isEthereumAddress,
"eth_addr_checksum": isEthereumAddressChecksum,
"btc_addr": isBitcoinAddress, "btc_addr": isBitcoinAddress,
"btc_addr_bech32": isBitcoinBech32Address, "btc_addr_bech32": isBitcoinBech32Address,
"uuid": isUUID, "uuid": isUUID,
@ -195,6 +203,7 @@ var (
"html_encoded": isHTMLEncoded, "html_encoded": isHTMLEncoded,
"url_encoded": isURLEncoded, "url_encoded": isURLEncoded,
"dir": isDir, "dir": isDir,
"dirpath": isDirPath,
"json": isJSON, "json": isJSON,
"jwt": isJWT, "jwt": isJWT,
"hostname_port": isHostnamePort, "hostname_port": isHostnamePort,
@ -215,6 +224,10 @@ var (
"semver": isSemverFormat, "semver": isSemverFormat,
"dns_rfc1035_label": isDnsRFC1035LabelFormat, "dns_rfc1035_label": isDnsRFC1035LabelFormat,
"credit_card": isCreditCard, "credit_card": isCreditCard,
"cve": isCveFormat,
"luhn_checksum": hasLuhnChecksum,
"mongodb": isMongoDB,
"cron": isCron,
} }
) )
@ -308,18 +321,42 @@ func isUnique(fl FieldLevel) bool {
} }
m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type())) m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type()))
var fieldlen int
for i := 0; i < field.Len(); i++ { for i := 0; i < field.Len(); i++ {
m.SetMapIndex(reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param)), v) key := reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param))
if key.IsValid() {
fieldlen++
m.SetMapIndex(key, v)
} }
return field.Len() == m.Len() }
return fieldlen == m.Len()
case reflect.Map: case reflect.Map:
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type())) var m reflect.Value
if field.Type().Elem().Kind() == reflect.Ptr {
m = reflect.MakeMap(reflect.MapOf(field.Type().Elem().Elem(), v.Type()))
} else {
m = reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
}
for _, k := range field.MapKeys() { for _, k := range field.MapKeys() {
m.SetMapIndex(field.MapIndex(k), v) m.SetMapIndex(reflect.Indirect(field.MapIndex(k)), v)
} }
return field.Len() == m.Len() return field.Len() == m.Len()
default: default:
if parent := fl.Parent(); parent.Kind() == reflect.Struct {
uniqueField := parent.FieldByName(param)
if uniqueField == reflect.ValueOf(nil) {
panic(fmt.Sprintf("Bad field name provided %s", param))
}
if uniqueField.Kind() != field.Kind() {
panic(fmt.Sprintf("Bad field type %T:%T", field.Interface(), uniqueField.Interface()))
}
return field.Interface() != uniqueField.Interface()
}
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
} }
@ -614,14 +651,16 @@ func isISBN10(fl FieldLevel) bool {
func isEthereumAddress(fl FieldLevel) bool { func isEthereumAddress(fl FieldLevel) bool {
address := fl.Field().String() address := fl.Field().String()
if !ethAddressRegex.MatchString(address) { return ethAddressRegex.MatchString(address)
return false
} }
if ethAddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) { // isEthereumAddressChecksum is the validation function for validating if the field's value is a valid checksumed Ethereum address.
return true func isEthereumAddressChecksum(fl FieldLevel) bool {
} address := fl.Field().String()
if !ethAddressRegex.MatchString(address) {
return false
}
// Checksum validation. Reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md // Checksum validation. Reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
address = address[2:] // Skip "0x" prefix. address = address[2:] // Skip "0x" prefix.
h := sha3.NewLegacyKeccak256() h := sha3.NewLegacyKeccak256()
@ -890,6 +929,12 @@ func isNe(fl FieldLevel) bool {
return !isEq(fl) return !isEq(fl)
} }
// isNe is the validation function for validating that the field's string value does not equal the
// provided param value. The comparison is case-insensitive
func isNeIgnoreCase(fl FieldLevel) bool {
return !isEqIgnoreCase(fl)
}
// isLteCrossStructField is the validation function for validating if the current field's value is less than or equal to the field, within a separate struct, specified by the param's value. // isLteCrossStructField is the validation function for validating if the current field's value is less than or equal to the field, within a separate struct, specified by the param's value.
func isLteCrossStructField(fl FieldLevel) bool { func isLteCrossStructField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@ -1261,6 +1306,22 @@ func isEq(fl FieldLevel) bool {
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
// isEqIgnoreCase is the validation function for validating if the current field's string value is
// equal to the param's value.
// The comparison is case-insensitive.
func isEqIgnoreCase(fl FieldLevel) bool {
field := fl.Field()
param := fl.Param()
switch field.Kind() {
case reflect.String:
return strings.EqualFold(field.String(), param)
}
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
// isPostcodeByIso3166Alpha2 validates by value which is country code in iso 3166 alpha 2 // isPostcodeByIso3166Alpha2 validates by value which is country code in iso 3166 alpha 2
// example: `postcode_iso3166_alpha2=US` // example: `postcode_iso3166_alpha2=US`
func isPostcodeByIso3166Alpha2(fl FieldLevel) bool { func isPostcodeByIso3166Alpha2(fl FieldLevel) bool {
@ -1312,6 +1373,11 @@ func isBase64URL(fl FieldLevel) bool {
return base64URLRegex.MatchString(fl.Field().String()) return base64URLRegex.MatchString(fl.Field().String())
} }
// isBase64RawURL is the validation function for validating if the current field's value is a valid base64 URL safe string without '=' padding.
func isBase64RawURL(fl FieldLevel) bool {
return base64RawURLRegex.MatchString(fl.Field().String())
}
// isURI is the validation function for validating if the current field's value is a valid URI. // isURI is the validation function for validating if the current field's value is a valid URI.
func isURI(fl FieldLevel) bool { func isURI(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@ -1371,6 +1437,23 @@ func isURL(fl FieldLevel) bool {
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
// isHttpURL is the validation function for validating if the current field's value is a valid HTTP(s) URL.
func isHttpURL(fl FieldLevel) bool {
if !isURL(fl) {
return false
}
field := fl.Field()
switch field.Kind() {
case reflect.String:
s := strings.ToLower(field.String())
return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
}
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
// isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141. // isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141.
func isUrnRFC2141(fl FieldLevel) bool { func isUrnRFC2141(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@ -1388,7 +1471,7 @@ func isUrnRFC2141(fl FieldLevel) bool {
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
// isFile is the validation function for validating if the current field's value is a valid file path. // isFile is the validation function for validating if the current field's value is a valid existing file path.
func isFile(fl FieldLevel) bool { func isFile(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@ -1405,6 +1488,57 @@ func isFile(fl FieldLevel) bool {
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
// isFilePath is the validation function for validating if the current field's value is a valid file path.
func isFilePath(fl FieldLevel) bool {
var exists bool
var err error
field := fl.Field()
// If it exists, it obviously is valid.
// This is done first to avoid code duplication and unnecessary additional logic.
if exists = isFile(fl); exists {
return true
}
// It does not exist but may still be a valid filepath.
switch field.Kind() {
case reflect.String:
// Every OS allows for whitespace, but none
// let you use a file with no filename (to my knowledge).
// Unless you're dealing with raw inodes, but I digress.
if strings.TrimSpace(field.String()) == "" {
return false
}
// We make sure it isn't a directory.
if strings.HasSuffix(field.String(), string(os.PathSeparator)) {
return false
}
if _, err = os.Stat(field.String()); err != nil {
switch t := err.(type) {
case *fs.PathError:
if t.Err == syscall.EINVAL {
// It's definitely an invalid character in the filepath.
return false
}
// It could be a permission error, a does-not-exist error, etc.
// Out-of-scope for this validation, though.
return true
default:
// Something went *seriously* wrong.
/*
Per https://pkg.go.dev/os#Stat:
"If there is an error, it will be of type *PathError."
*/
panic(err)
}
}
}
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
// isE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number. // isE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number.
func isE164(fl FieldLevel) bool { func isE164(fl FieldLevel) bool {
return e164Regex.MatchString(fl.Field().String()) return e164Regex.MatchString(fl.Field().String())
@ -1485,11 +1619,16 @@ func isAlphaUnicode(fl FieldLevel) bool {
return alphaUnicodeRegex.MatchString(fl.Field().String()) return alphaUnicodeRegex.MatchString(fl.Field().String())
} }
// isBoolean is the validation function for validating if the current field's value can be safely converted to a boolean. // isBoolean is the validation function for validating if the current field's value is a valid boolean value or can be safely converted to a boolean value.
func isBoolean(fl FieldLevel) bool { func isBoolean(fl FieldLevel) bool {
switch fl.Field().Kind() {
case reflect.Bool:
return true
default:
_, err := strconv.ParseBool(fl.Field().String()) _, err := strconv.ParseBool(fl.Field().String())
return err == nil return err == nil
} }
}
// isDefault is the opposite of required aka hasValue // isDefault is the opposite of required aka hasValue
func isDefault(fl FieldLevel) bool { func isDefault(fl FieldLevel) bool {
@ -1535,7 +1674,9 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo
} }
// requireCheckFieldValue is a func for check field value // requireCheckFieldValue is a func for check field value
func requireCheckFieldValue(fl FieldLevel, param string, value string, defaultNotFoundValue bool) bool { func requireCheckFieldValue(
fl FieldLevel, param string, value string, defaultNotFoundValue bool,
) bool {
field, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), param) field, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
if !found { if !found {
return defaultNotFoundValue return defaultNotFoundValue
@ -1634,10 +1775,10 @@ func excludedUnless(fl FieldLevel) bool {
} }
for i := 0; i < len(params); i += 2 { for i := 0; i < len(params); i += 2 {
if !requireCheckFieldValue(fl, params[i], params[i+1], false) { if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
return true return !hasValue(fl)
} }
} }
return !hasValue(fl) return true
} }
// excludedWith is the validation function // excludedWith is the validation function
@ -2286,7 +2427,7 @@ func isFQDN(fl FieldLevel) bool {
return fqdnRegexRFC1123.MatchString(val) return fqdnRegexRFC1123.MatchString(val)
} }
// isDir is the validation function for validating if the current field's value is a valid directory. // isDir is the validation function for validating if the current field's value is a valid existing directory.
func isDir(fl FieldLevel) bool { func isDir(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@ -2302,6 +2443,64 @@ func isDir(fl FieldLevel) bool {
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
// isDirPath is the validation function for validating if the current field's value is a valid directory.
func isDirPath(fl FieldLevel) bool {
var exists bool
var err error
field := fl.Field()
// If it exists, it obviously is valid.
// This is done first to avoid code duplication and unnecessary additional logic.
if exists = isDir(fl); exists {
return true
}
// It does not exist but may still be a valid path.
switch field.Kind() {
case reflect.String:
// Every OS allows for whitespace, but none
// let you use a dir with no name (to my knowledge).
// Unless you're dealing with raw inodes, but I digress.
if strings.TrimSpace(field.String()) == "" {
return false
}
if _, err = os.Stat(field.String()); err != nil {
switch t := err.(type) {
case *fs.PathError:
if t.Err == syscall.EINVAL {
// It's definitely an invalid character in the path.
return false
}
// It could be a permission error, a does-not-exist error, etc.
// Out-of-scope for this validation, though.
// Lastly, we make sure it is a directory.
if strings.HasSuffix(field.String(), string(os.PathSeparator)) {
return true
} else {
return false
}
default:
// Something went *seriously* wrong.
/*
Per https://pkg.go.dev/os#Stat:
"If there is an error, it will be of type *PathError."
*/
panic(err)
}
}
// We repeat the check here to make sure it is an explicit directory in case the above os.Stat didn't trigger an error.
if strings.HasSuffix(field.String(), string(os.PathSeparator)) {
return true
} else {
return false
}
}
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
// isJSON is the validation function for validating if the current field's value is a valid json string. // isJSON is the validation function for validating if the current field's value is a valid json string.
func isJSON(fl FieldLevel) bool { func isJSON(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@ -2327,7 +2526,9 @@ func isHostnamePort(fl FieldLevel) bool {
return false return false
} }
// Port must be a iny <= 65535. // Port must be a iny <= 65535.
if portNum, err := strconv.ParseInt(port, 10, 32); err != nil || portNum > 65535 || portNum < 1 { if portNum, err := strconv.ParseInt(
port, 10, 32,
); err != nil || portNum > 65535 || portNum < 1 {
return false return false
} }
@ -2490,6 +2691,13 @@ func isSemverFormat(fl FieldLevel) bool {
return semverRegex.MatchString(semverString) return semverRegex.MatchString(semverString)
} }
// isCveFormat is the validation function for validating if the current field's value is a valid cve id, defined in CVE mitre org
func isCveFormat(fl FieldLevel) bool {
cveString := fl.Field().String()
return cveRegex.MatchString(cveString)
}
// isDnsRFC1035LabelFormat is the validation function // isDnsRFC1035LabelFormat is the validation function
// for validating if the current field's value is // for validating if the current field's value is
// a valid dns RFC 1035 label, defined in RFC 1035. // a valid dns RFC 1035 label, defined in RFC 1035.
@ -2498,6 +2706,35 @@ func isDnsRFC1035LabelFormat(fl FieldLevel) bool {
return dnsRegexRFC1035Label.MatchString(val) return dnsRegexRFC1035Label.MatchString(val)
} }
// digitsHaveLuhnChecksum returns true if and only if the last element of the given digits slice is the Luhn checksum of the previous elements
func digitsHaveLuhnChecksum(digits []string) bool {
size := len(digits)
sum := 0
for i, digit := range digits {
value, err := strconv.Atoi(digit)
if err != nil {
return false
}
if size%2 == 0 && i%2 == 0 || size%2 == 1 && i%2 == 1 {
v := value * 2
if v >= 10 {
sum += 1 + (v % 10)
} else {
sum += v
}
} else {
sum += value
}
}
return (sum % 10) == 0
}
// isMongoDB is the validation function for validating if the current field's value is valid mongoDB objectID
func isMongoDB(fl FieldLevel) bool {
val := fl.Field().String()
return mongodbRegex.MatchString(val)
}
// isCreditCard is the validation function for validating if the current field's value is a valid credit card number // isCreditCard is the validation function for validating if the current field's value is a valid credit card number
func isCreditCard(fl FieldLevel) bool { func isCreditCard(fl FieldLevel) bool {
val := fl.Field().String() val := fl.Field().String()
@ -2516,22 +2753,33 @@ func isCreditCard(fl FieldLevel) bool {
return false return false
} }
sum := 0 return digitsHaveLuhnChecksum(ccDigits)
for i, digit := range ccDigits {
value, err := strconv.Atoi(digit)
if err != nil {
return false
} }
if size%2 == 0 && i%2 == 0 || size%2 == 1 && i%2 == 1 {
v := value * 2 // hasLuhnChecksum is the validation for validating if the current field's value has a valid Luhn checksum
if v >= 10 { func hasLuhnChecksum(fl FieldLevel) bool {
sum += 1 + (v % 10) field := fl.Field()
} else { var str string // convert to a string which will then be split into single digits; easier and more readable than shifting/extracting single digits from a number
sum += v switch field.Kind() {
case reflect.String:
str = field.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
str = strconv.FormatInt(field.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
str = strconv.FormatUint(field.Uint(), 10)
default:
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
} else { size := len(str)
sum += value if size < 2 { // there has to be at least one digit that carries a meaning + the checksum
return false
} }
digits := strings.Split(str, "")
return digitsHaveLuhnChecksum(digits)
} }
return (sum % 10) == 0
// isCron is the validation function for validating if the current field's value is a valid cron expression
func isCron(fl FieldLevel) bool {
cronString := fl.Field().String()
return cronRegex.MatchString(cronString)
} }

@ -51,7 +51,7 @@ var iso3166_1_alpha2 = map[string]bool{
"TV": true, "UG": true, "UA": true, "AE": true, "GB": true, "TV": true, "UG": true, "UA": true, "AE": true, "GB": true,
"US": true, "UM": true, "UY": true, "UZ": true, "VU": true, "US": true, "UM": true, "UY": true, "UZ": true, "VU": true,
"VE": true, "VN": true, "VG": true, "VI": true, "WF": true, "VE": true, "VN": true, "VG": true, "VI": true, "WF": true,
"EH": true, "YE": true, "ZM": true, "ZW": true, "EH": true, "YE": true, "ZM": true, "ZW": true, "XK": true,
} }
var iso3166_1_alpha3 = map[string]bool{ var iso3166_1_alpha3 = map[string]bool{
@ -105,7 +105,7 @@ var iso3166_1_alpha3 = map[string]bool{
"UGA": true, "UKR": true, "ARE": true, "GBR": true, "UMI": true, "UGA": true, "UKR": true, "ARE": true, "GBR": true, "UMI": true,
"USA": true, "URY": true, "UZB": true, "VUT": true, "VEN": true, "USA": true, "URY": true, "UZB": true, "VUT": true, "VEN": true,
"VNM": true, "VGB": true, "VIR": true, "WLF": true, "ESH": true, "VNM": true, "VGB": true, "VIR": true, "WLF": true, "ESH": true,
"YEM": true, "ZMB": true, "ZWE": true, "ALA": true, "YEM": true, "ZMB": true, "ZWE": true, "ALA": true, "UNK": true,
} }
var iso3166_1_alpha_numeric = map[int]bool{ var iso3166_1_alpha_numeric = map[int]bool{
// see: https://www.iso.org/iso-3166-country-codes.html // see: https://www.iso.org/iso-3166-country-codes.html
@ -158,7 +158,7 @@ var iso3166_1_alpha_numeric = map[int]bool{
800: true, 804: true, 784: true, 826: true, 581: true, 800: true, 804: true, 784: true, 826: true, 581: true,
840: true, 858: true, 860: true, 548: true, 862: true, 840: true, 858: true, 860: true, 548: true, 862: true,
704: true, 92: true, 850: true, 876: true, 732: true, 704: true, 92: true, 850: true, 876: true, 732: true,
887: true, 894: true, 716: true, 248: true, 887: true, 894: true, 716: true, 248: true, 153:true,
} }
var iso3166_2 = map[string]bool{ var iso3166_2 = map[string]bool{

342
doc.go

File diff suppressed because it is too large Load Diff

@ -44,12 +44,9 @@ func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString("") buff := bytes.NewBufferString("")
var fe *fieldError
for i := 0; i < len(ve); i++ { for i := 0; i < len(ve); i++ {
fe = ve[i].(*fieldError) buff.WriteString(ve[i].Error())
buff.WriteString(fe.Error())
buff.WriteString("\n") buff.WriteString("\n")
} }

@ -1,19 +1,14 @@
module github.com/go-playground/validator/v10 module github.com/go-playground/validator/v10
go 1.13 go 1.18
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-playground/assert/v2 v2.2.0
github.com/go-playground/assert/v2 v2.0.1 github.com/go-playground/locales v0.14.1
github.com/go-playground/locales v0.14.0 github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/universal-translator v0.18.0 github.com/leodido/go-urn v1.2.2
github.com/kr/pretty v0.3.0 // indirect golang.org/x/crypto v0.7.0
github.com/leodido/go-urn v1.2.1 golang.org/x/text v0.8.0
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
golang.org/x/text v0.3.7
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
) )
require golang.org/x/sys v0.6.0 // indirect

@ -1,52 +1,31 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -14,7 +14,7 @@ func NotBlank(fl validator.FieldLevel) bool {
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
return len(strings.TrimSpace(field.String())) > 0 return len(strings.Trim(strings.TrimSpace(field.String()), "\x1c\x1d\x1e\x1f")) > 0
case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array: case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array:
return field.Len() > 0 return field.Len() > 0
case reflect.Ptr, reflect.Interface, reflect.Func: case reflect.Ptr, reflect.Interface, reflect.Func:

@ -24,7 +24,7 @@ func TestNotBlank(t *testing.T) {
// Errors // Errors
var x *int var x *int
invalid := test{ invalid := test{
String: " ", String: " \x1c\x1d\x1e\x1f\r\n",
Array: []int{}, Array: []int{},
Pointer: x, Pointer: x,
Number: 0, Number: 0,

@ -19,6 +19,7 @@ const (
e164RegexString = "^\\+[1-9]?[0-9]{7,14}$" e164RegexString = "^\\+[1-9]?[0-9]{7,14}$"
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$" base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$"
base64RawURLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2,4})$"
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$" iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
@ -64,6 +65,9 @@ const (
bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$` bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$`
semverRegexString = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/ semverRegexString = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/
dnsRegexStringRFC1035Label = "^[a-z]([-a-z0-9]*[a-z0-9]){0,62}$" dnsRegexStringRFC1035Label = "^[a-z]([-a-z0-9]*[a-z0-9]){0,62}$"
cveRegexString = `^CVE-(1999|2\d{3})-(0[^0]\d{2}|0\d[^0]\d{1}|0\d{2}[^0]|[1-9]{1}\d{3,})$` // CVE Format Id https://cve.mitre.org/cve/identifiers/syntaxchange.html
mongodbRegexString = "^[a-f\\d]{24}$"
cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`
) )
var ( var (
@ -83,6 +87,7 @@ var (
emailRegex = regexp.MustCompile(emailRegexString) emailRegex = regexp.MustCompile(emailRegexString)
base64Regex = regexp.MustCompile(base64RegexString) base64Regex = regexp.MustCompile(base64RegexString)
base64URLRegex = regexp.MustCompile(base64URLRegexString) base64URLRegex = regexp.MustCompile(base64URLRegexString)
base64RawURLRegex = regexp.MustCompile(base64RawURLRegexString)
iSBN10Regex = regexp.MustCompile(iSBN10RegexString) iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
iSBN13Regex = regexp.MustCompile(iSBN13RegexString) iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
uUID3Regex = regexp.MustCompile(uUID3RegexString) uUID3Regex = regexp.MustCompile(uUID3RegexString)
@ -118,8 +123,6 @@ var (
btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32) btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32)
btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32) btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32)
ethAddressRegex = regexp.MustCompile(ethAddressRegexString) ethAddressRegex = regexp.MustCompile(ethAddressRegexString)
ethAddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString)
ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString)
uRLEncodedRegex = regexp.MustCompile(uRLEncodedRegexString) uRLEncodedRegex = regexp.MustCompile(uRLEncodedRegexString)
hTMLEncodedRegex = regexp.MustCompile(hTMLEncodedRegexString) hTMLEncodedRegex = regexp.MustCompile(hTMLEncodedRegexString)
hTMLRegex = regexp.MustCompile(hTMLRegexString) hTMLRegex = regexp.MustCompile(hTMLRegexString)
@ -128,4 +131,7 @@ var (
bicRegex = regexp.MustCompile(bicRegexString) bicRegex = regexp.MustCompile(bicRegexString)
semverRegex = regexp.MustCompile(semverRegexString) semverRegex = regexp.MustCompile(semverRegexString)
dnsRegexRFC1035Label = regexp.MustCompile(dnsRegexStringRFC1035Label) dnsRegexRFC1035Label = regexp.MustCompile(dnsRegexStringRFC1035Label)
cveRegex = regexp.MustCompile(cveRegexString)
mongodbRegex = regexp.MustCompile(mongodbRegexString)
cronRegex = regexp.MustCompile(cronRegexString)
) )

@ -1266,6 +1266,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0} must contain a valid MAC address", translation: "{0} must contain a valid MAC address",
override: false, override: false,
}, },
{
tag: "fqdn",
translation: "{0} must be a valid FQDN",
override: false,
},
{ {
tag: "unique", tag: "unique",
translation: "{0} must contain unique values", translation: "{0} must contain unique values",
@ -1276,6 +1281,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0} must be a valid color", translation: "{0} must be a valid color",
override: false, override: false,
}, },
{
tag: "cron",
translation: "{0} must be a valid cron expression",
override: false,
},
{ {
tag: "oneof", tag: "oneof",
translation: "{0} must be one of [{1}]", translation: "{0} must be one of [{1}]",
@ -1356,6 +1366,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0} must be a valid boolean value", translation: "{0} must be a valid boolean value",
override: false, override: false,
}, },
{
tag: "cve",
translation: "{0} must be a valid cve identifier",
override: false,
},
} }
for _, t := range translations { for _, t := range translations {

@ -130,6 +130,7 @@ func TestTranslations(t *testing.T) {
IPAddrv6 string `validate:"ip6_addr"` IPAddrv6 string `validate:"ip6_addr"`
UinxAddr string `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future UinxAddr string `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future
MAC string `validate:"mac"` MAC string `validate:"mac"`
FQDN string `validate:"fqdn"`
IsColor string `validate:"iscolor"` IsColor string `validate:"iscolor"`
StrPtrMinLen *string `validate:"min=10"` StrPtrMinLen *string `validate:"min=10"`
StrPtrMaxLen *string `validate:"max=1"` StrPtrMaxLen *string `validate:"max=1"`
@ -152,6 +153,7 @@ func TestTranslations(t *testing.T) {
PostCodeCountry string PostCodeCountry string
PostCodeByField string `validate:"postcode_iso3166_alpha2_field=PostCodeCountry"` PostCodeByField string `validate:"postcode_iso3166_alpha2_field=PostCodeCountry"`
BooleanString string `validate:"boolean"` BooleanString string `validate:"boolean"`
CveString string `validate:"cve"`
} }
var test Test var test Test
@ -205,6 +207,7 @@ func TestTranslations(t *testing.T) {
test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"} test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"}
test.Datetime = "2008-Feb-01" test.Datetime = "2008-Feb-01"
test.BooleanString = "A" test.BooleanString = "A"
test.CveString = "A"
test.Inner.RequiredIf = "abcd" test.Inner.RequiredIf = "abcd"
@ -226,6 +229,10 @@ func TestTranslations(t *testing.T) {
ns: "Test.MAC", ns: "Test.MAC",
expected: "MAC must contain a valid MAC address", expected: "MAC must contain a valid MAC address",
}, },
{
ns: "Test.FQDN",
expected: "FQDN must be a valid FQDN",
},
{ {
ns: "Test.IPAddr", ns: "Test.IPAddr",
expected: "IPAddr must be a resolvable IP address", expected: "IPAddr must be a resolvable IP address",
@ -690,6 +697,10 @@ func TestTranslations(t *testing.T) {
ns: "Test.BooleanString", ns: "Test.BooleanString",
expected: "BooleanString must be a valid boolean value", expected: "BooleanString must be a valid boolean value",
}, },
{
ns: "Test.CveString",
expected: "CveString must be a valid cve identifier",
},
} }
for _, tt := range tests { for _, tt := range tests {

@ -29,6 +29,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0} es un campo requerido", translation: "{0} es un campo requerido",
override: false, override: false,
}, },
{
tag: "required_if",
translation: "{0} es un campo requerido",
override: false,
},
{ {
tag: "len", tag: "len",
customRegisFunc: func(ut ut.Translator) (err error) { customRegisFunc: func(ut ut.Translator) (err error) {

@ -35,6 +35,7 @@ func TestTranslations(t *testing.T) {
RequiredString string `validate:"required"` RequiredString string `validate:"required"`
RequiredNumber int `validate:"required"` RequiredNumber int `validate:"required"`
RequiredMultiple []string `validate:"required"` RequiredMultiple []string `validate:"required"`
RequiredIf string `validate:"required_if=Inner.RequiredIf abcd"`
LenString string `validate:"len=1"` LenString string `validate:"len=1"`
LenNumber float64 `validate:"len=1113.00"` LenNumber float64 `validate:"len=1113.00"`
LenMultiple []string `validate:"len=7"` LenMultiple []string `validate:"len=7"`

@ -1132,6 +1132,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0} deve essere un colore valido", translation: "{0} deve essere un colore valido",
override: false, override: false,
}, },
{
tag: "cron",
translation: "{0} deve essere una stringa cron valida",
override: false,
},
{ {
tag: "oneof", tag: "oneof",
translation: "{0} deve essere uno di [{1}]", translation: "{0} deve essere uno di [{1}]",

@ -16,7 +16,6 @@ import (
// RegisterDefaultTranslations registers a set of default translations // RegisterDefaultTranslations registers a set of default translations
// for all built in tag's in validator; you may add your own as desired. // 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) { func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (err error) {
translations := []struct { translations := []struct {
tag string tag string
translation string translation string
@ -29,10 +28,14 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は必須フィールドです", translation: "{0}は必須フィールドです",
override: false, override: false,
}, },
{
tag: "required_if",
translation: "{0}は必須フィールドです",
override: false,
},
{ {
tag: "len", tag: "len",
customRegisFunc: func(ut ut.Translator) (err error) { customRegisFunc: func(ut ut.Translator) (err error) {
if err = ut.Add("len-string", "{0}の長さは{1}でなければなりません", false); err != nil { if err = ut.Add("len-string", "{0}の長さは{1}でなければなりません", false); err != nil {
return return
} }
@ -64,7 +67,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
}, },
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
var err error var err error
var t string var t string
@ -123,7 +125,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
{ {
tag: "min", tag: "min",
customRegisFunc: func(ut ut.Translator) (err error) { customRegisFunc: func(ut ut.Translator) (err error) {
if err = ut.Add("min-string", "{0}の長さは少なくとも{1}はなければなりません", false); err != nil { if err = ut.Add("min-string", "{0}の長さは少なくとも{1}はなければなりません", false); err != nil {
return return
} }
@ -136,7 +137,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
return return
} }
if err = ut.Add("min-number", "{0}は{1}より大きくなければなりません", false); err != nil { if err = ut.Add("min-number", "{0}は{1}以上でなければなりません", false); err != nil {
return return
} }
@ -155,7 +156,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
}, },
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
var err error var err error
var t string var t string
@ -214,7 +214,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
{ {
tag: "max", tag: "max",
customRegisFunc: func(ut ut.Translator) (err error) { customRegisFunc: func(ut ut.Translator) (err error) {
if err = ut.Add("max-string", "{0}の長さは最大でも{1}でなければなりません", false); err != nil { if err = ut.Add("max-string", "{0}の長さは最大でも{1}でなければなりません", false); err != nil {
return return
} }
@ -227,7 +226,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
return return
} }
if err = ut.Add("max-number", "{0}は{1}より小さくなければなりません", false); err != nil { if err = ut.Add("max-number", "{0}は{1}以下でなければなりません", false); err != nil {
return return
} }
@ -307,7 +306,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}と等しくありません", translation: "{0}は{1}と等しくありません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
fmt.Printf("warning: error translating FieldError: %#v", fe) fmt.Printf("warning: error translating FieldError: %#v", fe)
@ -391,7 +389,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
{ {
tag: "lt", tag: "lt",
customRegisFunc: func(ut ut.Translator) (err error) { customRegisFunc: func(ut ut.Translator) (err error) {
if err = ut.Add("lt-string", "{0}の長さは{1}よりも少なくなければなりません", false); err != nil { if err = ut.Add("lt-string", "{0}の長さは{1}よりも少なくなければなりません", false); err != nil {
return return
} }
@ -512,7 +509,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
{ {
tag: "lte", tag: "lte",
customRegisFunc: func(ut ut.Translator) (err error) { customRegisFunc: func(ut ut.Translator) (err error) {
if err = ut.Add("lte-string", "{0}の長さは最大でも{1}でなければなりません", false); err != nil { if err = ut.Add("lte-string", "{0}の長さは最大でも{1}でなければなりません", false); err != nil {
return return
} }
@ -525,7 +521,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
return return
} }
if err = ut.Add("lte-number", "{0}は{1}より小さくなければなりません", false); err != nil { if err = ut.Add("lte-number", "{0}は{1}以下でなければなりません", false); err != nil {
return return
} }
@ -632,7 +628,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
{ {
tag: "gt", tag: "gt",
customRegisFunc: func(ut ut.Translator) (err error) { customRegisFunc: func(ut ut.Translator) (err error) {
if err = ut.Add("gt-string", "{0}の長さは{1}よりも多くなければなりません", false); err != nil { if err = ut.Add("gt-string", "{0}の長さは{1}よりも多くなければなりません", false); err != nil {
return return
} }
@ -752,7 +747,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
{ {
tag: "gte", tag: "gte",
customRegisFunc: func(ut ut.Translator) (err error) { customRegisFunc: func(ut ut.Translator) (err error) {
if err = ut.Add("gte-string", "{0}の長さは少なくとも{1}以上はなければなりません", false); err != nil { if err = ut.Add("gte-string", "{0}の長さは少なくとも{1}以上はなければなりません", false); err != nil {
return return
} }
@ -765,7 +759,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
return return
} }
if err = ut.Add("gte-number", "{0}は{1}より大きくなければなりません", false); err != nil { if err = ut.Add("gte-number", "{0}は{1}以上でなければなりません", false); err != nil {
return return
} }
@ -874,7 +868,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}と等しくなければなりません", translation: "{0}は{1}と等しくなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -889,7 +882,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}と等しくなければなりません", translation: "{0}は{1}と等しくなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -904,7 +896,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}とは異ならなければなりません", translation: "{0}は{1}とは異ならなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -919,7 +910,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}よりも大きくなければなりません", translation: "{0}は{1}よりも大きくなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -934,7 +924,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}以上でなければなりません", translation: "{0}は{1}以上でなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -949,7 +938,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}よりも小さくなければなりません", translation: "{0}は{1}よりも小さくなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -964,7 +952,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}以下でなければなりません", translation: "{0}は{1}以下でなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -979,7 +966,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}とは異ならなければなりません", translation: "{0}は{1}とは異ならなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -994,7 +980,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}よりも大きくなければなりません", translation: "{0}は{1}よりも大きくなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -1009,7 +994,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}以上でなければなりません", translation: "{0}は{1}以上でなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -1024,7 +1008,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}よりも小さくなければなりません", translation: "{0}は{1}よりも小さくなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -1039,7 +1022,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は{1}以下でなければなりません", translation: "{0}は{1}以下でなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -1124,7 +1106,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は'{1}'を含まなければなりません", translation: "{0}は'{1}'を含まなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -1139,7 +1120,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は'{1}'の少なくとも1つを含まなければなりません", translation: "{0}は'{1}'の少なくとも1つを含まなければなりません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -1154,7 +1134,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}には'{1}'というテキストを含むことはできません", translation: "{0}には'{1}'というテキストを含むことはできません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -1169,7 +1148,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}には'{1}'のどれも含めることはできません", translation: "{0}には'{1}'のどれも含めることはできません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -1184,7 +1162,6 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}には'{1}'を含めることはできません", translation: "{0}には'{1}'を含めることはできません",
override: false, override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)
@ -1354,6 +1331,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0}は正しいMACアドレスを含まなければなりません", translation: "{0}は正しいMACアドレスを含まなければなりません",
override: false, override: false,
}, },
{
tag: "unique",
translation: "{0}は一意な値のみを含まなければなりません",
override: false,
},
{ {
tag: "iscolor", tag: "iscolor",
translation: "{0}は正しい色でなければなりません", translation: "{0}は正しい色でなければなりません",
@ -1372,22 +1354,83 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
return s return s
}, },
}, },
{
tag: "json",
translation: "{0}は正しいJSON文字列でなければなりません",
override: false,
},
{
tag: "jwt",
translation: "{0}は正しいJWT文字列でなければなりません",
override: false,
},
{
tag: "lowercase",
translation: "{0}は小文字でなければなりません",
override: false,
},
{
tag: "uppercase",
translation: "{0}は大文字でなければなりません",
override: false,
},
{
tag: "datetime",
translation: "{0}は{1}の書式と一致しません",
override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe)
return fe.(error).Error()
}
return t
},
},
{
tag: "postcode_iso3166_alpha2",
translation: "{0}は国名コード{1}の郵便番号形式と一致しません",
override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe)
return fe.(error).Error()
}
return t
},
},
{
tag: "postcode_iso3166_alpha2_field",
translation: "{0}は{1}フィールドで指定された国名コードの郵便番号形式と一致しません",
override: false,
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe)
return fe.(error).Error()
}
return t
},
},
{
tag: "boolean",
translation: "{0}は正しいブール値でなければなりません",
override: false,
},
} }
for _, t := range translations { for _, t := range translations {
if t.customTransFunc != nil && t.customRegisFunc != nil { if t.customTransFunc != nil && t.customRegisFunc != nil {
err = v.RegisterTranslation(t.tag, trans, t.customRegisFunc, t.customTransFunc) err = v.RegisterTranslation(t.tag, trans, t.customRegisFunc, t.customTransFunc)
} else if t.customTransFunc != nil && t.customRegisFunc == nil { } else if t.customTransFunc != nil && t.customRegisFunc == nil {
err = v.RegisterTranslation(t.tag, trans, registrationFunc(t.tag, t.translation, t.override), t.customTransFunc) err = v.RegisterTranslation(t.tag, trans, registrationFunc(t.tag, t.translation, t.override), t.customTransFunc)
} else if t.customTransFunc == nil && t.customRegisFunc != nil { } else if t.customTransFunc == nil && t.customRegisFunc != nil {
err = v.RegisterTranslation(t.tag, trans, t.customRegisFunc, translateFunc) err = v.RegisterTranslation(t.tag, trans, t.customRegisFunc, translateFunc)
} else { } else {
err = v.RegisterTranslation(t.tag, trans, registrationFunc(t.tag, t.translation, t.override), translateFunc) err = v.RegisterTranslation(t.tag, trans, registrationFunc(t.tag, t.translation, t.override), translateFunc)
} }
@ -1401,9 +1444,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
} }
func registrationFunc(tag string, translation string, override bool) validator.RegisterTranslationsFunc { func registrationFunc(tag string, translation string, override bool) validator.RegisterTranslationsFunc {
return func(ut ut.Translator) (err error) { return func(ut ut.Translator) (err error) {
if err = ut.Add(tag, translation, override); err != nil { if err = ut.Add(tag, translation, override); err != nil {
return return
} }
@ -1415,7 +1456,6 @@ func registrationFunc(tag string, translation string, override bool) validator.R
} }
func translateFunc(ut ut.Translator, fe validator.FieldError) string { func translateFunc(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field()) t, err := ut.T(fe.Tag(), fe.Field())
if err != nil { if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe) log.Printf("warning: error translating FieldError: %#v", fe)

@ -11,7 +11,6 @@ import (
) )
func TestTranslations(t *testing.T) { func TestTranslations(t *testing.T) {
japanese := ja_locale.New() japanese := ja_locale.New()
uni := ut.New(japanese, japanese) uni := ut.New(japanese, japanese)
trans, _ := uni.GetTranslator("ja") trans, _ := uni.GetTranslator("ja")
@ -28,6 +27,7 @@ func TestTranslations(t *testing.T) {
GteCSFieldString string GteCSFieldString string
LtCSFieldString string LtCSFieldString string
LteCSFieldString string LteCSFieldString string
RequiredIf string
} }
type Test struct { type Test struct {
@ -35,6 +35,7 @@ func TestTranslations(t *testing.T) {
RequiredString string `validate:"required"` RequiredString string `validate:"required"`
RequiredNumber int `validate:"required"` RequiredNumber int `validate:"required"`
RequiredMultiple []string `validate:"required"` RequiredMultiple []string `validate:"required"`
RequiredIf string `validate:"required_if=Inner.RequiredIf abcd"`
LenString string `validate:"len=1"` LenString string `validate:"len=1"`
LenNumber float64 `validate:"len=1113.00"` LenNumber float64 `validate:"len=1113.00"`
LenMultiple []string `validate:"len=7"` LenMultiple []string `validate:"len=7"`
@ -139,6 +140,18 @@ func TestTranslations(t *testing.T) {
StrPtrGte *string `validate:"gte=10"` StrPtrGte *string `validate:"gte=10"`
OneOfString string `validate:"oneof=red green"` OneOfString string `validate:"oneof=red green"`
OneOfInt int `validate:"oneof=5 63"` OneOfInt int `validate:"oneof=5 63"`
UniqueSlice []string `validate:"unique"`
UniqueArray [3]string `validate:"unique"`
UniqueMap map[string]string `validate:"unique"`
JSONString string `validate:"json"`
JWTString string `validate:"jwt"`
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"`
BooleanString string `validate:"boolean"`
} }
var test Test var test Test
@ -181,10 +194,20 @@ func TestTranslations(t *testing.T) {
test.MultiByte = "1234feerf" test.MultiByte = "1234feerf"
test.LowercaseString = "ABCDEFG"
test.UppercaseString = "abcdefg"
s := "toolong" s := "toolong"
test.StrPtrMaxLen = &s test.StrPtrMaxLen = &s
test.StrPtrLen = &s test.StrPtrLen = &s
test.UniqueSlice = []string{"1234", "1234"}
test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"}
test.Datetime = "2008-Feb-01"
test.BooleanString = "A"
test.Inner.RequiredIf = "abcd"
err = validate.Struct(test) err = validate.Struct(test)
NotEqual(t, err, nil) NotEqual(t, err, nil)
@ -453,7 +476,7 @@ func TestTranslations(t *testing.T) {
}, },
{ {
ns: "Test.GteNumber", ns: "Test.GteNumber",
expected: "GteNumberは5.56より大きくなければなりません", expected: "GteNumberは5.56以上でなければなりません",
}, },
{ {
ns: "Test.GteMultiple", ns: "Test.GteMultiple",
@ -485,7 +508,7 @@ func TestTranslations(t *testing.T) {
}, },
{ {
ns: "Test.LteNumber", ns: "Test.LteNumber",
expected: "LteNumberは5.56より小さくなければなりません", expected: "LteNumberは5.56以下でなければなりません",
}, },
{ {
ns: "Test.LteMultiple", ns: "Test.LteMultiple",
@ -541,7 +564,7 @@ func TestTranslations(t *testing.T) {
}, },
{ {
ns: "Test.MaxNumber", ns: "Test.MaxNumber",
expected: "MaxNumberは1,113.00より小さくなければなりません", expected: "MaxNumberは1,113.00以下でなければなりません",
}, },
{ {
ns: "Test.MaxMultiple", ns: "Test.MaxMultiple",
@ -553,7 +576,7 @@ func TestTranslations(t *testing.T) {
}, },
{ {
ns: "Test.MinNumber", ns: "Test.MinNumber",
expected: "MinNumberは1,113.00より大きくなければなりません", expected: "MinNumberは1,113.00以上でなければなりません",
}, },
{ {
ns: "Test.MinMultiple", ns: "Test.MinMultiple",
@ -575,6 +598,10 @@ func TestTranslations(t *testing.T) {
ns: "Test.RequiredString", ns: "Test.RequiredString",
expected: "RequiredStringは必須フィールドです", expected: "RequiredStringは必須フィールドです",
}, },
{
ns: "Test.RequiredIf",
expected: "RequiredIfは必須フィールドです",
},
{ {
ns: "Test.RequiredNumber", ns: "Test.RequiredNumber",
expected: "RequiredNumberは必須フィールドです", expected: "RequiredNumberは必須フィールドです",
@ -619,6 +646,50 @@ func TestTranslations(t *testing.T) {
ns: "Test.OneOfInt", ns: "Test.OneOfInt",
expected: "OneOfIntは[5 63]のうちのいずれかでなければなりません", expected: "OneOfIntは[5 63]のうちのいずれかでなければなりません",
}, },
{
ns: "Test.UniqueSlice",
expected: "UniqueSliceは一意な値のみを含まなければなりません",
},
{
ns: "Test.UniqueArray",
expected: "UniqueArrayは一意な値のみを含まなければなりません",
},
{
ns: "Test.UniqueMap",
expected: "UniqueMapは一意な値のみを含まなければなりません",
},
{
ns: "Test.JSONString",
expected: "JSONStringは正しいJSON文字列でなければなりません",
},
{
ns: "Test.JWTString",
expected: "JWTStringは正しいJWT文字列でなければなりません",
},
{
ns: "Test.LowercaseString",
expected: "LowercaseStringは小文字でなければなりません",
},
{
ns: "Test.UppercaseString",
expected: "UppercaseStringは大文字でなければなりません",
},
{
ns: "Test.Datetime",
expected: "Datetimeは2006-01-02の書式と一致しません",
},
{
ns: "Test.PostCode",
expected: "PostCodeは国名コードSGの郵便番号形式と一致しません",
},
{
ns: "Test.PostCodeByField",
expected: "PostCodeByFieldはPostCodeCountryフィールドで指定された国名コードの郵便番号形式と一致しません",
},
{
ns: "Test.BooleanString",
expected: "BooleanStringは正しいブール値でなければなりません",
},
} }
for _, tt := range tests { for _, tt := range tests {

File diff suppressed because it is too large Load Diff

@ -0,0 +1,709 @@
package lv
import (
"testing"
"time"
. "github.com/go-playground/assert/v2"
english "github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
)
func TestTranslations(t *testing.T) {
eng := english.New()
uni := ut.New(eng, eng)
trans, _ := uni.GetTranslator("en")
validate := validator.New()
err := RegisterDefaultTranslations(validate, trans)
Equal(t, err, nil)
type Inner struct {
EqCSFieldString string
NeCSFieldString string
GtCSFieldString string
GteCSFieldString string
LtCSFieldString string
LteCSFieldString string
RequiredIf string
}
type Test struct {
Inner Inner
RequiredString string `validate:"required"`
RequiredNumber int `validate:"required"`
RequiredMultiple []string `validate:"required"`
RequiredIf string `validate:"required_if=Inner.RequiredIf abcd"`
LenString string `validate:"len=1"`
LenNumber float64 `validate:"len=1113.00"`
LenMultiple []string `validate:"len=7"`
MinString string `validate:"min=1"`
MinNumber float64 `validate:"min=1113.00"`
MinMultiple []string `validate:"min=7"`
MaxString string `validate:"max=3"`
MaxNumber float64 `validate:"max=1113.00"`
MaxMultiple []string `validate:"max=7"`
EqString string `validate:"eq=3"`
EqNumber float64 `validate:"eq=2.33"`
EqMultiple []string `validate:"eq=7"`
NeString string `validate:"ne="`
NeNumber float64 `validate:"ne=0.00"`
NeMultiple []string `validate:"ne=0"`
LtString string `validate:"lt=3"`
LtNumber float64 `validate:"lt=5.56"`
LtMultiple []string `validate:"lt=2"`
LtTime time.Time `validate:"lt"`
LteString string `validate:"lte=3"`
LteNumber float64 `validate:"lte=5.56"`
LteMultiple []string `validate:"lte=2"`
LteTime time.Time `validate:"lte"`
GtString string `validate:"gt=3"`
GtNumber float64 `validate:"gt=5.56"`
GtMultiple []string `validate:"gt=2"`
GtTime time.Time `validate:"gt"`
GteString string `validate:"gte=3"`
GteNumber float64 `validate:"gte=5.56"`
GteMultiple []string `validate:"gte=2"`
GteTime time.Time `validate:"gte"`
EqFieldString string `validate:"eqfield=MaxString"`
EqCSFieldString string `validate:"eqcsfield=Inner.EqCSFieldString"`
NeCSFieldString string `validate:"necsfield=Inner.NeCSFieldString"`
GtCSFieldString string `validate:"gtcsfield=Inner.GtCSFieldString"`
GteCSFieldString string `validate:"gtecsfield=Inner.GteCSFieldString"`
LtCSFieldString string `validate:"ltcsfield=Inner.LtCSFieldString"`
LteCSFieldString string `validate:"ltecsfield=Inner.LteCSFieldString"`
NeFieldString string `validate:"nefield=EqFieldString"`
GtFieldString string `validate:"gtfield=MaxString"`
GteFieldString string `validate:"gtefield=MaxString"`
LtFieldString string `validate:"ltfield=MaxString"`
LteFieldString string `validate:"ltefield=MaxString"`
AlphaString string `validate:"alpha"`
AlphanumString string `validate:"alphanum"`
NumericString string `validate:"numeric"`
NumberString string `validate:"number"`
HexadecimalString string `validate:"hexadecimal"`
HexColorString string `validate:"hexcolor"`
RGBColorString string `validate:"rgb"`
RGBAColorString string `validate:"rgba"`
HSLColorString string `validate:"hsl"`
HSLAColorString string `validate:"hsla"`
Email string `validate:"email"`
URL string `validate:"url"`
URI string `validate:"uri"`
Base64 string `validate:"base64"`
Contains string `validate:"contains=purpose"`
ContainsAny string `validate:"containsany=!@#$"`
Excludes string `validate:"excludes=text"`
ExcludesAll string `validate:"excludesall=!@#$"`
ExcludesRune string `validate:"excludesrune=☻"`
ISBN string `validate:"isbn"`
ISBN10 string `validate:"isbn10"`
ISBN13 string `validate:"isbn13"`
UUID string `validate:"uuid"`
UUID3 string `validate:"uuid3"`
UUID4 string `validate:"uuid4"`
UUID5 string `validate:"uuid5"`
ULID string `validate:"ulid"`
ASCII string `validate:"ascii"`
PrintableASCII string `validate:"printascii"`
MultiByte string `validate:"multibyte"`
DataURI string `validate:"datauri"`
Latitude string `validate:"latitude"`
Longitude string `validate:"longitude"`
SSN string `validate:"ssn"`
IP string `validate:"ip"`
IPv4 string `validate:"ipv4"`
IPv6 string `validate:"ipv6"`
CIDR string `validate:"cidr"`
CIDRv4 string `validate:"cidrv4"`
CIDRv6 string `validate:"cidrv6"`
TCPAddr string `validate:"tcp_addr"`
TCPAddrv4 string `validate:"tcp4_addr"`
TCPAddrv6 string `validate:"tcp6_addr"`
UDPAddr string `validate:"udp_addr"`
UDPAddrv4 string `validate:"udp4_addr"`
UDPAddrv6 string `validate:"udp6_addr"`
IPAddr string `validate:"ip_addr"`
IPAddrv4 string `validate:"ip4_addr"`
IPAddrv6 string `validate:"ip6_addr"`
UinxAddr string `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future
MAC string `validate:"mac"`
IsColor string `validate:"iscolor"`
StrPtrMinLen *string `validate:"min=10"`
StrPtrMaxLen *string `validate:"max=1"`
StrPtrLen *string `validate:"len=2"`
StrPtrLt *string `validate:"lt=1"`
StrPtrLte *string `validate:"lte=1"`
StrPtrGt *string `validate:"gt=10"`
StrPtrGte *string `validate:"gte=10"`
OneOfString string `validate:"oneof=red green"`
OneOfInt int `validate:"oneof=5 63"`
UniqueSlice []string `validate:"unique"`
UniqueArray [3]string `validate:"unique"`
UniqueMap map[string]string `validate:"unique"`
JSONString string `validate:"json"`
JWTString string `validate:"jwt"`
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"`
BooleanString string `validate:"boolean"`
}
var test Test
test.Inner.EqCSFieldString = "1234"
test.Inner.GtCSFieldString = "1234"
test.Inner.GteCSFieldString = "1234"
test.MaxString = "1234"
test.MaxNumber = 2000
test.MaxMultiple = make([]string, 9)
test.LtString = "1234"
test.LtNumber = 6
test.LtMultiple = make([]string, 3)
test.LtTime = time.Now().Add(time.Hour * 24)
test.LteString = "1234"
test.LteNumber = 6
test.LteMultiple = make([]string, 3)
test.LteTime = time.Now().Add(time.Hour * 24)
test.LtFieldString = "12345"
test.LteFieldString = "12345"
test.LtCSFieldString = "1234"
test.LteCSFieldString = "1234"
test.AlphaString = "abc3"
test.AlphanumString = "abc3!"
test.NumericString = "12E.00"
test.NumberString = "12E"
test.Excludes = "this is some test text"
test.ExcludesAll = "This is Great!"
test.ExcludesRune = "Love it ☻"
test.ASCII = "カタカナ"
test.PrintableASCII = "カタカナ"
test.MultiByte = "1234feerf"
test.LowercaseString = "ABCDEFG"
test.UppercaseString = "abcdefg"
s := "toolong"
test.StrPtrMaxLen = &s
test.StrPtrLen = &s
test.UniqueSlice = []string{"1234", "1234"}
test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"}
test.Datetime = "2008-Feb-01"
test.BooleanString = "A"
test.Inner.RequiredIf = "abcd"
err = validate.Struct(test)
NotEqual(t, err, nil)
errs, ok := err.(validator.ValidationErrors)
Equal(t, ok, true)
tests := []struct {
ns string
expected string
}{
{
ns: "Test.IsColor",
expected: "IsColor jābūt derīgai krāsai",
},
{
ns: "Test.MAC",
expected: "MAC jābūt derīgai MAC adresei",
},
{
ns: "Test.IPAddr",
expected: "IPAddr jābūt atrisināmai IP adresei",
},
{
ns: "Test.IPAddrv4",
expected: "IPAddrv4 jābūt atrisināmai IPv4 adresei",
},
{
ns: "Test.IPAddrv6",
expected: "IPAddrv6 jābūt atrisināmai IPv6 adresei",
},
{
ns: "Test.UDPAddr",
expected: "UDPAddr jābūt derīgai UDP adresei",
},
{
ns: "Test.UDPAddrv4",
expected: "UDPAddrv4 jābūt derīgai IPv4 UDP adresei",
},
{
ns: "Test.UDPAddrv6",
expected: "UDPAddrv6 jābūt derīgai IPv6 UDP adresei",
},
{
ns: "Test.TCPAddr",
expected: "TCPAddr jābūt derīgai TCP adresei",
},
{
ns: "Test.TCPAddrv4",
expected: "TCPAddrv4 jābūt derīgai IPv4 TCP adresei",
},
{
ns: "Test.TCPAddrv6",
expected: "TCPAddrv6 jābūt derīgai IPv6 TCP adresei",
},
{
ns: "Test.CIDR",
expected: "CIDR jāsatur derīgu CIDR notāciju",
},
{
ns: "Test.CIDRv4",
expected: "CIDRv4 jāsatur derīgu CIDR notāciju IPv4 adresei",
},
{
ns: "Test.CIDRv6",
expected: "CIDRv6 jāsatur derīgu CIDR notāciju IPv6 adresei",
},
{
ns: "Test.SSN",
expected: "SSN jābūt derīgam SSN numuram",
},
{
ns: "Test.IP",
expected: "IP jābūt derīgai IP adresei",
},
{
ns: "Test.IPv4",
expected: "IPv4 jābūt derīgai IPv4 adresei",
},
{
ns: "Test.IPv6",
expected: "IPv6 jābūt derīgai IPv6 adresei",
},
{
ns: "Test.DataURI",
expected: "DataURI jāsatur derīgs Data URI",
},
{
ns: "Test.Latitude",
expected: "Latitude jāsatur derīgus platuma grādus",
},
{
ns: "Test.Longitude",
expected: "Longitude jāsatur derīgus garuma grādus",
},
{
ns: "Test.MultiByte",
expected: "MultiByte jāsatur multibyte rakstu zīmes",
},
{
ns: "Test.ASCII",
expected: "ASCII jāsatur tikai ascii rakstu zīmes",
},
{
ns: "Test.PrintableASCII",
expected: "PrintableASCII jāsatur tikai drukājamas ascii rakstu zīmes",
},
{
ns: "Test.UUID",
expected: "UUID jābūt derīgam UUID",
},
{
ns: "Test.UUID3",
expected: "UUID3 jābūt derīgam 3. versijas UUID",
},
{
ns: "Test.UUID4",
expected: "UUID4 jābūt derīgam 4. versijas UUID",
},
{
ns: "Test.UUID5",
expected: "UUID5 jābūt derīgam 5. versijas UUID",
},
{
ns: "Test.ULID",
expected: "ULID jābūt derīgam ULID",
},
{
ns: "Test.ISBN",
expected: "ISBN jābūt derīgam ISBN numuram",
},
{
ns: "Test.ISBN10",
expected: "ISBN10 jābūt derīgam ISBN-10 numuram",
},
{
ns: "Test.ISBN13",
expected: "ISBN13 jābūt derīgam ISBN-13 numuram",
},
{
ns: "Test.Excludes",
expected: "Excludes nedrīkst saturēt tekstu 'text'",
},
{
ns: "Test.ExcludesAll",
expected: "ExcludesAll nedrīkst saturēt nevienu no sekojošām rakstu zīmēm '!@#$'",
},
{
ns: "Test.ExcludesRune",
expected: "ExcludesRune nedrīkst saturēt sekojošo '☻'",
},
{
ns: "Test.ContainsAny",
expected: "ContainsAny jāsatur minimums 1 no rakstu zīmēm '!@#$'",
},
{
ns: "Test.Contains",
expected: "Contains jāsatur teksts 'purpose'",
},
{
ns: "Test.Base64",
expected: "Base64 jābūt derīgai Base64 virknei",
},
{
ns: "Test.Email",
expected: "Email jābūt derīgai e-pasta adresei",
},
{
ns: "Test.URL",
expected: "URL jābūt derīgam URL",
},
{
ns: "Test.URI",
expected: "URI jābūt derīgam URI",
},
{
ns: "Test.RGBColorString",
expected: "RGBColorString jābūt derīgai RGB krāsai",
},
{
ns: "Test.RGBAColorString",
expected: "RGBAColorString jābūt derīgai RGBA krāsai",
},
{
ns: "Test.HSLColorString",
expected: "HSLColorString jābūt derīgai HSL krāsai",
},
{
ns: "Test.HSLAColorString",
expected: "HSLAColorString jābūt derīgai HSLA krāsai",
},
{
ns: "Test.HexadecimalString",
expected: "HexadecimalString jābūt heksadecimālam skaitlim",
},
{
ns: "Test.HexColorString",
expected: "HexColorString jābūt derīgai HEX krāsai",
},
{
ns: "Test.NumberString",
expected: "NumberString jāsatur derīgs skaitlis",
},
{
ns: "Test.NumericString",
expected: "NumericString jāsatur tikai cipari",
},
{
ns: "Test.AlphanumString",
expected: "AlphanumString jāsatur tikai simboli no alfabēta vai cipari (Alphanumeric)",
},
{
ns: "Test.AlphaString",
expected: "AlphaString jāsatur tikai simboli no alfabēta",
},
{
ns: "Test.LtFieldString",
expected: "LtFieldString jābūt mazākam par MaxString",
},
{
ns: "Test.LteFieldString",
expected: "LteFieldString jābūt mazākam par MaxString vai vienādam",
},
{
ns: "Test.GtFieldString",
expected: "GtFieldString jābūt lielākam par MaxString",
},
{
ns: "Test.GteFieldString",
expected: "GteFieldString jābūt lielākam par MaxString vai vienādam",
},
{
ns: "Test.NeFieldString",
expected: "NeFieldString nedrīkst būt vienāds ar EqFieldString",
},
{
ns: "Test.LtCSFieldString",
expected: "LtCSFieldString jābūt mazākam par Inner.LtCSFieldString",
},
{
ns: "Test.LteCSFieldString",
expected: "LteCSFieldString jābūt mazākam par Inner.LteCSFieldString vai vienādam",
},
{
ns: "Test.GtCSFieldString",
expected: "GtCSFieldString jābūt lielākam par Inner.GtCSFieldString",
},
{
ns: "Test.GteCSFieldString",
expected: "GteCSFieldString jābūt lielākam par Inner.GteCSFieldString vai vienādam",
},
{
ns: "Test.NeCSFieldString",
expected: "NeCSFieldString nedrīkst būt vienāds ar Inner.NeCSFieldString",
},
{
ns: "Test.EqCSFieldString",
expected: "EqCSFieldString jābūt vienādam ar Inner.EqCSFieldString",
},
{
ns: "Test.EqFieldString",
expected: "EqFieldString jābūt vienādam ar MaxString",
},
{
ns: "Test.GteString",
expected: "GteString garumam jābūt minimums 3 rakstu zīmes",
},
{
ns: "Test.GteNumber",
expected: "GteNumber jābūt 5.56 vai lielākam",
},
{
ns: "Test.GteMultiple",
expected: "GteMultiple jāsatur minimums 2 elementi",
},
{
ns: "Test.GteTime",
expected: "GteTime jābūt lielākam par šī brīža Datumu un laiku vai vienādam",
},
{
ns: "Test.GtString",
expected: "GtString ir jābūt garākam par 3 rakstu zīmēm",
},
{
ns: "Test.GtNumber",
expected: "GtNumber jābūt lielākam par 5.56",
},
{
ns: "Test.GtMultiple",
expected: "GtMultiple jāsatur vairāk par 2 elementiem",
},
{
ns: "Test.GtTime",
expected: "GtTime jābūt lielākam par šī brīža Datumu un laiku",
},
{
ns: "Test.LteString",
expected: "LteString garumam jābūt maksimums 3 rakstu zīmes",
},
{
ns: "Test.LteNumber",
expected: "LteNumber jābūt 5.56 vai mazākam",
},
{
ns: "Test.LteMultiple",
expected: "LteMultiple jāsatur maksimums 2 elementi",
},
{
ns: "Test.LteTime",
expected: "LteTime jābūt mazākam par šī brīža Datumu un laiku vai vienādam",
},
{
ns: "Test.LtString",
expected: "LtString garumam jābūt mazākam par 3 rakstu zīmēm",
},
{
ns: "Test.LtNumber",
expected: "LtNumber jābūt mazākam par 5.56",
},
{
ns: "Test.LtMultiple",
expected: "LtMultiple jāsatur mazāk par 2 elementiem",
},
{
ns: "Test.LtTime",
expected: "LtTime jābūt mazākam par šī brīža Datumu un laiku",
},
{
ns: "Test.NeString",
expected: "NeString nedrīkst būt vienāds ar ",
},
{
ns: "Test.NeNumber",
expected: "NeNumber nedrīkst būt vienāds ar 0.00",
},
{
ns: "Test.NeMultiple",
expected: "NeMultiple nedrīkst būt vienāds ar 0",
},
{
ns: "Test.EqString",
expected: "EqString nav vienāds ar 3",
},
{
ns: "Test.EqNumber",
expected: "EqNumber nav vienāds ar 2.33",
},
{
ns: "Test.EqMultiple",
expected: "EqMultiple nav vienāds ar 7",
},
{
ns: "Test.MaxString",
expected: "MaxString vērtība pārsniedz maksimālo garumu 3 rakstu zīmes",
},
{
ns: "Test.MaxNumber",
expected: "MaxNumber vērtībai jābūt 1,113.00 vai mazākai",
},
{
ns: "Test.MaxMultiple",
expected: "MaxMultiple jāsatur maksimums 7 elementi",
},
{
ns: "Test.MinString",
expected: "MinString garumam jābūt minimums 1 rakstu zīme",
},
{
ns: "Test.MinNumber",
expected: "MinNumber vērtībai jābūt 1,113.00 vai lielākai",
},
{
ns: "Test.MinMultiple",
expected: "MinMultiple jāsatur minimums 7 elementi",
},
{
ns: "Test.LenString",
expected: "LenString garumam jābūt 1 rakstu zīme",
},
{
ns: "Test.LenNumber",
expected: "LenNumber vērtībai jābūt 1,113.00",
},
{
ns: "Test.LenMultiple",
expected: "LenMultiple vērtībai jāsatur 7 elementi",
},
{
ns: "Test.RequiredString",
expected: "RequiredString ir obligāts lauks",
},
{
ns: "Test.RequiredIf",
expected: "RequiredIf ir obligāts lauks",
},
{
ns: "Test.RequiredNumber",
expected: "RequiredNumber ir obligāts lauks",
},
{
ns: "Test.RequiredMultiple",
expected: "RequiredMultiple ir obligāts lauks",
},
{
ns: "Test.StrPtrMinLen",
expected: "StrPtrMinLen garumam jābūt minimums 10 rakstu zīmes",
},
{
ns: "Test.StrPtrMaxLen",
expected: "StrPtrMaxLen vērtība pārsniedz maksimālo garumu 1 rakstu zīme",
},
{
ns: "Test.StrPtrLen",
expected: "StrPtrLen garumam jābūt 2 rakstu zīmes",
},
{
ns: "Test.StrPtrLt",
expected: "StrPtrLt garumam jābūt mazākam par 1 rakstu zīmi",
},
{
ns: "Test.StrPtrLte",
expected: "StrPtrLte garumam jābūt maksimums 1 rakstu zīme",
},
{
ns: "Test.StrPtrGt",
expected: "StrPtrGt ir jābūt garākam par 10 rakstu zīmēm",
},
{
ns: "Test.StrPtrGte",
expected: "StrPtrGte garumam jābūt minimums 10 rakstu zīmes",
},
{
ns: "Test.OneOfString",
expected: "OneOfString jābūt vienam no [red green]",
},
{
ns: "Test.OneOfInt",
expected: "OneOfInt jābūt vienam no [5 63]",
},
{
ns: "Test.UniqueSlice",
expected: "UniqueSlice jāsatur unikālas vērtības",
},
{
ns: "Test.UniqueArray",
expected: "UniqueArray jāsatur unikālas vērtības",
},
{
ns: "Test.UniqueMap",
expected: "UniqueMap jāsatur unikālas vērtības",
},
{
ns: "Test.JSONString",
expected: "JSONString jābūt derīgai json virknei",
},
{
ns: "Test.JWTString",
expected: "JWTString jābūt derīgai jwt virknei",
},
{
ns: "Test.LowercaseString",
expected: "LowercaseString jābūt mazo burtu virknei",
},
{
ns: "Test.UppercaseString",
expected: "UppercaseString jābūt lielo burtu virknei",
},
{
ns: "Test.Datetime",
expected: "Datetime neatbilst formātam 2006-01-02",
},
{
ns: "Test.PostCode",
expected: "PostCode neatbilst pasta indeksa formātam valstī SG",
},
{
ns: "Test.PostCodeByField",
expected: "PostCodeByField neatbilst pasta indeksa formātam valstī, kura norādīta laukā PostCodeCountry",
},
{
ns: "Test.BooleanString",
expected: "BooleanString jābūt derīgai boolean vērtībai",
},
}
for _, tt := range tests {
var fe validator.FieldError
for _, e := range errs {
if tt.ns == e.Namespace() {
fe = e
break
}
}
NotEqual(t, fe, nil)
Equal(t, tt.expected, fe.Translate(trans))
}
}

@ -26,7 +26,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
}{ }{
{ {
tag: "required", tag: "required",
translation: "{0} é um campo requerido", translation: "{0} é um campo obrigatório",
override: false, override: false,
}, },
{ {
@ -1321,6 +1321,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0} deve ser um valor booleano válido", translation: "{0} deve ser um valor booleano válido",
override: false, override: false,
}, },
{
tag: "cve",
translation: "{0} deve ser um identificador cve válido",
override: false,
},
} }
for _, t := range translations { for _, t := range translations {

@ -140,6 +140,7 @@ func TestTranslations(t *testing.T) {
OneOfString string `validate:"oneof=red green"` OneOfString string `validate:"oneof=red green"`
OneOfInt int `validate:"oneof=5 63"` OneOfInt int `validate:"oneof=5 63"`
BooleanString string `validate:"boolean"` BooleanString string `validate:"boolean"`
CveString string `validate:"cve"`
} }
var test Test var test Test
@ -173,6 +174,7 @@ func TestTranslations(t *testing.T) {
test.NumericString = "12E.00" test.NumericString = "12E.00"
test.NumberString = "12E" test.NumberString = "12E"
test.BooleanString = "A" test.BooleanString = "A"
test.CveString = "A"
test.Excludes = "este é um texto de teste" test.Excludes = "este é um texto de teste"
test.ExcludesAll = "Isso é Ótimo!" test.ExcludesAll = "Isso é Ótimo!"
@ -575,15 +577,15 @@ func TestTranslations(t *testing.T) {
}, },
{ {
ns: "Test.RequiredString", ns: "Test.RequiredString",
expected: "RequiredString é um campo requerido", expected: "RequiredString é um campo obrigatório",
}, },
{ {
ns: "Test.RequiredNumber", ns: "Test.RequiredNumber",
expected: "RequiredNumber é um campo requerido", expected: "RequiredNumber é um campo obrigatório",
}, },
{ {
ns: "Test.RequiredMultiple", ns: "Test.RequiredMultiple",
expected: "RequiredMultiple é um campo requerido", expected: "RequiredMultiple é um campo obrigatório",
}, },
{ {
ns: "Test.StrPtrMinLen", ns: "Test.StrPtrMinLen",
@ -625,6 +627,10 @@ func TestTranslations(t *testing.T) {
ns: "Test.BooleanString", ns: "Test.BooleanString",
expected: "BooleanString deve ser um valor booleano válido", expected: "BooleanString deve ser um valor booleano válido",
}, },
{
ns: "Test.CveString",
expected: "CveString deve ser um identificador cve válido",
},
} }
for _, tt := range tests { for _, tt := range tests {

@ -452,7 +452,6 @@ OUTER:
v.ct = ct v.ct = ct
if !ct.fn(ctx, v) { if !ct.fn(ctx, v) {
v.str1 = string(append(ns, cf.altName...)) v.str1 = string(append(ns, cf.altName...))
if v.v.hasTagNameFunc { if v.v.hasTagNameFunc {

@ -615,7 +615,7 @@ func (v *Validate) Var(field interface{}, tag string) error {
} }
// VarCtx validates a single variable using tag style validation and allows passing of contextual // VarCtx validates a single variable using tag style validation and allows passing of contextual
// validation validation information via context.Context. // validation information via context.Context.
// eg. // eg.
// var i int // var i int
// validate.Var(i, "gt=1,lt=10") // validate.Var(i, "gt=1,lt=10")
@ -634,6 +634,7 @@ func (v *Validate) VarCtx(ctx context.Context, field interface{}, tag string) (e
} }
ctag := v.fetchCacheTag(tag) ctag := v.fetchCacheTag(tag)
val := reflect.ValueOf(field) val := reflect.ValueOf(field)
vd := v.pool.Get().(*validate) vd := v.pool.Get().(*validate)
vd.top = val vd.top = val

@ -8,6 +8,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings" "strings"
@ -2038,10 +2039,10 @@ func TestCrossNamespaceFieldValidation(t *testing.T) {
Equal(t, kind, reflect.String) Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2") Equal(t, current.String(), "val2")
current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.CrazyNonExistantField") _, _, _, ok = v.getStructFieldOKInternal(val, "Inner.CrazyNonExistantField")
Equal(t, ok, false) Equal(t, ok, false)
current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[101]") _, _, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[101]")
Equal(t, ok, false) Equal(t, ok, false)
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.Map[key3]") current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.Map[key3]")
@ -3819,12 +3820,14 @@ func TestDataURIValidation(t *testing.T) {
{"data:image/png;base64,TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=", true}, {"data:image/png;base64,TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=", true},
{"data:text/plain;base64,Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg==", true}, {"data:text/plain;base64,Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg==", true},
{"image/gif;base64,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", false}, {"image/gif;base64,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", false},
{"data:image/gif;base64,MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw" + {
"data:image/gif;base64,MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw" +
"UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye" + "UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye" +
"rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619" + "rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619" +
"FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx" + "FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx" +
"QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ" + "QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ" +
"Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ" + "HQIDAQAB", true}, "Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ" + "HQIDAQAB", true,
},
{"data:image/png;base64,12345", false}, {"data:image/png;base64,12345", false},
{"", false}, {"", false},
{"data:text,:;base85,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", false}, {"data:text,:;base85,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", false},
@ -5207,6 +5210,24 @@ func TestIsNeValidation(t *testing.T) {
Equal(t, errs, nil) Equal(t, errs, nil)
} }
func TestIsNeIgnoreCaseValidation(t *testing.T) {
var errs error
validate := New()
s := "abcd"
now := time.Now()
errs = validate.Var(s, "ne_ignore_case=efgh")
Equal(t, errs, nil)
errs = validate.Var(s, "ne_ignore_case=AbCd")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "ne_ignore_case")
PanicMatches(
t, func() { _ = validate.Var(now, "ne_ignore_case=abcd") }, "Bad field type time.Time",
)
}
func TestIsEqFieldValidation(t *testing.T) { func TestIsEqFieldValidation(t *testing.T) {
var errs error var errs error
validate := New() validate := New()
@ -5484,6 +5505,23 @@ func TestIsEqValidation(t *testing.T) {
Equal(t, errs, nil) Equal(t, errs, nil)
} }
func TestIsEqIgnoreCaseValidation(t *testing.T) {
var errs error
validate := New()
s := "abcd"
now := time.Now()
errs = validate.Var(s, "eq_ignore_case=abcd")
Equal(t, errs, nil)
errs = validate.Var(s, "eq_ignore_case=AbCd")
Equal(t, errs, nil)
PanicMatches(
t, func() { _ = validate.Var(now, "eq_ignore_case=abcd") }, "Bad field type time.Time",
)
}
func TestOneOfValidation(t *testing.T) { func TestOneOfValidation(t *testing.T) {
validate := New() validate := New()
@ -5617,6 +5655,53 @@ func TestBase64URLValidation(t *testing.T) {
} }
} }
func TestBase64RawURLValidation(t *testing.T) {
validate := New()
testCases := []struct {
decoded, encoded string
success bool
}{
// empty string, although a valid base64 string, should fail
{"", "", false},
// invalid length
{"", "a", false},
// base64 with padding should fail
{"f", "Zg==", false},
{"fo", "Zm8=", false},
// base64 without padding
{"foo", "Zm9v", true},
{"hello", "aGVsbG8", true},
{"", "aGVsb", false},
// // base64 URL safe encoding with invalid, special characters '+' and '/'
{"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+", false},
{"\x14\xfb\x9c\x03\xf9\x73", "FPucA/lz", false},
// // base64 URL safe encoding with valid, special characters '-' and '_'
{"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l-", true},
{"\x14\xfb\x9c\x03\xf9\x73", "FPucA_lz", true},
// non base64 characters
{"", "@mc=", false},
{"", "Zm 9", false},
}
for _, tc := range testCases {
err := validate.Var(tc.encoded, "base64rawurl")
if tc.success {
Equal(t, err, nil)
// make sure encoded value is decoded back to the expected value
d, innerErr := base64.RawURLEncoding.DecodeString(tc.encoded)
Equal(t, innerErr, nil)
Equal(t, tc.decoded, string(d))
} else {
NotEqual(t, err, nil)
if len(tc.encoded) > 0 {
// make sure that indeed the encoded value was faulty
_, err := base64.RawURLEncoding.DecodeString(tc.encoded)
NotEqual(t, err, nil)
}
}
}
}
func TestFileValidation(t *testing.T) { func TestFileValidation(t *testing.T) {
validate := New() validate := New()
@ -5650,6 +5735,39 @@ func TestFileValidation(t *testing.T) {
}, "Bad field type int") }, "Bad field type int")
} }
func TestFilePathValidation(t *testing.T) {
validate := New()
tests := []struct {
title string
param string
expected bool
}{
{"empty filepath", "", false},
{"valid filepath", filepath.Join("testdata", "a.go"), true},
{"invalid filepath", filepath.Join("testdata", "no\000.go"), false},
{"directory, not a filepath", "testdata" + string(os.PathSeparator), false},
}
for _, test := range tests {
errs := validate.Var(test.param, "filepath")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Test: '%s' failed Error: %s", test.title, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Test: '%s' failed Error: %s", test.title, errs)
}
}
}
PanicMatches(t, func() {
_ = validate.Var(6, "filepath")
}, "Bad field type int")
}
func TestEthereumAddressValidation(t *testing.T) { func TestEthereumAddressValidation(t *testing.T) {
validate := New() validate := New()
@ -5672,7 +5790,7 @@ func TestEthereumAddressValidation(t *testing.T) {
{"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true}, {"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true},
{"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true}, {"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true},
{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true}, {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true},
{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", false}, // Invalid checksum. {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", true}, // Invalid checksum, but valid address.
// Other. // Other.
{"", false}, {"", false},
@ -5703,6 +5821,56 @@ func TestEthereumAddressValidation(t *testing.T) {
} }
} }
func TestEthereumAddressChecksumValidation(t *testing.T) {
validate := New()
tests := []struct {
param string
expected bool
}{
// All caps.
{"0x52908400098527886E0F7030069857D2E4169EE7", true},
{"0x8617E340B3D01FA5F11F306F4090FD50E238070D", true},
// All lower.
{"0x27b1fdb04752bbc536007a920d24acb045561c26", true},
{"0x123f681646d4a755815f9cb19e1acc8565a0c2ac", false},
// Mixed case: runs checksum validation.
{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true},
{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", false}, // Invalid checksum.
{"0x000000000000000000000000000000000000dead", false}, // Invalid checksum.
{"0x000000000000000000000000000000000000dEaD", true}, // Valid checksum.
// Other.
{"", false},
{"D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", false}, // Missing "0x" prefix.
{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDbc", false}, // More than 40 hex digits.
{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aD", false}, // Less than 40 hex digits.
{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDw", false}, // Invalid hex digit "w".
}
for i, test := range tests {
errs := validate.Var(test.param, "eth_addr_checksum")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d eth_addr_checksum failed Error: %s", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d eth_addr_checksum failed Error: %s", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "eth_addr_checksum" {
t.Fatalf("Index: %d Latitude failed Error: %s", i, errs)
}
}
}
}
}
func TestBitcoinAddressValidation(t *testing.T) { func TestBitcoinAddressValidation(t *testing.T) {
validate := New() validate := New()
@ -7793,6 +7961,77 @@ func TestUrl(t *testing.T) {
PanicMatches(t, func() { _ = validate.Var(i, "url") }, "Bad field type int") PanicMatches(t, func() { _ = validate.Var(i, "url") }, "Bad field type int")
} }
func TestHttpUrl(t *testing.T) {
tests := []struct {
param string
expected bool
}{
{"http://foo.bar#com", true},
{"http://foobar.com", true},
{"HTTP://foobar.com", true},
{"https://foobar.com", true},
{"foobar.com", false},
{"http://foobar.coffee/", true},
{"http://foobar.中文网/", true},
{"http://foobar.org/", true},
{"http://foobar.org:8080/", true},
{"ftp://foobar.ru/", false},
{"file:///etc/passwd", false},
{"file://C:/windows/win.ini", false},
{"http://user:pass@www.foobar.com/", true},
{"http://127.0.0.1/", true},
{"http://duckduckgo.com/?q=%2F", true},
{"http://localhost:3000/", true},
{"http://foobar.com/?foo=bar#baz=qux", true},
{"http://foobar.com?foo=bar", true},
{"http://www.xn--froschgrn-x9a.net/", true},
{"", false},
{"a://b", false},
{"xyz://foobar.com", false},
{"invalid.", false},
{".com", false},
{"rtmp://foobar.com", false},
{"http://www.foo_bar.com/", true},
{"http://localhost:3000/", true},
{"http://foobar.com/#baz", true},
{"http://foobar.com#baz=qux", true},
{"http://foobar.com/t$-_.+!*\\'(),", true},
{"http://www.foobar.com/~foobar", true},
{"http://www.-foobar.com/", true},
{"http://www.foo---bar.com/", true},
{"mailto:someone@example.com", false},
{"irc://irc.server.org/channel", false},
{"irc://#channel@network", false},
{"/abs/test/dir", false},
{"./rel/test/dir", false},
}
validate := New()
for i, test := range tests {
errs := validate.Var(test.param, "http_url")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d HTTP URL failed Error: %s", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d HTTP URL failed Error: %s", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "http_url" {
t.Fatalf("Index: %d HTTP URL failed Error: %s", i, errs)
}
}
}
}
i := 1
PanicMatches(t, func() { _ = validate.Var(i, "http_url") }, "Bad field type int")
}
func TestUri(t *testing.T) { func TestUri(t *testing.T) {
tests := []struct { tests := []struct {
param string param string
@ -8302,6 +8541,43 @@ func TestNumeric(t *testing.T) {
errs = validate.Var(i, "numeric") errs = validate.Var(i, "numeric")
Equal(t, errs, nil) Equal(t, errs, nil)
} }
func TestBoolean(t *testing.T) {
validate := New()
b := true
errs := validate.Var(b, "boolean")
Equal(t, errs, nil)
b = false
errs = validate.Var(b, "boolean")
Equal(t, errs, nil)
s := "true"
errs = validate.Var(s, "boolean")
Equal(t, errs, nil)
s = "false"
errs = validate.Var(s, "boolean")
Equal(t, errs, nil)
s = "0"
errs = validate.Var(s, "boolean")
Equal(t, errs, nil)
s = "1"
errs = validate.Var(s, "boolean")
Equal(t, errs, nil)
s = "xyz"
errs = validate.Var(s, "boolean")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "boolean")
s = "1."
errs = validate.Var(s, "boolean")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "boolean")
}
func TestAlphaNumeric(t *testing.T) { func TestAlphaNumeric(t *testing.T) {
validate := New() validate := New()
@ -9768,6 +10044,12 @@ func TestUniqueValidation(t *testing.T) {
{map[string]string{"one": "a", "two": "a"}, false}, {map[string]string{"one": "a", "two": "a"}, false},
{map[string]interface{}{"one": "a", "two": "a"}, false}, {map[string]interface{}{"one": "a", "two": "a"}, false},
{map[string]interface{}{"one": "a", "two": 1, "three": "b", "four": 1}, false}, {map[string]interface{}{"one": "a", "two": 1, "three": "b", "four": 1}, false},
{map[string]*string{"one": stringPtr("a"), "two": stringPtr("a")}, false},
{map[string]*string{"one": stringPtr("a"), "two": stringPtr("b")}, true},
{map[string]*int{"one": intPtr(1), "two": intPtr(1)}, false},
{map[string]*int{"one": intPtr(1), "two": intPtr(2)}, true},
{map[string]*float64{"one": float64Ptr(1.1), "two": float64Ptr(1.1)}, false},
{map[string]*float64{"one": float64Ptr(1.1), "two": float64Ptr(1.2)}, true},
} }
validate := New() validate := New()
@ -9792,6 +10074,41 @@ func TestUniqueValidation(t *testing.T) {
} }
} }
PanicMatches(t, func() { _ = validate.Var(1.0, "unique") }, "Bad field type float64") PanicMatches(t, func() { _ = validate.Var(1.0, "unique") }, "Bad field type float64")
t.Run("struct", func(t *testing.T) {
tests := []struct {
param interface{}
expected bool
}{
{struct {
A string `validate:"unique=B"`
B string
}{A: "abc", B: "bcd"}, true},
{struct {
A string `validate:"unique=B"`
B string
}{A: "abc", B: "abc"}, false},
}
validate := New()
for i, test := range tests {
errs := validate.Struct(test.param)
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d unique failed Error: %v", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d unique failed Error: %v", i, errs)
} else {
val := getError(errs, "A", "A")
if val.Tag() != "unique" {
t.Fatalf("Index: %d unique failed Error: %v", i, errs)
}
}
}
}
})
} }
func TestUniqueValidationStructSlice(t *testing.T) { func TestUniqueValidationStructSlice(t *testing.T) {
@ -9844,6 +10161,7 @@ func TestUniqueValidationStructPtrSlice(t *testing.T) {
}{ }{
{A: stringPtr("one"), B: stringPtr("two")}, {A: stringPtr("one"), B: stringPtr("two")},
{A: stringPtr("one"), B: stringPtr("three")}, {A: stringPtr("one"), B: stringPtr("three")},
{},
} }
tests := []struct { tests := []struct {
@ -10287,6 +10605,40 @@ func TestDirValidation(t *testing.T) {
}, "Bad field type int") }, "Bad field type int")
} }
func TestDirPathValidation(t *testing.T) {
validate := New()
tests := []struct {
title string
param string
expected bool
}{
{"empty dirpath", "", false},
{"valid dirpath - exists", "testdata", true},
{"valid dirpath - explicit", "testdatanoexist" + string(os.PathSeparator), true},
{"invalid dirpath", "testdata\000" + string(os.PathSeparator), false},
{"file, not a dirpath", filepath.Join("testdata", "a.go"), false},
}
for _, test := range tests {
errs := validate.Var(test.param, "dirpath")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Test: '%s' failed Error: %s", test.title, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Test: '%s' failed Error: %s", test.title, errs)
}
}
}
PanicMatches(t, func() {
_ = validate.Var(6, "filepath")
}, "Bad field type int")
}
func TestStartsWithValidation(t *testing.T) { func TestStartsWithValidation(t *testing.T) {
tests := []struct { tests := []struct {
Value string `validate:"startswith=(/^ヮ^)/*:・゚✧"` Value string `validate:"startswith=(/^ヮ^)/*:・゚✧"`
@ -11213,7 +11565,7 @@ func TestExcludedUnless(t *testing.T) {
FieldE string `validate:"omitempty" json:"field_e"` FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"` FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
}{ }{
FieldE: "notest", FieldE: "test",
FieldER: "filled", FieldER: "filled",
} }
errs := validate.Struct(test) errs := validate.Struct(test)
@ -11223,7 +11575,7 @@ func TestExcludedUnless(t *testing.T) {
FieldE string `validate:"omitempty" json:"field_e"` FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"` FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
}{ }{
FieldE: "test", FieldE: "notest",
FieldER: "filled", FieldER: "filled",
} }
errs = validate.Struct(test2) errs = validate.Struct(test2)
@ -11232,7 +11584,26 @@ func TestExcludedUnless(t *testing.T) {
Equal(t, len(ve), 1) Equal(t, len(ve), 1)
AssertError(t, errs, "FieldER", "FieldER", "FieldER", "FieldER", "excluded_unless") AssertError(t, errs, "FieldER", "FieldER", "FieldER", "FieldER", "excluded_unless")
shouldError := "test" // test5 and test6: excluded_unless has no effect if FieldER is left blank
test5 := struct {
FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
}{
FieldE: "test",
}
errs = validate.Struct(test5)
Equal(t, errs, nil)
test6 := struct {
FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
}{
FieldE: "notest",
}
errs = validate.Struct(test6)
Equal(t, errs, nil)
shouldError := "notest"
test3 := struct { test3 := struct {
Inner *Inner Inner *Inner
Field1 string `validate:"excluded_unless=Inner.Field test" json:"field_1"` Field1 string `validate:"excluded_unless=Inner.Field test" json:"field_1"`
@ -11246,7 +11617,7 @@ func TestExcludedUnless(t *testing.T) {
Equal(t, len(ve), 1) Equal(t, len(ve), 1)
AssertError(t, errs, "Field1", "Field1", "Field1", "Field1", "excluded_unless") AssertError(t, errs, "Field1", "Field1", "Field1", "Field1", "excluded_unless")
shouldPass := "shouldPass" shouldPass := "test"
test4 := struct { test4 := struct {
Inner *Inner Inner *Inner
FieldE string `validate:"omitempty" json:"field_e"` FieldE string `validate:"omitempty" json:"field_e"`
@ -11258,6 +11629,26 @@ func TestExcludedUnless(t *testing.T) {
errs = validate.Struct(test4) errs = validate.Struct(test4)
Equal(t, errs, nil) Equal(t, errs, nil)
// test7 and test8: excluded_unless has no effect if FieldER is left blank
test7 := struct {
Inner *Inner
FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"excluded_unless=Inner.Field test" json:"field_er"`
}{
FieldE: "test",
}
errs = validate.Struct(test7)
Equal(t, errs, nil)
test8 := struct {
FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"excluded_unless=Inner.Field test" json:"field_er"`
}{
FieldE: "test",
}
errs = validate.Struct(test8)
Equal(t, errs, nil)
// Checks number of params in struct tag is correct // Checks number of params in struct tag is correct
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
@ -12059,6 +12450,50 @@ func TestSemverFormatValidation(t *testing.T) {
} }
} }
func TestCveFormatValidation(t *testing.T) {
tests := []struct {
value string `validate:"cve"`
tag string
expected bool
}{
{"CVE-1999-0001", "cve", true},
{"CVE-1998-0001", "cve", false},
{"CVE-2000-0001", "cve", true},
{"CVE-2222-0001", "cve", true},
{"2222-0001", "cve", false},
{"-2222-0001", "cve", false},
{"CVE22220001", "cve", false},
{"CVE-2222-000001", "cve", false},
{"CVE-2222-100001", "cve", true},
{"CVE-2222-99999999999", "cve", true},
{"CVE-3000-0001", "cve", false},
{"CVE-1999-0000", "cve", false},
{"CVE-2099-0000", "cve", 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 cve failed Error: %s", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d cve failed Error: %s", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "cve" {
t.Fatalf("Index: %d cve failed Error: %s", i, errs)
}
}
}
}
}
func TestRFC1035LabelFormatValidation(t *testing.T) { func TestRFC1035LabelFormatValidation(t *testing.T) {
tests := []struct { tests := []struct {
value string `validate:"dns_rfc1035_label"` value string `validate:"dns_rfc1035_label"`
@ -12116,10 +12551,12 @@ func TestPostCodeByIso3166Alpha2(t *testing.T) {
{"00803", true}, {"00803", true},
{"1234567", false}, {"1234567", false},
}, },
"LC": { // not support regexp for post code "LC": {
// not support regexp for post code
{"123456", false}, {"123456", false},
}, },
"XX": { // not support country "XX": {
// not support country
{"123456", false}, {"123456", false},
}, },
} }
@ -12301,6 +12738,42 @@ func TestValidate_ValidateMapCtx(t *testing.T) {
} }
} }
func TestMongoDBObjectIDFormatValidation(t *testing.T) {
tests := []struct {
value string `validate:"mongodb"`
tag string
expected bool
}{
{"507f191e810c19729de860ea", "mongodb", true},
{"507f191e810c19729de860eG", "mongodb", false},
{"M07f191e810c19729de860eG", "mongodb", false},
{"07f191e810c19729de860ea", "mongodb", false},
{"507f191e810c19729de860e", "mongodb", false},
{"507f191e810c19729de860ea4", "mongodb", 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 mongodb failed Error: %s", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d mongodb failed Error: %s", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "mongodb" {
t.Fatalf("Index: %d mongodb failed Error: %s", i, errs)
}
}
}
}
}
func TestCreditCardFormatValidation(t *testing.T) { func TestCreditCardFormatValidation(t *testing.T) {
tests := []struct { tests := []struct {
value string `validate:"credit_card"` value string `validate:"credit_card"`
@ -12339,6 +12812,51 @@ func TestCreditCardFormatValidation(t *testing.T) {
} }
} }
func TestLuhnChecksumValidation(t *testing.T) {
testsUint := []struct {
value interface{} `validate:"luhn_checksum"` // the type is interface{} because the luhn_checksum works on both strings and numbers
tag string
expected bool
}{
{uint64(586824160825533338), "luhn_checksum", true}, // credit card numbers are just special cases of numbers with luhn checksum
{586824160825533338, "luhn_checksum", true},
{"586824160825533338", "luhn_checksum", true},
{uint64(586824160825533328), "luhn_checksum", false},
{586824160825533328, "luhn_checksum", false},
{"586824160825533328", "luhn_checksum", false},
{10000000116, "luhn_checksum", true}, // but there may be shorter numbers (11 digits)
{"10000000116", "luhn_checksum", true},
{10000000117, "luhn_checksum", false},
{"10000000117", "luhn_checksum", false},
{uint64(12345678123456789011), "luhn_checksum", true}, // or longer numbers (19 digits)
{"12345678123456789011", "luhn_checksum", true},
{1, "luhn_checksum", false}, // single digits (checksum only) are not allowed
{"1", "luhn_checksum", false},
{-10, "luhn_checksum", false}, // negative ints are not allowed
{"abcdefghijklmnop", "luhn_checksum", false},
}
validate := New()
for i, test := range testsUint {
errs := validate.Var(test.value, test.tag)
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d luhn_checksum failed Error: %s", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d luhn_checksum failed Error: %s", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "luhn_checksum" {
t.Fatalf("Index: %d luhn_checksum failed Error: %s", i, errs)
}
}
}
}
}
func TestMultiOrOperatorGroup(t *testing.T) { func TestMultiOrOperatorGroup(t *testing.T) {
tests := []struct { tests := []struct {
Value int `validate:"eq=1|gte=5,eq=1|lt=7"` Value int `validate:"eq=1|gte=5,eq=1|lt=7"`
@ -12362,3 +12880,37 @@ func TestMultiOrOperatorGroup(t *testing.T) {
} }
} }
} }
func TestCronExpressionValidation(t *testing.T) {
tests := []struct {
value string `validate:"cron"`
tag string
expected bool
}{
{"0 0 12 * * ?", "cron", true},
{"0 15 10 ? * *", "cron", true},
{"0 15 10 * * ?", "cron", true},
{"0 15 10 * * ? 2005", "cron", true},
{"0 15 10 ? * 6L", "cron", true},
{"0 15 10 ? * 6L 2002-2005", "cron", true},
{"*/20 * * * *", "cron", true},
{"0 15 10 ? * MON-FRI", "cron", true},
{"0 15 10 ? * 6#3", "cron", true},
{"wrong", "cron", 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 cron "%s" failed Error: %s`, i, test.value, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf(`Index: %d cron "%s" should have errs`, i, test.value)
}
}
}
}
Loading…
Cancel
Save