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}, + {"", 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()