diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..aeeee9d
--- /dev/null
+++ b/Makefile
@@ -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
\ No newline at end of file
diff --git a/README.md b/README.md
index 31bdcb0..b89466c 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
Package validator
================
[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-![Project status](https://img.shields.io/badge/version-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)
[![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)
@@ -66,71 +66,73 @@ Please see http://godoc.org/gopkg.in/go-playground/validator.v9 for detailed usa
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
goos: darwin
goarch: amd64
pkg: github.com/go-playground/validator
-BenchmarkFieldSuccess-8 20000000 86.4 ns/op 0 B/op 0 allocs/op
-BenchmarkFieldSuccessParallel-8 50000000 27.6 ns/op 0 B/op 0 allocs/op
-BenchmarkFieldFailure-8 5000000 297 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
-BenchmarkFieldArrayDiveSuccessParallel-8 10000000 225 ns/op 201 B/op 11 allocs/op
-BenchmarkFieldArrayDiveFailure-8 2000000 863 ns/op 412 B/op 16 allocs/op
-BenchmarkFieldArrayDiveFailureParallel-8 5000000 322 ns/op 413 B/op 16 allocs/op
-BenchmarkFieldMapDiveSuccess-8 1000000 1336 ns/op 432 B/op 18 allocs/op
-BenchmarkFieldMapDiveSuccessParallel-8 3000000 474 ns/op 432 B/op 18 allocs/op
-BenchmarkFieldMapDiveFailure-8 1000000 1103 ns/op 512 B/op 16 allocs/op
-BenchmarkFieldMapDiveFailureParallel-8 5000000 412 ns/op 512 B/op 16 allocs/op
-BenchmarkFieldMapDiveWithKeysSuccess-8 1000000 1572 ns/op 480 B/op 21 allocs/op
-BenchmarkFieldMapDiveWithKeysSuccessParallel-8 3000000 615 ns/op 480 B/op 21 allocs/op
-BenchmarkFieldMapDiveWithKeysFailure-8 1000000 1438 ns/op 721 B/op 21 allocs/op
-BenchmarkFieldMapDiveWithKeysFailureParallel-8 3000000 543 ns/op 721 B/op 21 allocs/op
-BenchmarkFieldCustomTypeSuccess-8 10000000 230 ns/op 32 B/op 2 allocs/op
-BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.5 ns/op 32 B/op 2 allocs/op
-BenchmarkFieldCustomTypeFailure-8 5000000 284 ns/op 208 B/op 4 allocs/op
-BenchmarkFieldCustomTypeFailureParallel-8 20000000 118 ns/op 208 B/op 4 allocs/op
-BenchmarkFieldOrTagSuccess-8 2000000 824 ns/op 16 B/op 1 allocs/op
-BenchmarkFieldOrTagSuccessParallel-8 3000000 472 ns/op 16 B/op 1 allocs/op
-BenchmarkFieldOrTagFailure-8 3000000 487 ns/op 224 B/op 5 allocs/op
-BenchmarkFieldOrTagFailureParallel-8 5000000 405 ns/op 224 B/op 5 allocs/op
-BenchmarkStructLevelValidationSuccess-8 10000000 214 ns/op 32 B/op 2 allocs/op
-BenchmarkStructLevelValidationSuccessParallel-8 20000000 78.0 ns/op 32 B/op 2 allocs/op
-BenchmarkStructLevelValidationFailure-8 3000000 475 ns/op 304 B/op 8 allocs/op
-BenchmarkStructLevelValidationFailureParallel-8 10000000 200 ns/op 304 B/op 8 allocs/op
-BenchmarkStructSimpleCustomTypeSuccess-8 3000000 403 ns/op 32 B/op 2 allocs/op
-BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 143 ns/op 32 B/op 2 allocs/op
-BenchmarkStructSimpleCustomTypeFailure-8 2000000 655 ns/op 424 B/op 9 allocs/op
-BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 286 ns/op 440 B/op 10 allocs/op
-BenchmarkStructFilteredSuccess-8 2000000 598 ns/op 288 B/op 9 allocs/op
-BenchmarkStructFilteredSuccessParallel-8 10000000 231 ns/op 288 B/op 9 allocs/op
-BenchmarkStructFilteredFailure-8 3000000 455 ns/op 256 B/op 7 allocs/op
-BenchmarkStructFilteredFailureParallel-8 10000000 197 ns/op 256 B/op 7 allocs/op
-BenchmarkStructPartialSuccess-8 3000000 552 ns/op 256 B/op 6 allocs/op
-BenchmarkStructPartialSuccessParallel-8 10000000 206 ns/op 256 B/op 6 allocs/op
-BenchmarkStructPartialFailure-8 2000000 750 ns/op 480 B/op 11 allocs/op
-BenchmarkStructPartialFailureParallel-8 5000000 317 ns/op 480 B/op 11 allocs/op
-BenchmarkStructExceptSuccess-8 2000000 853 ns/op 496 B/op 12 allocs/op
-BenchmarkStructExceptSuccessParallel-8 10000000 179 ns/op 240 B/op 5 allocs/op
-BenchmarkStructExceptFailure-8 2000000 698 ns/op 464 B/op 10 allocs/op
-BenchmarkStructExceptFailureParallel-8 5000000 276 ns/op 464 B/op 10 allocs/op
-BenchmarkStructSimpleCrossFieldSuccess-8 3000000 412 ns/op 72 B/op 3 allocs/op
-BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 148 ns/op 72 B/op 3 allocs/op
-BenchmarkStructSimpleCrossFieldFailure-8 2000000 630 ns/op 304 B/op 8 allocs/op
-BenchmarkStructSimpleCrossFieldFailureParallel-8 10000000 244 ns/op 304 B/op 8 allocs/op
-BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 610 ns/op 80 B/op 4 allocs/op
-BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 205 ns/op 80 B/op 4 allocs/op
-BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 861 ns/op 320 B/op 9 allocs/op
-BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 315 ns/op 320 B/op 9 allocs/op
-BenchmarkStructSimpleSuccess-8 5000000 279 ns/op 0 B/op 0 allocs/op
-BenchmarkStructSimpleSuccessParallel-8 20000000 86.4 ns/op 0 B/op 0 allocs/op
-BenchmarkStructSimpleFailure-8 2000000 636 ns/op 424 B/op 9 allocs/op
-BenchmarkStructSimpleFailureParallel-8 10000000 264 ns/op 424 B/op 9 allocs/op
-BenchmarkStructComplexSuccess-8 1000000 1539 ns/op 128 B/op 8 allocs/op
-BenchmarkStructComplexSuccessParallel-8 3000000 557 ns/op 128 B/op 8 allocs/op
-BenchmarkStructComplexFailure-8 300000 4136 ns/op 3041 B/op 53 allocs/op
-BenchmarkStructComplexFailureParallel-8 1000000 1855 ns/op 3041 B/op 53 allocs/op
+BenchmarkFieldSuccess-8 20000000 83.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 291 ns/op 208 B/op 4 allocs/op
+BenchmarkFieldFailureParallel-8 20000000 107 ns/op 208 B/op 4 allocs/op
+BenchmarkFieldArrayDiveSuccess-8 2000000 623 ns/op 201 B/op 11 allocs/op
+BenchmarkFieldArrayDiveSuccessParallel-8 10000000 237 ns/op 201 B/op 11 allocs/op
+BenchmarkFieldArrayDiveFailure-8 2000000 859 ns/op 412 B/op 16 allocs/op
+BenchmarkFieldArrayDiveFailureParallel-8 5000000 335 ns/op 413 B/op 16 allocs/op
+BenchmarkFieldMapDiveSuccess-8 1000000 1292 ns/op 432 B/op 18 allocs/op
+BenchmarkFieldMapDiveSuccessParallel-8 3000000 467 ns/op 432 B/op 18 allocs/op
+BenchmarkFieldMapDiveFailure-8 1000000 1082 ns/op 512 B/op 16 allocs/op
+BenchmarkFieldMapDiveFailureParallel-8 5000000 425 ns/op 512 B/op 16 allocs/op
+BenchmarkFieldMapDiveWithKeysSuccess-8 1000000 1539 ns/op 480 B/op 21 allocs/op
+BenchmarkFieldMapDiveWithKeysSuccessParallel-8 3000000 613 ns/op 480 B/op 21 allocs/op
+BenchmarkFieldMapDiveWithKeysFailure-8 1000000 1413 ns/op 721 B/op 21 allocs/op
+BenchmarkFieldMapDiveWithKeysFailureParallel-8 3000000 575 ns/op 721 B/op 21 allocs/op
+BenchmarkFieldCustomTypeSuccess-8 10000000 216 ns/op 32 B/op 2 allocs/op
+BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.2 ns/op 32 B/op 2 allocs/op
+BenchmarkFieldCustomTypeFailure-8 5000000 274 ns/op 208 B/op 4 allocs/op
+BenchmarkFieldCustomTypeFailureParallel-8 20000000 116 ns/op 208 B/op 4 allocs/op
+BenchmarkFieldOrTagSuccess-8 2000000 740 ns/op 16 B/op 1 allocs/op
+BenchmarkFieldOrTagSuccessParallel-8 3000000 474 ns/op 16 B/op 1 allocs/op
+BenchmarkFieldOrTagFailure-8 3000000 471 ns/op 224 B/op 5 allocs/op
+BenchmarkFieldOrTagFailureParallel-8 3000000 414 ns/op 224 B/op 5 allocs/op
+BenchmarkStructLevelValidationSuccess-8 10000000 213 ns/op 32 B/op 2 allocs/op
+BenchmarkStructLevelValidationSuccessParallel-8 20000000 91.8 ns/op 32 B/op 2 allocs/op
+BenchmarkStructLevelValidationFailure-8 3000000 473 ns/op 304 B/op 8 allocs/op
+BenchmarkStructLevelValidationFailureParallel-8 10000000 234 ns/op 304 B/op 8 allocs/op
+BenchmarkStructSimpleCustomTypeSuccess-8 5000000 385 ns/op 32 B/op 2 allocs/op
+BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 161 ns/op 32 B/op 2 allocs/op
+BenchmarkStructSimpleCustomTypeFailure-8 2000000 640 ns/op 424 B/op 9 allocs/op
+BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 318 ns/op 440 B/op 10 allocs/op
+BenchmarkStructFilteredSuccess-8 2000000 597 ns/op 288 B/op 9 allocs/op
+BenchmarkStructFilteredSuccessParallel-8 10000000 266 ns/op 288 B/op 9 allocs/op
+BenchmarkStructFilteredFailure-8 3000000 454 ns/op 256 B/op 7 allocs/op
+BenchmarkStructFilteredFailureParallel-8 10000000 214 ns/op 256 B/op 7 allocs/op
+BenchmarkStructPartialSuccess-8 3000000 502 ns/op 256 B/op 6 allocs/op
+BenchmarkStructPartialSuccessParallel-8 10000000 225 ns/op 256 B/op 6 allocs/op
+BenchmarkStructPartialFailure-8 2000000 702 ns/op 480 B/op 11 allocs/op
+BenchmarkStructPartialFailureParallel-8 5000000 329 ns/op 480 B/op 11 allocs/op
+BenchmarkStructExceptSuccess-8 2000000 793 ns/op 496 B/op 12 allocs/op
+BenchmarkStructExceptSuccessParallel-8 10000000 193 ns/op 240 B/op 5 allocs/op
+BenchmarkStructExceptFailure-8 2000000 639 ns/op 464 B/op 10 allocs/op
+BenchmarkStructExceptFailureParallel-8 5000000 300 ns/op 464 B/op 10 allocs/op
+BenchmarkStructSimpleCrossFieldSuccess-8 3000000 417 ns/op 72 B/op 3 allocs/op
+BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 163 ns/op 72 B/op 3 allocs/op
+BenchmarkStructSimpleCrossFieldFailure-8 2000000 645 ns/op 304 B/op 8 allocs/op
+BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 285 ns/op 304 B/op 8 allocs/op
+BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 3000000 588 ns/op 80 B/op 4 allocs/op
+BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 221 ns/op 80 B/op 4 allocs/op
+BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 868 ns/op 320 B/op 9 allocs/op
+BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 337 ns/op 320 B/op 9 allocs/op
+BenchmarkStructSimpleSuccess-8 5000000 260 ns/op 0 B/op 0 allocs/op
+BenchmarkStructSimpleSuccessParallel-8 20000000 90.6 ns/op 0 B/op 0 allocs/op
+BenchmarkStructSimpleFailure-8 2000000 619 ns/op 424 B/op 9 allocs/op
+BenchmarkStructSimpleFailureParallel-8 5000000 296 ns/op 424 B/op 9 allocs/op
+BenchmarkStructComplexSuccess-8 1000000 1454 ns/op 128 B/op 8 allocs/op
+BenchmarkStructComplexSuccessParallel-8 3000000 579 ns/op 128 B/op 8 allocs/op
+BenchmarkStructComplexFailure-8 300000 4140 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
diff --git a/_examples/gin-upgrading-overriding/v8_to_v9.go b/_examples/gin-upgrading-overriding/v8_to_v9.go
index 272b670..014e3f5 100644
--- a/_examples/gin-upgrading-overriding/v8_to_v9.go
+++ b/_examples/gin-upgrading-overriding/v8_to_v9.go
@@ -29,6 +29,11 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
return nil
}
+func (v *defaultValidator) Engine() interface{} {
+ v.lazyinit()
+ return v.validate
+}
+
func (v *defaultValidator) lazyinit() {
v.once.Do(func() {
v.validate = validator.New()
diff --git a/_examples/simple/main.go b/_examples/simple/main.go
index 9e3d106..459356d 100644
--- a/_examples/simple/main.go
+++ b/_examples/simple/main.go
@@ -52,7 +52,7 @@ func validateStruct() {
Addresses: []*Address{address},
}
- // returns nil or ValidationErrors ( map[string]*FieldError )
+ // returns nil or ValidationErrors ( []FieldError )
err := validate.Struct(user)
if err != nil {
diff --git a/baked_in.go b/baked_in.go
index 231b78e..51b65f4 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -1,10 +1,13 @@
package validator
import (
+ "bytes"
"context"
+ "crypto/sha256"
"fmt"
"net"
"net/url"
+ "os"
"reflect"
"strconv"
"strings"
@@ -95,7 +98,9 @@ var (
"email": isEmail,
"url": isURL,
"uri": isURI,
+ "file": isFile,
"base64": isBase64,
+ "base64url": isBase64URL,
"contains": contains,
"containsany": containsAny,
"containsrune": containsRune,
@@ -105,6 +110,9 @@ var (
"isbn": isISBN,
"isbn10": isISBN10,
"isbn13": isISBN13,
+ "eth_addr": isEthereumAddress,
+ "btc_addr": isBitcoinAddress,
+ "btc_addr_bech32": isBitcoinBech32Address,
"uuid": isUUID,
"uuid3": isUUID3,
"uuid4": isUUID4,
@@ -138,6 +146,9 @@ var (
"fqdn": isFQDN,
"unique": isUnique,
"oneof": isOneOf,
+ "html": isHTML,
+ "html_encoded": isHTMLEncoded,
+ "url_encoded": isURLEncoded,
}
)
@@ -157,6 +168,18 @@ func parseOneOfParam2(s string) []string {
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 {
vals := parseOneOfParam2(fl.Param())
@@ -181,7 +204,7 @@ func isOneOf(fl FieldLevel) bool {
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 {
field := fl.Field()
@@ -189,12 +212,19 @@ func isUnique(fl FieldLevel) bool {
switch field.Kind() {
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++ {
m.SetMapIndex(field.Index(i), v)
}
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:
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
@@ -387,6 +417,140 @@ func isISBN10(fl FieldLevel) bool {
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.
func excludesRune(fl FieldLevel) bool {
return !containsRune(fl)
@@ -845,6 +1009,11 @@ func isBase64(fl FieldLevel) bool {
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.
func isURI(fl FieldLevel) bool {
@@ -908,6 +1077,23 @@ func isURL(fl FieldLevel) bool {
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.
func isEmail(fl FieldLevel) bool {
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.
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.
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.
diff --git a/cache.go b/cache.go
index c7fb0fb..a7a4202 100644
--- a/cache.go
+++ b/cache.go
@@ -223,7 +223,7 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
current.typeof = typeKeys
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
diff --git a/doc.go b/doc.go
index f7efe23..6bd3c83 100644
--- a/doc.go
+++ b/doc.go
@@ -168,7 +168,7 @@ StructOnly
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
-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.
NOTE: only "required" and "omitempty" can be used on a struct itself.
@@ -506,6 +506,7 @@ to the top level struct.
Unique
For arrays & slices, unique will ensure that there are no duplicates.
+For maps, unique will ensure that there are no duplicate values.
Usage: unique
@@ -537,6 +538,7 @@ Numeric
This validates that a string value contains a basic numeric value.
basic excludes exponents etc...
+for integers or float it returns true.
Usage: numeric
@@ -585,6 +587,14 @@ does any email provider accept all posibilities.
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
This validates that a string value contains a valid url
@@ -609,6 +619,40 @@ this with the omitempty tag.
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
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
-
Universally Unique Identifier 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
-This validates that a string value contains a valid IP Adress.
+This validates that a string value contains a valid IP Address.
Usage: ip
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
@@ -860,6 +903,27 @@ This validates that a string value contains a valid 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
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
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
- 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.
In place of this new validator functions should be created; a regex can
diff --git a/field_level.go b/field_level.go
index 6d73192..cbfbc15 100644
--- a/field_level.go
+++ b/field_level.go
@@ -17,7 +17,7 @@ type FieldLevel interface {
Field() reflect.Value
// 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
// returns the struct field's name
diff --git a/regexes.go b/regexes.go
index 78f3ea0..07a8705 100644
--- a/regexes.go
+++ b/regexes.go
@@ -3,65 +3,85 @@ package validator
import "regexp"
const (
- alphaRegexString = "^[a-zA-Z]+$"
- alphaNumericRegexString = "^[a-zA-Z0-9]+$"
- alphaUnicodeRegexString = "^[\\p{L}]+$"
- alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$"
- numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
- numberRegexString = "^[0-9]+$"
- hexadecimalRegexString = "^[0-9a-fA-F]+$"
- hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
- rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$"
- rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
- hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
- 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}])))\\.?$"
- 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})$"
- 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}$"
- uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[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}$"
- uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
- aSCIIRegexString = "^[\x00-\x7F]*$"
- printableASCIIRegexString = "^[\x20-\x7E]*$"
- multibyteRegexString = "[^\x00-\x7F]"
- dataURIRegexString = "^data:.+\\/(.+);base64$"
- latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
- longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
- sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
- 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
+ alphaRegexString = "^[a-zA-Z]+$"
+ alphaNumericRegexString = "^[a-zA-Z0-9]+$"
+ alphaUnicodeRegexString = "^[\\p{L}]+$"
+ alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$"
+ numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
+ numberRegexString = "^[0-9]+$"
+ hexadecimalRegexString = "^[0-9a-fA-F]+$"
+ hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
+ rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$"
+ rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
+ hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
+ 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}])))\\.?$"
+ 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})$"
+ iSBN10RegexString = "^(?:[0-9]{9}X|[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}$"
+ uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[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}$"
+ uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
+ aSCIIRegexString = "^[\x00-\x7F]*$"
+ printableASCIIRegexString = "^[\x20-\x7E]*$"
+ multibyteRegexString = "[^\x00-\x7F]"
+ dataURIRegexString = "^data:.+\\/(.+);base64$"
+ latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
+ longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
+ sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
+ 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})|(>)|(<)|(")|(&)+[;]?`
+ hTMLRegexString = `<[/]?([a-zA-Z]+).*?>`
)
var (
- alphaRegex = regexp.MustCompile(alphaRegexString)
- alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString)
- alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString)
- alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString)
- numericRegex = regexp.MustCompile(numericRegexString)
- numberRegex = regexp.MustCompile(numberRegexString)
- hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString)
- hexcolorRegex = regexp.MustCompile(hexcolorRegexString)
- rgbRegex = regexp.MustCompile(rgbRegexString)
- rgbaRegex = regexp.MustCompile(rgbaRegexString)
- hslRegex = regexp.MustCompile(hslRegexString)
- hslaRegex = regexp.MustCompile(hslaRegexString)
- emailRegex = regexp.MustCompile(emailRegexString)
- base64Regex = regexp.MustCompile(base64RegexString)
- iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
- iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
- uUID3Regex = regexp.MustCompile(uUID3RegexString)
- uUID4Regex = regexp.MustCompile(uUID4RegexString)
- uUID5Regex = regexp.MustCompile(uUID5RegexString)
- uUIDRegex = regexp.MustCompile(uUIDRegexString)
- aSCIIRegex = regexp.MustCompile(aSCIIRegexString)
- printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString)
- multibyteRegex = regexp.MustCompile(multibyteRegexString)
- dataURIRegex = regexp.MustCompile(dataURIRegexString)
- latitudeRegex = regexp.MustCompile(latitudeRegexString)
- longitudeRegex = regexp.MustCompile(longitudeRegexString)
- sSNRegex = regexp.MustCompile(sSNRegexString)
- hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952)
- hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123)
+ alphaRegex = regexp.MustCompile(alphaRegexString)
+ alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString)
+ alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString)
+ alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString)
+ numericRegex = regexp.MustCompile(numericRegexString)
+ numberRegex = regexp.MustCompile(numberRegexString)
+ hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString)
+ hexcolorRegex = regexp.MustCompile(hexcolorRegexString)
+ rgbRegex = regexp.MustCompile(rgbRegexString)
+ rgbaRegex = regexp.MustCompile(rgbaRegexString)
+ hslRegex = regexp.MustCompile(hslRegexString)
+ hslaRegex = regexp.MustCompile(hslaRegexString)
+ emailRegex = regexp.MustCompile(emailRegexString)
+ base64Regex = regexp.MustCompile(base64RegexString)
+ base64URLRegex = regexp.MustCompile(base64URLRegexString)
+ iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
+ iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
+ uUID3Regex = regexp.MustCompile(uUID3RegexString)
+ uUID4Regex = regexp.MustCompile(uUID4RegexString)
+ uUID5Regex = regexp.MustCompile(uUID5RegexString)
+ uUIDRegex = regexp.MustCompile(uUIDRegexString)
+ aSCIIRegex = regexp.MustCompile(aSCIIRegexString)
+ printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString)
+ multibyteRegex = regexp.MustCompile(multibyteRegexString)
+ dataURIRegex = regexp.MustCompile(dataURIRegexString)
+ latitudeRegex = regexp.MustCompile(latitudeRegexString)
+ longitudeRegex = regexp.MustCompile(longitudeRegexString)
+ sSNRegex = regexp.MustCompile(sSNRegexString)
+ 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)
)
diff --git a/testdata/a.go b/testdata/a.go
new file mode 100644
index 0000000..69d29d3
--- /dev/null
+++ b/testdata/a.go
@@ -0,0 +1 @@
+package testdata
diff --git a/translations.go b/translations.go
index 4465abb..4d9d75c 100644
--- a/translations.go
+++ b/translations.go
@@ -7,5 +7,5 @@ import ut "github.com/go-playground/universal-translator"
type TranslationFunc func(ut ut.Translator, fe FieldError) string
// 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
diff --git a/translations/en/en.go b/translations/en/en.go
index 3ac1c47..f38fa1a 100644
--- a/translations/en/en.go
+++ b/translations/en/en.go
@@ -429,7 +429,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
@@ -548,7 +549,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
@@ -667,7 +669,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
@@ -786,7 +789,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
diff --git a/translations/fr/fr.go b/translations/fr/fr.go
index 871cf5f..1bf5dae 100644
--- a/translations/fr/fr.go
+++ b/translations/fr/fr.go
@@ -429,7 +429,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
@@ -548,7 +549,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
@@ -667,7 +669,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
@@ -786,7 +789,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
diff --git a/translations/pt_BR/pt_BR.go b/translations/pt_BR/pt_BR.go
index 13a00bd..dcb223d 100644
--- a/translations/pt_BR/pt_BR.go
+++ b/translations/pt_BR/pt_BR.go
@@ -429,7 +429,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
@@ -548,7 +549,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
@@ -667,7 +669,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
@@ -786,7 +789,8 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct:
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())
diff --git a/validator.go b/validator.go
index 483e0a2..67473f1 100644
--- a/validator.go
+++ b/validator.go
@@ -213,8 +213,8 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
CONTINUE:
// 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...
- // VarWithField - this allows for validating against each field withing the struct against a specific value
- // pretty handly in certain situations
+ // VarWithField - this allows for validating against each field within the struct against a specific value
+ // pretty handy in certain situations
if len(cf.name) > 0 {
ns = append(append(ns, cf.altName...), '.')
structNs = append(append(structNs, cf.name...), '.')
diff --git a/validator_instance.go b/validator_instance.go
index e84b452..400efcf 100644
--- a/validator_instance.go
+++ b/validator_instance.go
@@ -71,7 +71,7 @@ type Validate struct {
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 {
tc := new(tagCache)
@@ -97,7 +97,7 @@ func New() *Validate {
for k, val := range bakedInValidators {
// 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{
@@ -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
// 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'
//
// 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
// 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'
//
// 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
// 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'
//
// 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
// 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'
//
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
diff --git a/validator_test.go b/validator_test.go
index 9600d0f..2efabc5 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -5,8 +5,10 @@ import (
"context"
"database/sql"
"database/sql/driver"
+ "encoding/base64"
"encoding/json"
"fmt"
+ "path/filepath"
"reflect"
"strings"
"testing"
@@ -1015,7 +1017,7 @@ func TestCrossStructLteFieldValidation(t *testing.T) {
NotEqual(t, errs, nil)
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")
NotEqual(t, errs, nil)
Equal(t, len(errs.(ValidationErrors)), 6)
@@ -1112,7 +1114,7 @@ func TestCrossStructLtFieldValidation(t *testing.T) {
NotEqual(t, errs, nil)
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")
NotEqual(t, errs, nil)
AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "ltcsfield")
@@ -1220,7 +1222,7 @@ func TestCrossStructGteFieldValidation(t *testing.T) {
NotEqual(t, errs, nil)
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")
NotEqual(t, errs, nil)
AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtecsfield")
@@ -1316,7 +1318,7 @@ func TestCrossStructGtFieldValidation(t *testing.T) {
NotEqual(t, errs, nil)
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")
NotEqual(t, errs, nil)
AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtcsfield")
@@ -4398,6 +4400,283 @@ func TestBase64Validation(t *testing.T) {
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) {
type Inner struct {
@@ -5873,6 +6152,11 @@ func TestEmail(t *testing.T) {
errs = validate.Var(s, "email")
Equal(t, errs, nil)
+ s = "mail@domain_with_underscores.org"
+ errs = validate.Var(s, "email")
+ NotEqual(t, errs, nil)
+ AssertError(t, errs, "", "", "", "", "email")
+
s = ""
errs = validate.Var(s, "email")
NotEqual(t, errs, nil)
@@ -5991,8 +6275,7 @@ func TestNumber(t *testing.T) {
i := 1
errs = validate.Var(i, "number")
- NotEqual(t, errs, nil)
- AssertError(t, errs, "", "", "", "", "number")
+ Equal(t, errs, nil)
}
func TestNumeric(t *testing.T) {
@@ -6035,8 +6318,7 @@ func TestNumeric(t *testing.T) {
i := 1
errs = validate.Var(i, "numeric")
- NotEqual(t, errs, nil)
- AssertError(t, errs, "", "", "", "", "numeric")
+ Equal(t, errs, nil)
}
func TestAlphaNumeric(t *testing.T) {
@@ -6593,8 +6875,8 @@ func TestTranslations(t *testing.T) {
}, func(ut ut.Translator, fe FieldError) string {
- t, err := ut.T(fe.Tag(), fe.Field())
- if err != nil {
+ t, transErr := ut.T(fe.Tag(), fe.Field())
+ if transErr != nil {
fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError))
return fe.(*fieldError).Error()
}
@@ -7451,6 +7733,18 @@ func TestUniqueValidation(t *testing.T) {
param interface{}
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},
{[]int{1, 2}, true},
{[]float64{1, 2}, true},
@@ -7461,6 +7755,17 @@ func TestUniqueValidation(t *testing.T) {
{[]string{"a", "a"}, false},
{[]interface{}{"a", "a"}, 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()
@@ -7487,6 +7792,129 @@ func TestUniqueValidation(t *testing.T) {
PanicMatches(t, func() { validate.Var(1.0, "unique") }, "Bad field type float64")
}
+func TestHTMLValidation(t *testing.T) {
+ tests := []struct {
+ param string
+ expected bool
+ }{
+ {"", true},
+ {"", 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
+ }{
+ {"<", true},
+ {"¯", true},
+ {"", true},
+ {"ð", true},
+ {"<", true},
+ {"¯", true},
+ {"", true},
+ {"ð", true},
+ {"ab", true},
+ {"<", true},
+ {">", true},
+ {""", true},
+ {"&", true},
+ {"#x0a", false},
+ {"&x00", false},
+ {"z", 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) {
type Test struct {
@@ -7567,7 +7995,7 @@ func TestKeys(t *testing.T) {
// 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(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
validate = New()