pull/379/head
foolin 6 years ago
commit 11caf00975
  1. 16
      Makefile
  2. 126
      README.md
  3. 5
      _examples/gin-upgrading-overriding/v8_to_v9.go
  4. 2
      _examples/simple/main.go
  5. 204
      baked_in.go
  6. 2
      cache.go
  7. 104
      doc.go
  8. 2
      field_level.go
  9. 136
      regexes.go
  10. 1
      testdata/a.go
  11. 2
      translations.go
  12. 12
      translations/en/en.go
  13. 12
      translations/fr/fr.go
  14. 12
      translations/pt_BR/pt_BR.go
  15. 4
      validator.go
  16. 12
      validator_instance.go
  17. 450
      validator_test.go

@ -0,0 +1,16 @@
GOCMD=go
linters-install:
$(GOCMD) get -u github.com/alecthomas/gometalinter
gometalinter --install
lint: linters-install
gometalinter --vendor --disable-all --enable=vet --enable=vetshadow --enable=golint --enable=maligned --enable=megacheck --enable=ineffassign --enable=misspell --enable=errcheck --enable=goconst ./...
test:
$(GOCMD) test -cover -race ./...
bench:
$(GOCMD) test -bench=. -benchmem ./...
.PHONY: test lint linters-install

@ -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/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)
![Project status](https://img.shields.io/badge/version-9.13.0-green.svg) ![Project status](https://img.shields.io/badge/version-9.19.0-green.svg)
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
@ -66,71 +66,73 @@ Please see http://godoc.org/gopkg.in/go-playground/validator.v9 for detailed usa
Benchmarks Benchmarks
------ ------
###### Run on MacBook Pro (15-inch, 2017) Go version go1.9.4 darwin/amd64 ###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64
```go ```go
goos: darwin goos: darwin
goarch: amd64 goarch: amd64
pkg: github.com/go-playground/validator pkg: github.com/go-playground/validator
BenchmarkFieldSuccess-8 20000000 86.4 ns/op 0 B/op 0 allocs/op BenchmarkFieldSuccess-8 20000000 83.6 ns/op 0 B/op 0 allocs/op
BenchmarkFieldSuccessParallel-8 50000000 27.6 ns/op 0 B/op 0 allocs/op BenchmarkFieldSuccessParallel-8 50000000 26.8 ns/op 0 B/op 0 allocs/op
BenchmarkFieldFailure-8 5000000 297 ns/op 208 B/op 4 allocs/op BenchmarkFieldFailure-8 5000000 291 ns/op 208 B/op 4 allocs/op
BenchmarkFieldFailureParallel-8 20000000 107 ns/op 208 B/op 4 allocs/op BenchmarkFieldFailureParallel-8 20000000 107 ns/op 208 B/op 4 allocs/op
BenchmarkFieldArrayDiveSuccess-8 2000000 618 ns/op 201 B/op 11 allocs/op BenchmarkFieldArrayDiveSuccess-8 2000000 623 ns/op 201 B/op 11 allocs/op
BenchmarkFieldArrayDiveSuccessParallel-8 10000000 225 ns/op 201 B/op 11 allocs/op BenchmarkFieldArrayDiveSuccessParallel-8 10000000 237 ns/op 201 B/op 11 allocs/op
BenchmarkFieldArrayDiveFailure-8 2000000 863 ns/op 412 B/op 16 allocs/op BenchmarkFieldArrayDiveFailure-8 2000000 859 ns/op 412 B/op 16 allocs/op
BenchmarkFieldArrayDiveFailureParallel-8 5000000 322 ns/op 413 B/op 16 allocs/op BenchmarkFieldArrayDiveFailureParallel-8 5000000 335 ns/op 413 B/op 16 allocs/op
BenchmarkFieldMapDiveSuccess-8 1000000 1336 ns/op 432 B/op 18 allocs/op BenchmarkFieldMapDiveSuccess-8 1000000 1292 ns/op 432 B/op 18 allocs/op
BenchmarkFieldMapDiveSuccessParallel-8 3000000 474 ns/op 432 B/op 18 allocs/op BenchmarkFieldMapDiveSuccessParallel-8 3000000 467 ns/op 432 B/op 18 allocs/op
BenchmarkFieldMapDiveFailure-8 1000000 1103 ns/op 512 B/op 16 allocs/op BenchmarkFieldMapDiveFailure-8 1000000 1082 ns/op 512 B/op 16 allocs/op
BenchmarkFieldMapDiveFailureParallel-8 5000000 412 ns/op 512 B/op 16 allocs/op BenchmarkFieldMapDiveFailureParallel-8 5000000 425 ns/op 512 B/op 16 allocs/op
BenchmarkFieldMapDiveWithKeysSuccess-8 1000000 1572 ns/op 480 B/op 21 allocs/op BenchmarkFieldMapDiveWithKeysSuccess-8 1000000 1539 ns/op 480 B/op 21 allocs/op
BenchmarkFieldMapDiveWithKeysSuccessParallel-8 3000000 615 ns/op 480 B/op 21 allocs/op BenchmarkFieldMapDiveWithKeysSuccessParallel-8 3000000 613 ns/op 480 B/op 21 allocs/op
BenchmarkFieldMapDiveWithKeysFailure-8 1000000 1438 ns/op 721 B/op 21 allocs/op BenchmarkFieldMapDiveWithKeysFailure-8 1000000 1413 ns/op 721 B/op 21 allocs/op
BenchmarkFieldMapDiveWithKeysFailureParallel-8 3000000 543 ns/op 721 B/op 21 allocs/op BenchmarkFieldMapDiveWithKeysFailureParallel-8 3000000 575 ns/op 721 B/op 21 allocs/op
BenchmarkFieldCustomTypeSuccess-8 10000000 230 ns/op 32 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccess-8 10000000 216 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.5 ns/op 32 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.2 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-8 5000000 284 ns/op 208 B/op 4 allocs/op BenchmarkFieldCustomTypeFailure-8 5000000 274 ns/op 208 B/op 4 allocs/op
BenchmarkFieldCustomTypeFailureParallel-8 20000000 118 ns/op 208 B/op 4 allocs/op BenchmarkFieldCustomTypeFailureParallel-8 20000000 116 ns/op 208 B/op 4 allocs/op
BenchmarkFieldOrTagSuccess-8 2000000 824 ns/op 16 B/op 1 allocs/op BenchmarkFieldOrTagSuccess-8 2000000 740 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagSuccessParallel-8 3000000 472 ns/op 16 B/op 1 allocs/op BenchmarkFieldOrTagSuccessParallel-8 3000000 474 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagFailure-8 3000000 487 ns/op 224 B/op 5 allocs/op BenchmarkFieldOrTagFailure-8 3000000 471 ns/op 224 B/op 5 allocs/op
BenchmarkFieldOrTagFailureParallel-8 5000000 405 ns/op 224 B/op 5 allocs/op BenchmarkFieldOrTagFailureParallel-8 3000000 414 ns/op 224 B/op 5 allocs/op
BenchmarkStructLevelValidationSuccess-8 10000000 214 ns/op 32 B/op 2 allocs/op BenchmarkStructLevelValidationSuccess-8 10000000 213 ns/op 32 B/op 2 allocs/op
BenchmarkStructLevelValidationSuccessParallel-8 20000000 78.0 ns/op 32 B/op 2 allocs/op BenchmarkStructLevelValidationSuccessParallel-8 20000000 91.8 ns/op 32 B/op 2 allocs/op
BenchmarkStructLevelValidationFailure-8 3000000 475 ns/op 304 B/op 8 allocs/op BenchmarkStructLevelValidationFailure-8 3000000 473 ns/op 304 B/op 8 allocs/op
BenchmarkStructLevelValidationFailureParallel-8 10000000 200 ns/op 304 B/op 8 allocs/op BenchmarkStructLevelValidationFailureParallel-8 10000000 234 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-8 3000000 403 ns/op 32 B/op 2 allocs/op BenchmarkStructSimpleCustomTypeSuccess-8 5000000 385 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 143 ns/op 32 B/op 2 allocs/op BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 161 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeFailure-8 2000000 655 ns/op 424 B/op 9 allocs/op BenchmarkStructSimpleCustomTypeFailure-8 2000000 640 ns/op 424 B/op 9 allocs/op
BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 286 ns/op 440 B/op 10 allocs/op BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 318 ns/op 440 B/op 10 allocs/op
BenchmarkStructFilteredSuccess-8 2000000 598 ns/op 288 B/op 9 allocs/op BenchmarkStructFilteredSuccess-8 2000000 597 ns/op 288 B/op 9 allocs/op
BenchmarkStructFilteredSuccessParallel-8 10000000 231 ns/op 288 B/op 9 allocs/op BenchmarkStructFilteredSuccessParallel-8 10000000 266 ns/op 288 B/op 9 allocs/op
BenchmarkStructFilteredFailure-8 3000000 455 ns/op 256 B/op 7 allocs/op BenchmarkStructFilteredFailure-8 3000000 454 ns/op 256 B/op 7 allocs/op
BenchmarkStructFilteredFailureParallel-8 10000000 197 ns/op 256 B/op 7 allocs/op BenchmarkStructFilteredFailureParallel-8 10000000 214 ns/op 256 B/op 7 allocs/op
BenchmarkStructPartialSuccess-8 3000000 552 ns/op 256 B/op 6 allocs/op BenchmarkStructPartialSuccess-8 3000000 502 ns/op 256 B/op 6 allocs/op
BenchmarkStructPartialSuccessParallel-8 10000000 206 ns/op 256 B/op 6 allocs/op BenchmarkStructPartialSuccessParallel-8 10000000 225 ns/op 256 B/op 6 allocs/op
BenchmarkStructPartialFailure-8 2000000 750 ns/op 480 B/op 11 allocs/op BenchmarkStructPartialFailure-8 2000000 702 ns/op 480 B/op 11 allocs/op
BenchmarkStructPartialFailureParallel-8 5000000 317 ns/op 480 B/op 11 allocs/op BenchmarkStructPartialFailureParallel-8 5000000 329 ns/op 480 B/op 11 allocs/op
BenchmarkStructExceptSuccess-8 2000000 853 ns/op 496 B/op 12 allocs/op BenchmarkStructExceptSuccess-8 2000000 793 ns/op 496 B/op 12 allocs/op
BenchmarkStructExceptSuccessParallel-8 10000000 179 ns/op 240 B/op 5 allocs/op BenchmarkStructExceptSuccessParallel-8 10000000 193 ns/op 240 B/op 5 allocs/op
BenchmarkStructExceptFailure-8 2000000 698 ns/op 464 B/op 10 allocs/op BenchmarkStructExceptFailure-8 2000000 639 ns/op 464 B/op 10 allocs/op
BenchmarkStructExceptFailureParallel-8 5000000 276 ns/op 464 B/op 10 allocs/op BenchmarkStructExceptFailureParallel-8 5000000 300 ns/op 464 B/op 10 allocs/op
BenchmarkStructSimpleCrossFieldSuccess-8 3000000 412 ns/op 72 B/op 3 allocs/op BenchmarkStructSimpleCrossFieldSuccess-8 3000000 417 ns/op 72 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 148 ns/op 72 B/op 3 allocs/op BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 163 ns/op 72 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldFailure-8 2000000 630 ns/op 304 B/op 8 allocs/op BenchmarkStructSimpleCrossFieldFailure-8 2000000 645 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCrossFieldFailureParallel-8 10000000 244 ns/op 304 B/op 8 allocs/op BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 285 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 610 ns/op 80 B/op 4 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 3000000 588 ns/op 80 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 205 ns/op 80 B/op 4 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 221 ns/op 80 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 861 ns/op 320 B/op 9 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 868 ns/op 320 B/op 9 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 315 ns/op 320 B/op 9 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 337 ns/op 320 B/op 9 allocs/op
BenchmarkStructSimpleSuccess-8 5000000 279 ns/op 0 B/op 0 allocs/op BenchmarkStructSimpleSuccess-8 5000000 260 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleSuccessParallel-8 20000000 86.4 ns/op 0 B/op 0 allocs/op BenchmarkStructSimpleSuccessParallel-8 20000000 90.6 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleFailure-8 2000000 636 ns/op 424 B/op 9 allocs/op BenchmarkStructSimpleFailure-8 2000000 619 ns/op 424 B/op 9 allocs/op
BenchmarkStructSimpleFailureParallel-8 10000000 264 ns/op 424 B/op 9 allocs/op BenchmarkStructSimpleFailureParallel-8 5000000 296 ns/op 424 B/op 9 allocs/op
BenchmarkStructComplexSuccess-8 1000000 1539 ns/op 128 B/op 8 allocs/op BenchmarkStructComplexSuccess-8 1000000 1454 ns/op 128 B/op 8 allocs/op
BenchmarkStructComplexSuccessParallel-8 3000000 557 ns/op 128 B/op 8 allocs/op BenchmarkStructComplexSuccessParallel-8 3000000 579 ns/op 128 B/op 8 allocs/op
BenchmarkStructComplexFailure-8 300000 4136 ns/op 3041 B/op 53 allocs/op BenchmarkStructComplexFailure-8 300000 4140 ns/op 3041 B/op 53 allocs/op
BenchmarkStructComplexFailureParallel-8 1000000 1855 ns/op 3041 B/op 53 allocs/op BenchmarkStructComplexFailureParallel-8 1000000 2127 ns/op 3041 B/op 53 allocs/op
BenchmarkOneof-8 10000000 140 ns/op 0 B/op 0 allocs/op
BenchmarkOneofParallel-8 20000000 70.1 ns/op 0 B/op 0 allocs/op
``` ```
Complementary Software Complementary Software

@ -29,6 +29,11 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
return nil return nil
} }
func (v *defaultValidator) Engine() interface{} {
v.lazyinit()
return v.validate
}
func (v *defaultValidator) lazyinit() { func (v *defaultValidator) lazyinit() {
v.once.Do(func() { v.once.Do(func() {
v.validate = validator.New() v.validate = validator.New()

@ -52,7 +52,7 @@ func validateStruct() {
Addresses: []*Address{address}, Addresses: []*Address{address},
} }
// returns nil or ValidationErrors ( map[string]*FieldError ) // returns nil or ValidationErrors ( []FieldError )
err := validate.Struct(user) err := validate.Struct(user)
if err != nil { if err != nil {

@ -1,10 +1,13 @@
package validator package validator
import ( import (
"bytes"
"context" "context"
"crypto/sha256"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
"os"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -95,7 +98,9 @@ var (
"email": isEmail, "email": isEmail,
"url": isURL, "url": isURL,
"uri": isURI, "uri": isURI,
"file": isFile,
"base64": isBase64, "base64": isBase64,
"base64url": isBase64URL,
"contains": contains, "contains": contains,
"containsany": containsAny, "containsany": containsAny,
"containsrune": containsRune, "containsrune": containsRune,
@ -105,6 +110,9 @@ var (
"isbn": isISBN, "isbn": isISBN,
"isbn10": isISBN10, "isbn10": isISBN10,
"isbn13": isISBN13, "isbn13": isISBN13,
"eth_addr": isEthereumAddress,
"btc_addr": isBitcoinAddress,
"btc_addr_bech32": isBitcoinBech32Address,
"uuid": isUUID, "uuid": isUUID,
"uuid3": isUUID3, "uuid3": isUUID3,
"uuid4": isUUID4, "uuid4": isUUID4,
@ -138,6 +146,9 @@ var (
"fqdn": isFQDN, "fqdn": isFQDN,
"unique": isUnique, "unique": isUnique,
"oneof": isOneOf, "oneof": isOneOf,
"html": isHTML,
"html_encoded": isHTMLEncoded,
"url_encoded": isURLEncoded,
} }
) )
@ -157,6 +168,18 @@ func parseOneOfParam2(s string) []string {
return vals return vals
} }
func isURLEncoded(fl FieldLevel) bool {
return uRLEncodedRegex.MatchString(fl.Field().String())
}
func isHTMLEncoded(fl FieldLevel) bool {
return hTMLEncodedRegex.MatchString(fl.Field().String())
}
func isHTML(fl FieldLevel) bool {
return hTMLRegex.MatchString(fl.Field().String())
}
func isOneOf(fl FieldLevel) bool { func isOneOf(fl FieldLevel) bool {
vals := parseOneOfParam2(fl.Param()) vals := parseOneOfParam2(fl.Param())
@ -181,7 +204,7 @@ func isOneOf(fl FieldLevel) bool {
return false return false
} }
// isUnique is the validation function for validating if each array|slice element is unique // isUnique is the validation function for validating if each array|slice|map value is unique
func isUnique(fl FieldLevel) bool { func isUnique(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@ -189,12 +212,19 @@ func isUnique(fl FieldLevel) bool {
switch field.Kind() { switch field.Kind() {
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
m := reflect.MakeMap(reflect.MapOf(fl.Field().Type().Elem(), v.Type())) m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
for i := 0; i < field.Len(); i++ { for i := 0; i < field.Len(); i++ {
m.SetMapIndex(field.Index(i), v) m.SetMapIndex(field.Index(i), v)
} }
return field.Len() == m.Len() return field.Len() == m.Len()
case reflect.Map:
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
for _, k := range field.MapKeys() {
m.SetMapIndex(field.MapIndex(k), v)
}
return field.Len() == m.Len()
default: default:
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
@ -387,6 +417,140 @@ func isISBN10(fl FieldLevel) bool {
return checksum%11 == 0 return checksum%11 == 0
} }
// IsEthereumAddress is the validation function for validating if the field's value is a valid ethereum address based currently only on the format
func isEthereumAddress(fl FieldLevel) bool {
address := fl.Field().String()
if !ethAddressRegex.MatchString(address) {
return false
}
if ethaddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) {
return true
}
// checksum validation is blocked by https://github.com/golang/crypto/pull/28
return true
}
// IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address
func isBitcoinAddress(fl FieldLevel) bool {
address := fl.Field().String()
if !btcAddressRegex.MatchString(address) {
return false
}
alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
decode := [25]byte{}
for _, n := range []byte(address) {
d := bytes.IndexByte(alphabet, n)
for i := 24; i >= 0; i-- {
d += 58 * int(decode[i])
decode[i] = byte(d % 256)
d /= 256
}
}
h := sha256.New()
_, _ = h.Write(decode[:21])
d := h.Sum([]byte{})
h = sha256.New()
_, _ = h.Write(d)
validchecksum := [4]byte{}
computedchecksum := [4]byte{}
copy(computedchecksum[:], h.Sum(d[:0]))
copy(validchecksum[:], decode[21:])
return validchecksum == computedchecksum
}
// IsBitcoinBech32Address is the validation function for validating if the field's value is a valid bech32 btc address
func isBitcoinBech32Address(fl FieldLevel) bool {
address := fl.Field().String()
if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address) {
return false
}
am := len(address) % 8
if am == 0 || am == 3 || am == 5 {
return false
}
address = strings.ToLower(address)
alphabet := "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
hr := []int{3, 3, 0, 2, 3} // the human readable part will always be bc
addr := address[3:]
dp := make([]int, 0, len(addr))
for _, c := range addr {
dp = append(dp, strings.IndexRune(alphabet, c))
}
ver := dp[0]
if ver < 0 || ver > 16 {
return false
}
if ver == 0 {
if len(address) != 42 && len(address) != 62 {
return false
}
}
values := append(hr, dp...)
GEN := []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
p := 1
for _, v := range values {
b := p >> 25
p = (p&0x1ffffff)<<5 ^ v
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
p ^= GEN[i]
}
}
}
if p != 1 {
return false
}
b := uint(0)
acc := 0
mv := (1 << 5) - 1
var sw []int
for _, v := range dp[1 : len(dp)-6] {
acc = (acc << 5) | v
b += 5
for b >= 8 {
b -= 8
sw = append(sw, (acc>>b)&mv)
}
}
if len(sw) < 2 || len(sw) > 40 {
return false
}
return true
}
// ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param. // ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param.
func excludesRune(fl FieldLevel) bool { func excludesRune(fl FieldLevel) bool {
return !containsRune(fl) return !containsRune(fl)
@ -845,6 +1009,11 @@ func isBase64(fl FieldLevel) bool {
return base64Regex.MatchString(fl.Field().String()) return base64Regex.MatchString(fl.Field().String())
} }
// IsBase64URL is the validation function for validating if the current field's value is a valid base64 URL safe string.
func isBase64URL(fl FieldLevel) bool {
return base64URLRegex.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 {
@ -908,6 +1077,23 @@ func isURL(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.
func isFile(fl FieldLevel) bool {
field := fl.Field()
switch field.Kind() {
case reflect.String:
fileInfo, err := os.Stat(field.String())
if err != nil {
return false
}
return !fileInfo.IsDir()
}
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
// IsEmail is the validation function for validating if the current field's value is a valid email address. // IsEmail is the validation function for validating if the current field's value is a valid email address.
func isEmail(fl FieldLevel) bool { func isEmail(fl FieldLevel) bool {
return emailRegex.MatchString(fl.Field().String()) return emailRegex.MatchString(fl.Field().String())
@ -945,12 +1131,22 @@ func isHexadecimal(fl FieldLevel) bool {
// IsNumber is the validation function for validating if the current field's value is a valid number. // IsNumber is the validation function for validating if the current field's value is a valid number.
func isNumber(fl FieldLevel) bool { func isNumber(fl FieldLevel) bool {
return numberRegex.MatchString(fl.Field().String()) switch fl.Field().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64:
return true
default:
return numberRegex.MatchString(fl.Field().String())
}
} }
// IsNumeric is the validation function for validating if the current field's value is a valid numeric value. // IsNumeric is the validation function for validating if the current field's value is a valid numeric value.
func isNumeric(fl FieldLevel) bool { func isNumeric(fl FieldLevel) bool {
return numericRegex.MatchString(fl.Field().String()) switch fl.Field().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64:
return true
default:
return numericRegex.MatchString(fl.Field().String())
}
} }
// IsAlphanum is the validation function for validating if the current field's value is a valid alphanumeric value. // IsAlphanum is the validation function for validating if the current field's value is a valid alphanumeric value.

@ -223,7 +223,7 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
current.typeof = typeKeys current.typeof = typeKeys
if i == 0 || prevTag != typeDive { if i == 0 || prevTag != typeDive {
panic(fmt.Sprintf("'%s' tag must be immediately preceeded by the '%s' tag", keysTag, diveTag)) panic(fmt.Sprintf("'%s' tag must be immediately preceded by the '%s' tag", keysTag, diveTag))
} }
current.typeof = typeKeys current.typeof = typeKeys

104
doc.go

@ -168,7 +168,7 @@ StructOnly
When a field that is a nested struct is encountered, and contains this flag When a field that is a nested struct is encountered, and contains this flag
any validation on the nested struct will be run, but none of the nested any validation on the nested struct will be run, but none of the nested
struct fields will be validated. This is usefull if inside of you program struct fields will be validated. This is useful if inside of you program
you know the struct will be valid, but need to verify it has been assigned. you know the struct will be valid, but need to verify it has been assigned.
NOTE: only "required" and "omitempty" can be used on a struct itself. NOTE: only "required" and "omitempty" can be used on a struct itself.
@ -506,6 +506,7 @@ to the top level struct.
Unique Unique
For arrays & slices, unique will ensure that there are no duplicates. For arrays & slices, unique will ensure that there are no duplicates.
For maps, unique will ensure that there are no duplicate values.
Usage: unique Usage: unique
@ -537,6 +538,7 @@ Numeric
This validates that a string value contains a basic numeric value. This validates that a string value contains a basic numeric value.
basic excludes exponents etc... basic excludes exponents etc...
for integers or float it returns true.
Usage: numeric Usage: numeric
@ -585,6 +587,14 @@ does any email provider accept all posibilities.
Usage: email Usage: email
File path
This validates that a string value contains a valid file path and that
the file exists on the machine.
This is done using os.Stat, which is a platform independent function.
Usage: file
URL String URL String
This validates that a string value contains a valid url This validates that a string value contains a valid url
@ -609,6 +619,40 @@ this with the omitempty tag.
Usage: base64 Usage: base64
Base64URL String
This validates that a string value contains a valid base64 URL safe value
according the the RFC4648 spec.
Although an empty string is a valid base64 URL safe value, this will report
an empty string as an error, if you wish to accept an empty string as valid
you can use this with the omitempty tag.
Usage: base64url
Bitcoin Address
This validates that a string value contains a valid bitcoin address.
The format of the string is checked to ensure it matches one of the three formats
P2PKH, P2SH and performs checksum validation.
Usage: btc_addr
Bitcoin Bech32 Address (segwit)
This validates that a string value contains a valid bitcoin Bech32 address as defined
by bip-0173 (https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)
Special thanks to Pieter Wuille for providng reference implementations.
Usage: btc_addr_bech32
Ethereum Address
This validates that a string value contains a valid ethereum address.
The format of the string is checked to ensure it matches the standard Ethereum address format
Full validation is blocked by https://github.com/golang/crypto/pull/28
Usage: eth_addr
Contains Contains
This validates that a string value contains the substring value. This validates that a string value contains the substring value.
@ -665,7 +709,6 @@ This validates that a string value contains a valid isbn13 value.
Usage: isbn13 Usage: isbn13
Universally Unique Identifier UUID Universally Unique Identifier UUID
This validates that a string value contains a valid UUID. This validates that a string value contains a valid UUID.
@ -738,103 +781,103 @@ This validates that a string value contains a valid U.S. Social Security Number.
Internet Protocol Address IP Internet Protocol Address IP
This validates that a string value contains a valid IP Adress. This validates that a string value contains a valid IP Address.
Usage: ip Usage: ip
Internet Protocol Address IPv4 Internet Protocol Address IPv4
This validates that a string value contains a valid v4 IP Adress. This validates that a string value contains a valid v4 IP Address.
Usage: ipv4 Usage: ipv4
Internet Protocol Address IPv6 Internet Protocol Address IPv6
This validates that a string value contains a valid v6 IP Adress. This validates that a string value contains a valid v6 IP Address.
Usage: ipv6 Usage: ipv6
Classless Inter-Domain Routing CIDR Classless Inter-Domain Routing CIDR
This validates that a string value contains a valid CIDR Adress. This validates that a string value contains a valid CIDR Address.
Usage: cidr Usage: cidr
Classless Inter-Domain Routing CIDRv4 Classless Inter-Domain Routing CIDRv4
This validates that a string value contains a valid v4 CIDR Adress. This validates that a string value contains a valid v4 CIDR Address.
Usage: cidrv4 Usage: cidrv4
Classless Inter-Domain Routing CIDRv6 Classless Inter-Domain Routing CIDRv6
This validates that a string value contains a valid v6 CIDR Adress. This validates that a string value contains a valid v6 CIDR Address.
Usage: cidrv6 Usage: cidrv6
Transmission Control Protocol Address TCP Transmission Control Protocol Address TCP
This validates that a string value contains a valid resolvable TCP Adress. This validates that a string value contains a valid resolvable TCP Address.
Usage: tcp_addr Usage: tcp_addr
Transmission Control Protocol Address TCPv4 Transmission Control Protocol Address TCPv4
This validates that a string value contains a valid resolvable v4 TCP Adress. This validates that a string value contains a valid resolvable v4 TCP Address.
Usage: tcp4_addr Usage: tcp4_addr
Transmission Control Protocol Address TCPv6 Transmission Control Protocol Address TCPv6
This validates that a string value contains a valid resolvable v6 TCP Adress. This validates that a string value contains a valid resolvable v6 TCP Address.
Usage: tcp6_addr Usage: tcp6_addr
User Datagram Protocol Address UDP User Datagram Protocol Address UDP
This validates that a string value contains a valid resolvable UDP Adress. This validates that a string value contains a valid resolvable UDP Address.
Usage: udp_addr Usage: udp_addr
User Datagram Protocol Address UDPv4 User Datagram Protocol Address UDPv4
This validates that a string value contains a valid resolvable v4 UDP Adress. This validates that a string value contains a valid resolvable v4 UDP Address.
Usage: udp4_addr Usage: udp4_addr
User Datagram Protocol Address UDPv6 User Datagram Protocol Address UDPv6
This validates that a string value contains a valid resolvable v6 UDP Adress. This validates that a string value contains a valid resolvable v6 UDP Address.
Usage: udp6_addr Usage: udp6_addr
Internet Protocol Address IP Internet Protocol Address IP
This validates that a string value contains a valid resolvable IP Adress. This validates that a string value contains a valid resolvable IP Address.
Usage: ip_addr Usage: ip_addr
Internet Protocol Address IPv4 Internet Protocol Address IPv4
This validates that a string value contains a valid resolvable v4 IP Adress. This validates that a string value contains a valid resolvable v4 IP Address.
Usage: ip4_addr Usage: ip4_addr
Internet Protocol Address IPv6 Internet Protocol Address IPv6
This validates that a string value contains a valid resolvable v6 IP Adress. This validates that a string value contains a valid resolvable v6 IP Address.
Usage: ip6_addr Usage: ip6_addr
Unix domain socket end point Address Unix domain socket end point Address
This validates that a string value contains a valid Unix Adress. This validates that a string value contains a valid Unix Address.
Usage: unix_addr Usage: unix_addr
Media Access Control Address MAC Media Access Control Address MAC
This validates that a string value contains a valid MAC Adress. This validates that a string value contains a valid MAC Address.
Usage: mac Usage: mac
@ -860,6 +903,27 @@ This validates that a string value contains a valid FQDN.
Usage: fqdn Usage: fqdn
HTML Tags
This validates that a string value appears to be an HTML element tag
including those described at https://developer.mozilla.org/en-US/docs/Web/HTML/Element
Usage: html
HTML Encoded
This validates that a string value is a proper character reference in decimal
or hexadecimal format
Usage: html_encoded
URL Encoded
This validates that a string value is percent-encoded (URL encoded) according
to https://tools.ietf.org/html/rfc3986#section-2.1
Usage: url_encoded
Alias Validators and Tags Alias Validators and Tags
NOTE: When returning an error, the tag returned in "FieldError" will be NOTE: When returning an error, the tag returned in "FieldError" will be
@ -879,7 +943,7 @@ Validator notes:
of a regex which conflict with the validation definitions. Although of a regex which conflict with the validation definitions. Although
workarounds can be made, they take away from using pure regex's. workarounds can be made, they take away from using pure regex's.
Furthermore it's quick and dirty but the regex's become harder to Furthermore it's quick and dirty but the regex's become harder to
maintain and are not reusable, so it's as much a programming philosiphy maintain and are not reusable, so it's as much a programming philosophy
as anything. as anything.
In place of this new validator functions should be created; a regex can In place of this new validator functions should be created; a regex can

@ -17,7 +17,7 @@ type FieldLevel interface {
Field() reflect.Value Field() reflect.Value
// returns the field's name with the tag // returns the field's name with the tag
// name takeing precedence over the fields actual name. // name taking precedence over the fields actual name.
FieldName() string FieldName() string
// returns the struct field's name // returns the struct field's name

@ -3,65 +3,85 @@ package validator
import "regexp" import "regexp"
const ( const (
alphaRegexString = "^[a-zA-Z]+$" alphaRegexString = "^[a-zA-Z]+$"
alphaNumericRegexString = "^[a-zA-Z0-9]+$" alphaNumericRegexString = "^[a-zA-Z0-9]+$"
alphaUnicodeRegexString = "^[\\p{L}]+$" alphaUnicodeRegexString = "^[\\p{L}]+$"
alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$" alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$"
numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$" numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
numberRegexString = "^[0-9]+$" numberRegexString = "^[0-9]+$"
hexadecimalRegexString = "^[0-9a-fA-F]+$" hexadecimalRegexString = "^[0-9a-fA-F]+$"
hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$" rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$"
rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
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})$"
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$"
iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$" iSBN10RegexString = "^(?:[0-9]{9}X|[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}$" iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[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}$"
uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
aSCIIRegexString = "^[\x00-\x7F]*$" uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
printableASCIIRegexString = "^[\x20-\x7E]*$" aSCIIRegexString = "^[\x00-\x7F]*$"
multibyteRegexString = "[^\x00-\x7F]" printableASCIIRegexString = "^[\x20-\x7E]*$"
dataURIRegexString = "^data:.+\\/(.+);base64$" multibyteRegexString = "[^\x00-\x7F]"
latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" dataURIRegexString = "^data:.+\\/(.+);base64$"
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952
hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123
btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address
btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
ethAddressRegexString = `^0x[0-9a-fA-F]{40}$`
ethAddressUpperRegexString = `^0x[0-9A-F]{40}$`
ethAddressLowerRegexString = `^0x[0-9a-f]{40}$`
uRLEncodedRegexString = `(%[A-Fa-f0-9]{2})`
hTMLEncodedRegexString = `&#[x]?([0-9a-fA-F]{2})|(&gt)|(&lt)|(&quot)|(&amp)+[;]?`
hTMLRegexString = `<[/]?([a-zA-Z]+).*?>`
) )
var ( var (
alphaRegex = regexp.MustCompile(alphaRegexString) alphaRegex = regexp.MustCompile(alphaRegexString)
alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString) alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString)
alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString) alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString)
alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString) alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString)
numericRegex = regexp.MustCompile(numericRegexString) numericRegex = regexp.MustCompile(numericRegexString)
numberRegex = regexp.MustCompile(numberRegexString) numberRegex = regexp.MustCompile(numberRegexString)
hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString) hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString)
hexcolorRegex = regexp.MustCompile(hexcolorRegexString) hexcolorRegex = regexp.MustCompile(hexcolorRegexString)
rgbRegex = regexp.MustCompile(rgbRegexString) rgbRegex = regexp.MustCompile(rgbRegexString)
rgbaRegex = regexp.MustCompile(rgbaRegexString) rgbaRegex = regexp.MustCompile(rgbaRegexString)
hslRegex = regexp.MustCompile(hslRegexString) hslRegex = regexp.MustCompile(hslRegexString)
hslaRegex = regexp.MustCompile(hslaRegexString) hslaRegex = regexp.MustCompile(hslaRegexString)
emailRegex = regexp.MustCompile(emailRegexString) emailRegex = regexp.MustCompile(emailRegexString)
base64Regex = regexp.MustCompile(base64RegexString) base64Regex = regexp.MustCompile(base64RegexString)
iSBN10Regex = regexp.MustCompile(iSBN10RegexString) base64URLRegex = regexp.MustCompile(base64URLRegexString)
iSBN13Regex = regexp.MustCompile(iSBN13RegexString) iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
uUID3Regex = regexp.MustCompile(uUID3RegexString) iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
uUID4Regex = regexp.MustCompile(uUID4RegexString) uUID3Regex = regexp.MustCompile(uUID3RegexString)
uUID5Regex = regexp.MustCompile(uUID5RegexString) uUID4Regex = regexp.MustCompile(uUID4RegexString)
uUIDRegex = regexp.MustCompile(uUIDRegexString) uUID5Regex = regexp.MustCompile(uUID5RegexString)
aSCIIRegex = regexp.MustCompile(aSCIIRegexString) uUIDRegex = regexp.MustCompile(uUIDRegexString)
printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString) aSCIIRegex = regexp.MustCompile(aSCIIRegexString)
multibyteRegex = regexp.MustCompile(multibyteRegexString) printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString)
dataURIRegex = regexp.MustCompile(dataURIRegexString) multibyteRegex = regexp.MustCompile(multibyteRegexString)
latitudeRegex = regexp.MustCompile(latitudeRegexString) dataURIRegex = regexp.MustCompile(dataURIRegexString)
longitudeRegex = regexp.MustCompile(longitudeRegexString) latitudeRegex = regexp.MustCompile(latitudeRegexString)
sSNRegex = regexp.MustCompile(sSNRegexString) longitudeRegex = regexp.MustCompile(longitudeRegexString)
hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952) sSNRegex = regexp.MustCompile(sSNRegexString)
hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123) hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952)
hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123)
btcAddressRegex = regexp.MustCompile(btcAddressRegexString)
btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32)
btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32)
ethAddressRegex = regexp.MustCompile(ethAddressRegexString)
ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString)
ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString)
uRLEncodedRegex = regexp.MustCompile(uRLEncodedRegexString)
hTMLEncodedRegex = regexp.MustCompile(hTMLEncodedRegexString)
hTMLRegex = regexp.MustCompile(hTMLRegexString)
) )

1
testdata/a.go vendored

@ -0,0 +1 @@
package testdata

@ -7,5 +7,5 @@ import ut "github.com/go-playground/universal-translator"
type TranslationFunc func(ut ut.Translator, fe FieldError) string type TranslationFunc func(ut ut.Translator, fe FieldError) string
// RegisterTranslationsFunc allows for registering of translations // RegisterTranslationsFunc allows for registering of translations
// for a 'ut.Translator' for use withing the 'TranslationFunc' // for a 'ut.Translator' for use within the 'TranslationFunc'
type RegisterTranslationsFunc func(ut ut.Translator) error type RegisterTranslationsFunc func(ut ut.Translator) error

@ -429,7 +429,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag())
goto END
} }
t, err = ut.T("lt-datetime", fe.Field()) t, err = ut.T("lt-datetime", fe.Field())
@ -548,7 +549,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag())
goto END
} }
t, err = ut.T("lte-datetime", fe.Field()) t, err = ut.T("lte-datetime", fe.Field())
@ -667,7 +669,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag())
goto END
} }
t, err = ut.T("gt-datetime", fe.Field()) t, err = ut.T("gt-datetime", fe.Field())
@ -786,7 +789,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag())
goto END
} }
t, err = ut.T("gte-datetime", fe.Field()) t, err = ut.T("gte-datetime", fe.Field())

@ -429,7 +429,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag())
goto END
} }
t, err = ut.T("lt-datetime", fe.Field()) t, err = ut.T("lt-datetime", fe.Field())
@ -548,7 +549,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag())
goto END
} }
t, err = ut.T("lte-datetime", fe.Field()) t, err = ut.T("lte-datetime", fe.Field())
@ -667,7 +669,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag())
goto END
} }
t, err = ut.T("gt-datetime", fe.Field()) t, err = ut.T("gt-datetime", fe.Field())
@ -786,7 +789,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s' cannot be used on a struct type.", fe.Tag()) err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag())
goto END
} }
t, err = ut.T("gte-datetime", fe.Field()) t, err = ut.T("gte-datetime", fe.Field())

@ -429,7 +429,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("a tag '%s' não pode ser usada em uma struct type.", fe.Tag()) err = fmt.Errorf("a tag '%s' não pode ser usada em uma struct type", fe.Tag())
goto END
} }
t, err = ut.T("lt-datetime", fe.Field()) t, err = ut.T("lt-datetime", fe.Field())
@ -548,7 +549,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type.", fe.Tag()) err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type", fe.Tag())
goto END
} }
t, err = ut.T("lte-datetime", fe.Field()) t, err = ut.T("lte-datetime", fe.Field())
@ -667,7 +669,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type.", fe.Tag()) err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type", fe.Tag())
goto END
} }
t, err = ut.T("gt-datetime", fe.Field()) t, err = ut.T("gt-datetime", fe.Field())
@ -786,7 +789,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type.", fe.Tag()) err = fmt.Errorf("a tag '%s' não pode ser usado em uma struct type", fe.Tag())
goto END
} }
t, err = ut.T("gte-datetime", fe.Field()) t, err = ut.T("gte-datetime", fe.Field())

@ -213,8 +213,8 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
CONTINUE: CONTINUE:
// if len == 0 then validating using 'Var' or 'VarWithValue' // if len == 0 then validating using 'Var' or 'VarWithValue'
// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm... // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
// VarWithField - this allows for validating against each field withing the struct against a specific value // VarWithField - this allows for validating against each field within the struct against a specific value
// pretty handly in certain situations // pretty handy in certain situations
if len(cf.name) > 0 { if len(cf.name) > 0 {
ns = append(append(ns, cf.altName...), '.') ns = append(append(ns, cf.altName...), '.')
structNs = append(append(structNs, cf.name...), '.') structNs = append(append(structNs, cf.name...), '.')

@ -71,7 +71,7 @@ type Validate struct {
structCache *structCache structCache *structCache
} }
// New returns a new instacne of 'validate' with sane defaults. // New returns a new instance of 'validate' with sane defaults.
func New() *Validate { func New() *Validate {
tc := new(tagCache) tc := new(tagCache)
@ -97,7 +97,7 @@ func New() *Validate {
for k, val := range bakedInValidators { for k, val := range bakedInValidators {
// no need to error check here, baked in will always be valid // no need to error check here, baked in will always be valid
v.registerValidation(k, wrapFunc(val), true) _ = v.registerValidation(k, wrapFunc(val), true)
} }
v.pool = &sync.Pool{ v.pool = &sync.Pool{
@ -489,7 +489,7 @@ func (v *Validate) StructExceptCtx(ctx context.Context, s interface{}, fields ..
// //
// WARNING: a struct can be passed for validation eg. time.Time is a struct or // WARNING: a struct can be passed for validation eg. time.Time is a struct or
// if you have a custom type and have registered a custom type handler, so must // if you have a custom type and have registered a custom type handler, so must
// allow it; however unforseen validations will occur if trying to validate a // allow it; however unforeseen validations will occur if trying to validate a
// struct that is meant to be passed to 'validate.Struct' // struct that is meant to be passed to 'validate.Struct'
// //
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
@ -507,7 +507,7 @@ func (v *Validate) Var(field interface{}, tag string) error {
// //
// WARNING: a struct can be passed for validation eg. time.Time is a struct or // WARNING: a struct can be passed for validation eg. time.Time is a struct or
// if you have a custom type and have registered a custom type handler, so must // if you have a custom type and have registered a custom type handler, so must
// allow it; however unforseen validations will occur if trying to validate a // allow it; however unforeseen validations will occur if trying to validate a
// struct that is meant to be passed to 'validate.Struct' // struct that is meant to be passed to 'validate.Struct'
// //
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
@ -541,7 +541,7 @@ func (v *Validate) VarCtx(ctx context.Context, field interface{}, tag string) (e
// //
// WARNING: a struct can be passed for validation eg. time.Time is a struct or // WARNING: a struct can be passed for validation eg. time.Time is a struct or
// if you have a custom type and have registered a custom type handler, so must // if you have a custom type and have registered a custom type handler, so must
// allow it; however unforseen validations will occur if trying to validate a // allow it; however unforeseen validations will occur if trying to validate a
// struct that is meant to be passed to 'validate.Struct' // struct that is meant to be passed to 'validate.Struct'
// //
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
@ -560,7 +560,7 @@ func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string
// //
// WARNING: a struct can be passed for validation eg. time.Time is a struct or // WARNING: a struct can be passed for validation eg. time.Time is a struct or
// if you have a custom type and have registered a custom type handler, so must // if you have a custom type and have registered a custom type handler, so must
// allow it; however unforseen validations will occur if trying to validate a // allow it; however unforeseen validations will occur if trying to validate a
// struct that is meant to be passed to 'validate.Struct' // struct that is meant to be passed to 'validate.Struct'
// //
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.

@ -5,8 +5,10 @@ import (
"context" "context"
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"path/filepath"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -1015,7 +1017,7 @@ func TestCrossStructLteFieldValidation(t *testing.T) {
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "ltecsfield") AssertError(t, errs, "", "", "", "", "ltecsfield")
// this test is for the WARNING about unforseen validation issues. // this test is for the WARNING about unforeseen validation issues.
errs = validate.VarWithValue(test, now, "ltecsfield") errs = validate.VarWithValue(test, now, "ltecsfield")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs.(ValidationErrors)), 6) Equal(t, len(errs.(ValidationErrors)), 6)
@ -1112,7 +1114,7 @@ func TestCrossStructLtFieldValidation(t *testing.T) {
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "ltcsfield") AssertError(t, errs, "", "", "", "", "ltcsfield")
// this test is for the WARNING about unforseen validation issues. // this test is for the WARNING about unforeseen validation issues.
errs = validate.VarWithValue(test, now, "ltcsfield") errs = validate.VarWithValue(test, now, "ltcsfield")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "ltcsfield") AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "ltcsfield")
@ -1220,7 +1222,7 @@ func TestCrossStructGteFieldValidation(t *testing.T) {
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "gtecsfield") AssertError(t, errs, "", "", "", "", "gtecsfield")
// this test is for the WARNING about unforseen validation issues. // this test is for the WARNING about unforeseen validation issues.
errs = validate.VarWithValue(test, now, "gtecsfield") errs = validate.VarWithValue(test, now, "gtecsfield")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtecsfield") AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtecsfield")
@ -1316,7 +1318,7 @@ func TestCrossStructGtFieldValidation(t *testing.T) {
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "gtcsfield") AssertError(t, errs, "", "", "", "", "gtcsfield")
// this test is for the WARNING about unforseen validation issues. // this test is for the WARNING about unforeseen validation issues.
errs = validate.VarWithValue(test, now, "gtcsfield") errs = validate.VarWithValue(test, now, "gtcsfield")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtcsfield") AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtcsfield")
@ -4398,6 +4400,283 @@ func TestBase64Validation(t *testing.T) {
AssertError(t, errs, "", "", "", "", "base64") AssertError(t, errs, "", "", "", "", "base64")
} }
func TestBase64URLValidation(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
{"f", "Zg==", true},
{"fo", "Zm8=", true},
// base64 without padding
{"foo", "Zm9v", true},
{"", "Zg", false},
{"", "Zm8", 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, "base64url")
if tc.success {
Equal(t, err, nil)
// make sure encoded value is decoded back to the expected value
d, innerErr := base64.URLEncoding.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.URLEncoding.DecodeString(tc.encoded)
NotEqual(t, err, nil)
}
}
}
}
func TestFileValidation(t *testing.T) {
validate := New()
tests := []struct {
title string
param string
expected bool
}{
{"empty path", "", false},
{"regular file", filepath.Join("testdata", "a.go"), true},
{"missing file", filepath.Join("testdata", "no.go"), false},
{"directory, not a file", "testdata", false},
}
for _, test := range tests {
errs := validate.Var(test.param, "file")
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, "file")
}, "Bad field type int")
}
func TestEthereumAddressValidation(t *testing.T) {
validate := New()
tests := []struct {
param string
expected bool
}{
{"", false},
{"0x02F9AE5f22EA3fA88F05780B30385bEC", false},
{"123f681646d4a755815f9cb19e1acc8565a0c2ac", false},
{"0x02F9AE5f22EA3fA88F05780B30385bECFacbf130", true},
{"0x123f681646d4a755815f9cb19e1acc8565a0c2ac", true},
}
for i, test := range tests {
errs := validate.Var(test.param, "eth_addr")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d eth_addr failed Error: %s", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d eth_addr failed Error: %s", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "eth_addr" {
t.Fatalf("Index: %d Latitude failed Error: %s", i, errs)
}
}
}
}
}
func TestBitcoinAddressValidation(t *testing.T) {
validate := New()
tests := []struct {
param string
expected bool
}{
{"", false},
{"x", false},
{"0x02F9AE5f22EA3fA88F05780B30385bEC", false},
{"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false},
{"1P9RQEr2XeE3PEb44ZE35sfZRRW1JH8Uqx", false},
{"3P14159I73E4gFr7JterCCQh9QjiTjiZrG", false},
{"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false},
{"37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y", false},
{"dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv", false},
{"MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S", false},
{"rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf", false},
{"4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq", false},
{"7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb", false},
{"17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs", false},
{"KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3", false},
{"7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th", false},
{"cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va", false},
{"gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk", false},
{"emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs", false},
{"7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo", false},
{"1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso", false},
{"31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq", false},
{"DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN", false},
{"2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i", false},
{"7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos", false},
{"1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu", false},
{"2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb", false},
{"8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ", false},
{"163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ", false},
{"2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu", false},
{"461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU", false},
{"2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs", false},
{"cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn", false},
{"gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj", false},
{"nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny", false},
{"L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc", false},
{"7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ ", false},
{"2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP", false},
{"dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw", false},
{"HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX", false},
{"4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB", false},
{"31uEbMgunupShBVTewXjtqbBv5MndwfXhb", false},
{"175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W", false},
{"Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ", false},
{"Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs", false},
{"6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ", false},
{"giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4", false},
{"cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK", false},
{"37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig", false},
{"EsYbG4tWWWY45G31nox838qNdzksbPySWc", false},
{"nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT", false},
{"cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx", false},
{"1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde", false},
{"2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU", false},
{"ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf", false},
{"Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd", false},
{"2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED", false},
{"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", true},
{"1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", true},
{"1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", true},
{"1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", true},
{"1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", true},
{"19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", true},
{"13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", true},
{"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true},
{"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true},
{"3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", true},
{"3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", true},
{"3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", true},
{"33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", true},
{"3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", true},
{"37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", true},
{"3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", true},
{"12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP", true},
{"12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y", true},
{"1oNLrsHnBcR6dpaBpwz3LSwutbUNkNSjs", true},
{"1SQHtwR5oJRKLfiWQ2APsAd9miUc4k2ez", true},
{"116CGDLddrZhMrTwhCVJXtXQpxygTT1kHd", true},
{"3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt", true},
}
for i, test := range tests {
errs := validate.Var(test.param, "btc_addr")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d btc_addr failed with Error: %s", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d btc_addr failed with Error: %s", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "btc_addr" {
t.Fatalf("Index: %d Latitude failed with Error: %s", i, errs)
}
}
}
}
}
func TestBitcoinBech32AddressValidation(t *testing.T) {
validate := New()
tests := []struct {
param string
expected bool
}{
{"", false},
{"bc1rw5uspcuh", false},
{"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false},
{"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", false},
{"qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", false},
{"bc1rw5uspcuh", false},
{"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false},
{"BC1QW508d6QEJxTDG4y5R3ZArVARY0C5XW7KV8F3T4", false},
{"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", false},
{"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false},
{"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false},
{"bc1pw508d6qejxtdg4y5r3zarqfsj6c3", false},
{"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", false},
{"bc1gmk9yu", false},
{"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", false},
{"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", true},
{"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", true},
{"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", true},
{"BC1SW50QA3JX3S", true},
{"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", true},
}
for i, test := range tests {
errs := validate.Var(test.param, "btc_addr_bech32")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d btc_addr_bech32 failed with Error: %s", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d btc_addr_bech32 failed with Error: %s", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "btc_addr_bech32" {
t.Fatalf("Index: %d Latitude failed with Error: %s", i, errs)
}
}
}
}
}
func TestNoStructLevelValidation(t *testing.T) { func TestNoStructLevelValidation(t *testing.T) {
type Inner struct { type Inner struct {
@ -5873,6 +6152,11 @@ func TestEmail(t *testing.T) {
errs = validate.Var(s, "email") errs = validate.Var(s, "email")
Equal(t, errs, nil) Equal(t, errs, nil)
s = "mail@domain_with_underscores.org"
errs = validate.Var(s, "email")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "email")
s = "" s = ""
errs = validate.Var(s, "email") errs = validate.Var(s, "email")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
@ -5991,8 +6275,7 @@ func TestNumber(t *testing.T) {
i := 1 i := 1
errs = validate.Var(i, "number") errs = validate.Var(i, "number")
NotEqual(t, errs, nil) Equal(t, errs, nil)
AssertError(t, errs, "", "", "", "", "number")
} }
func TestNumeric(t *testing.T) { func TestNumeric(t *testing.T) {
@ -6035,8 +6318,7 @@ func TestNumeric(t *testing.T) {
i := 1 i := 1
errs = validate.Var(i, "numeric") errs = validate.Var(i, "numeric")
NotEqual(t, errs, nil) Equal(t, errs, nil)
AssertError(t, errs, "", "", "", "", "numeric")
} }
func TestAlphaNumeric(t *testing.T) { func TestAlphaNumeric(t *testing.T) {
@ -6593,8 +6875,8 @@ func TestTranslations(t *testing.T) {
}, func(ut ut.Translator, fe FieldError) string { }, func(ut ut.Translator, fe FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field()) t, transErr := ut.T(fe.Tag(), fe.Field())
if err != nil { if transErr != nil {
fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError)) fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError))
return fe.(*fieldError).Error() return fe.(*fieldError).Error()
} }
@ -7451,6 +7733,18 @@ func TestUniqueValidation(t *testing.T) {
param interface{} param interface{}
expected bool expected bool
}{ }{
// Arrays
{[2]string{"a", "b"}, true},
{[2]int{1, 2}, true},
{[2]float64{1, 2}, true},
{[2]interface{}{"a", "b"}, true},
{[2]interface{}{"a", 1}, true},
{[2]float64{1, 1}, false},
{[2]int{1, 1}, false},
{[2]string{"a", "a"}, false},
{[2]interface{}{"a", "a"}, false},
{[4]interface{}{"a", 1, "b", 1}, false},
// Slices
{[]string{"a", "b"}, true}, {[]string{"a", "b"}, true},
{[]int{1, 2}, true}, {[]int{1, 2}, true},
{[]float64{1, 2}, true}, {[]float64{1, 2}, true},
@ -7461,6 +7755,17 @@ func TestUniqueValidation(t *testing.T) {
{[]string{"a", "a"}, false}, {[]string{"a", "a"}, false},
{[]interface{}{"a", "a"}, false}, {[]interface{}{"a", "a"}, false},
{[]interface{}{"a", 1, "b", 1}, false}, {[]interface{}{"a", 1, "b", 1}, false},
// Maps
{map[string]string{"one": "a", "two": "b"}, true},
{map[string]int{"one": 1, "two": 2}, true},
{map[string]float64{"one": 1, "two": 2}, true},
{map[string]interface{}{"one": "a", "two": "b"}, true},
{map[string]interface{}{"one": "a", "two": 1}, true},
{map[string]float64{"one": 1, "two": 1}, false},
{map[string]int{"one": 1, "two": 1}, false},
{map[string]string{"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},
} }
validate := New() validate := New()
@ -7487,6 +7792,129 @@ 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")
} }
func TestHTMLValidation(t *testing.T) {
tests := []struct {
param string
expected bool
}{
{"<html>", true},
{"<script>", true},
{"<stillworks>", true},
{"</html", false},
{"</script>", true},
{"<//script>", false},
{"<123nonsense>", false},
{"test", false},
{"&example", false},
}
validate := New()
for i, test := range tests {
errs := validate.Var(test.param, "html")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d html failed Error: %v", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d html failed Error: %v", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "html" {
t.Fatalf("Index: %d html failed Error: %v", i, errs)
}
}
}
}
}
func TestHTMLEncodedValidation(t *testing.T) {
tests := []struct {
param string
expected bool
}{
{"&#x3c;", true},
{"&#xaf;", true},
{"&#x00;", true},
{"&#xf0;", true},
{"&#x3c", true},
{"&#xaf", true},
{"&#x00", true},
{"&#xf0", true},
{"&#ab", true},
{"&lt;", true},
{"&gt;", true},
{"&quot;", true},
{"&amp;", true},
{"#x0a", false},
{"&x00", false},
{"&#x1z", false},
}
validate := New()
for i, test := range tests {
errs := validate.Var(test.param, "html_encoded")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d html_encoded failed Error: %v", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d html_enocded failed Error: %v", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "html_encoded" {
t.Fatalf("Index: %d html_encoded failed Error: %v", i, errs)
}
}
}
}
}
func TestURLEncodedValidation(t *testing.T) {
tests := []struct {
param string
expected bool
}{
{"%20", true},
{"%af", true},
{"%ff", true},
{"<%az", false},
{"%test%", false},
{"a%b", false},
{"1%2", false},
{"%%a%%", false},
}
validate := New()
for i, test := range tests {
errs := validate.Var(test.param, "url_encoded")
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d url_encoded failed Error: %v", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d url_enocded failed Error: %v", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "url_encoded" {
t.Fatalf("Index: %d url_encoded failed Error: %v", i, errs)
}
}
}
}
}
func TestKeys(t *testing.T) { func TestKeys(t *testing.T) {
type Test struct { type Test struct {
@ -7567,7 +7995,7 @@ func TestKeys(t *testing.T) {
// test bad tag definitions // test bad tag definitions
PanicMatches(t, func() { validate.Var(map[string]string{"key": "val"}, "endkeys,dive,eq=val") }, "'endkeys' tag encountered without a corresponding 'keys' tag") PanicMatches(t, func() { validate.Var(map[string]string{"key": "val"}, "endkeys,dive,eq=val") }, "'endkeys' tag encountered without a corresponding 'keys' tag")
PanicMatches(t, func() { validate.Var(1, "keys,eq=1,endkeys") }, "'keys' tag must be immediately preceeded by the 'dive' tag") PanicMatches(t, func() { validate.Var(1, "keys,eq=1,endkeys") }, "'keys' tag must be immediately preceded by the 'dive' tag")
// test custom tag name // test custom tag name
validate = New() validate = New()

Loading…
Cancel
Save