Merge branch 'do_not_valide_structs_twice_pr' of git://github.com/France-ioi/govalidator into France-ioi-do_not_valide_structs_twice_pr

# Conflicts:
#	validator_test.go
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8d69a94
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+bin/
+.idea/
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
diff --git a/.travis.yml b/.travis.yml
index e29f8ee..17c4d0a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,18 @@
+dist: bionic
 language: go
+env: GO111MODULE=on GOFLAGS='-mod vendor'
+install: true
+email: false
 
 go:
-  - 1.1
-  - 1.2
-  - 1.3
-  - 1.4
-  - 1.5
-  - 1.6
+  - 1.10
+  - 1.11
+  - 1.12
+  - 1.13
   - tip
 
-notifications:
-  email:
-    - bwatas@gmail.com
+before_script:
+  - go install github.com/golangci/golangci-lint/cmd/golangci-lint
+script:
+  - golangci-lint run       # run a bunch of code checkers/linters in parallel
+  - go test -v -race ./...  # Run all the tests with the race detector enabled
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f0f7e3a..66b3aa6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -37,7 +37,7 @@
 ### Contributors
 
 Thank you to all the people who have already contributed to govalidator!
-<a href="graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
+<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
 
 
 ### Backers
diff --git a/README.md b/README.md
index 0e8793f..5b02b75 100644
--- a/README.md
+++ b/README.md
@@ -165,6 +165,7 @@
 func IsSSN(str string) bool
 func IsSemver(str string) bool
 func IsTime(str string, format string) bool
+func IsUnixTime(str string) bool
 func IsURL(str string) bool
 func IsUTFDigit(str string) bool
 func IsUTFLetter(str string) bool
@@ -330,8 +331,10 @@
 "range(min|max)": Range,
 "length(min|max)": ByteLength,
 "runelength(min|max)": RuneLength,
+"stringlength(min|max)": StringLength,
 "matches(pattern)": StringMatches,
 "in(string1|string2|...|stringN)": IsIn,
+"rsapub(keylength)" : IsRsaPub,
 ```
 
 And here is small example of usage:
@@ -339,12 +342,14 @@
 type Post struct {
 	Title    string `valid:"alphanum,required"`
 	Message  string `valid:"duck,ascii"`
+	Message2 string `valid:"animal(dog)"`
 	AuthorIP string `valid:"ipv4"`
 	Date     string `valid:"-"`
 }
 post := &Post{
 	Title:   "My Example Post",
 	Message: "duck",
+	Message2: "dog",
 	AuthorIP: "123.234.54.3",
 }
 
@@ -353,6 +358,13 @@
 	return str == "duck"
 })
 
+// Add your own struct validation tags with parameter
+govalidator.ParamTagMap["animal"] = govalidator.ParamValidator(func(str string, params ...string) bool {
+    species := params[0]
+    return str == species
+})
+govalidator.ParamTagRegexMap["animal"] = regexp.MustCompile("^animal\\((\\w+)\\)$")
+
 result, err := govalidator.ValidateStruct(post)
 if err != nil {
 	println("error: " + err.Error())
@@ -464,7 +476,7 @@
 * [Matt Sanford](https://github.com/mzsanford)
 * [Simon ccl1115](https://github.com/ccl1115)
 
-<a href="graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
+<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
 
 
 ### Backers
diff --git a/doc.go b/doc.go
new file mode 100644
index 0000000..55dce62
--- /dev/null
+++ b/doc.go
@@ -0,0 +1,3 @@
+package govalidator
+
+// A package of validators and sanitizers for strings, structures and collections.
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..c1ce891
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/asaskevich/govalidator
+
+go 1.12
diff --git a/patterns.go b/patterns.go
index 61a05d4..1cf9726 100644
--- a/patterns.go
+++ b/patterns.go
@@ -4,49 +4,50 @@
 
 // Basic regular expressions for validating strings
 const (
-    Email             string = "^(((([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}])))\\.?$"
-    CreditCard        string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
-    ISBN10            string = "^(?:[0-9]{9}X|[0-9]{10})$"
-    ISBN13            string = "^(?:[0-9]{13})$"
-    UUID3             string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
-    UUID4             string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
-    UUID5             string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
-    UUID              string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
-    Alpha             string = "^[a-zA-Z]+$"
-    Alphanumeric      string = "^[a-zA-Z0-9]+$"
-    Numeric           string = "^[0-9]+$"
-    Int               string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
-    Float             string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
-    Hexadecimal       string = "^[0-9a-fA-F]+$"
-    Hexcolor          string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
-    RGBcolor          string = "^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])\\s*\\)$"
-    ASCII             string = "^[\x00-\x7F]+$"
-    Multibyte         string = "[^\x00-\x7F]"
-    FullWidth         string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
-    HalfWidth         string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
-    Base64            string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
-    PrintableASCII    string = "^[\x20-\x7E]+$"
-    DataURI           string = "^data:.+\\/(.+);base64$"
-    Latitude          string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
-    Longitude         string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
-    DNSName           string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
-    IP                string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
-    URLSchema         string = `((ftp|tcp|udp|wss?|https?):\/\/)`
-    URLUsername       string = `(\S+(:\S*)?@)`
-    URLPath           string = `((\/|\?|#)[^\s]*)`
-    URLPort           string = `(:(\d{1,5}))`
-    URLIP             string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))`
-	  URLSubdomain      string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`  
-    URL               string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
-    SSN               string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
-    WinPath           string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
-    UnixPath          string = `^(/[^/\x00]*)+/?$`
-    Semver            string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
-    tagName           string = "valid"
-    hasLowerCase      string = ".*[[:lower:]]"
-    hasUpperCase      string = ".*[[:upper:]]"
-    hasWhitespace     string = ".*[[:space:]]"
-    hasWhitespaceOnly string = "^[[:space:]]+$"
+	Email             string = "^(((([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}])))\\.?$"
+	CreditCard        string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$"
+	ISBN10            string = "^(?:[0-9]{9}X|[0-9]{10})$"
+	ISBN13            string = "^(?:[0-9]{13})$"
+	UUID3             string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
+	UUID4             string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
+	UUID5             string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
+	UUID              string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
+	Alpha             string = "^[a-zA-Z]+$"
+	Alphanumeric      string = "^[a-zA-Z0-9]+$"
+	Numeric           string = "^[0-9]+$"
+	Int               string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
+	Float             string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
+	Hexadecimal       string = "^[0-9a-fA-F]+$"
+	Hexcolor          string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
+	RGBcolor          string = "^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])\\s*\\)$"
+	ASCII             string = "^[\x00-\x7F]+$"
+	Multibyte         string = "[^\x00-\x7F]"
+	FullWidth         string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
+	HalfWidth         string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
+	Base64            string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
+	PrintableASCII    string = "^[\x20-\x7E]+$"
+	DataURI           string = "^data:.+\\/(.+);base64$"
+	MagnetURI         string = "^magnet:\\?xt=urn:[a-zA-Z0-9]+:[a-zA-Z0-9]{32,40}&dn=.+&tr=.+$"
+	Latitude          string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
+	Longitude         string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
+	DNSName           string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
+	IP                string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
+	URLSchema         string = `((ftp|tcp|udp|wss?|https?):\/\/)`
+	URLUsername       string = `(\S+(:\S*)?@)`
+	URLPath           string = `((\/|\?|#)[^\s]*)`
+	URLPort           string = `(:(\d{1,5}))`
+	URLIP             string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))`
+	URLSubdomain      string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
+	URL               string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
+	SSN               string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
+	WinPath           string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
+	UnixPath          string = `^(/[^/\x00]*)+/?$`
+	Semver            string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
+	tagName           string = "valid"
+	hasLowerCase      string = ".*[[:lower:]]"
+	hasUpperCase      string = ".*[[:upper:]]"
+	hasWhitespace     string = ".*[[:space:]]"
+	hasWhitespaceOnly string = "^[[:space:]]+$"
 )
 
 // Used by IsFilePath func
@@ -60,42 +61,43 @@
 )
 
 var (
-    userRegexp            = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$")
-    hostRegexp            = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$")
-    userDotRegexp         = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})")
-    rxEmail               = regexp.MustCompile(Email)
-    rxCreditCard          = regexp.MustCompile(CreditCard)
-    rxISBN10              = regexp.MustCompile(ISBN10)
-    rxISBN13              = regexp.MustCompile(ISBN13)
-    rxUUID3               = regexp.MustCompile(UUID3)
-    rxUUID4               = regexp.MustCompile(UUID4)
-    rxUUID5               = regexp.MustCompile(UUID5)
-    rxUUID                = regexp.MustCompile(UUID)
-    rxAlpha               = regexp.MustCompile(Alpha)
-    rxAlphanumeric        = regexp.MustCompile(Alphanumeric)
-    rxNumeric             = regexp.MustCompile(Numeric)
-    rxInt                 = regexp.MustCompile(Int)
-    rxFloat               = regexp.MustCompile(Float)
-    rxHexadecimal         = regexp.MustCompile(Hexadecimal)
-    rxHexcolor            = regexp.MustCompile(Hexcolor)
-    rxRGBcolor            = regexp.MustCompile(RGBcolor)
-    rxASCII               = regexp.MustCompile(ASCII)
-    rxPrintableASCII      = regexp.MustCompile(PrintableASCII)
-    rxMultibyte           = regexp.MustCompile(Multibyte)
-    rxFullWidth           = regexp.MustCompile(FullWidth)
-    rxHalfWidth           = regexp.MustCompile(HalfWidth)
-    rxBase64              = regexp.MustCompile(Base64)
-    rxDataURI             = regexp.MustCompile(DataURI)
-    rxLatitude            = regexp.MustCompile(Latitude)
-    rxLongitude           = regexp.MustCompile(Longitude)
-    rxDNSName             = regexp.MustCompile(DNSName)
-    rxURL                 = regexp.MustCompile(URL)
-    rxSSN                 = regexp.MustCompile(SSN)
-    rxWinPath             = regexp.MustCompile(WinPath)
-    rxUnixPath            = regexp.MustCompile(UnixPath)
-    rxSemver              = regexp.MustCompile(Semver)
-    rxHasLowerCase        = regexp.MustCompile(hasLowerCase)
-    rxHasUpperCase        = regexp.MustCompile(hasUpperCase)
-    rxHasWhitespace       = regexp.MustCompile(hasWhitespace)
-    rxHasWhitespaceOnly   = regexp.MustCompile(hasWhitespaceOnly)
+	userRegexp          = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$")
+	hostRegexp          = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$")
+	userDotRegexp       = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})")
+	rxEmail             = regexp.MustCompile(Email)
+	rxCreditCard        = regexp.MustCompile(CreditCard)
+	rxISBN10            = regexp.MustCompile(ISBN10)
+	rxISBN13            = regexp.MustCompile(ISBN13)
+	rxUUID3             = regexp.MustCompile(UUID3)
+	rxUUID4             = regexp.MustCompile(UUID4)
+	rxUUID5             = regexp.MustCompile(UUID5)
+	rxUUID              = regexp.MustCompile(UUID)
+	rxAlpha             = regexp.MustCompile(Alpha)
+	rxAlphanumeric      = regexp.MustCompile(Alphanumeric)
+	rxNumeric           = regexp.MustCompile(Numeric)
+	rxInt               = regexp.MustCompile(Int)
+	rxFloat             = regexp.MustCompile(Float)
+	rxHexadecimal       = regexp.MustCompile(Hexadecimal)
+	rxHexcolor          = regexp.MustCompile(Hexcolor)
+	rxRGBcolor          = regexp.MustCompile(RGBcolor)
+	rxASCII             = regexp.MustCompile(ASCII)
+	rxPrintableASCII    = regexp.MustCompile(PrintableASCII)
+	rxMultibyte         = regexp.MustCompile(Multibyte)
+	rxFullWidth         = regexp.MustCompile(FullWidth)
+	rxHalfWidth         = regexp.MustCompile(HalfWidth)
+	rxBase64            = regexp.MustCompile(Base64)
+	rxDataURI           = regexp.MustCompile(DataURI)
+	rxMagnetURI         = regexp.MustCompile(MagnetURI)
+	rxLatitude          = regexp.MustCompile(Latitude)
+	rxLongitude         = regexp.MustCompile(Longitude)
+	rxDNSName           = regexp.MustCompile(DNSName)
+	rxURL               = regexp.MustCompile(URL)
+	rxSSN               = regexp.MustCompile(SSN)
+	rxWinPath           = regexp.MustCompile(WinPath)
+	rxUnixPath          = regexp.MustCompile(UnixPath)
+	rxSemver            = regexp.MustCompile(Semver)
+	rxHasLowerCase      = regexp.MustCompile(hasLowerCase)
+	rxHasUpperCase      = regexp.MustCompile(hasUpperCase)
+	rxHasWhitespace     = regexp.MustCompile(hasWhitespace)
+	rxHasWhitespaceOnly = regexp.MustCompile(hasWhitespaceOnly)
 )
diff --git a/utils.go b/utils.go
index a0b706a..f4c30f8 100644
--- a/utils.go
+++ b/utils.go
@@ -12,20 +12,20 @@
 	"unicode/utf8"
 )
 
-// Contains check if the string contains the substring.
+// Contains checks if the string contains the substring.
 func Contains(str, substring string) bool {
 	return strings.Contains(str, substring)
 }
 
-// Matches check if string matches the pattern (pattern is regular expression)
+// Matches checks if string matches the pattern (pattern is regular expression)
 // In case of error return false
 func Matches(str, pattern string) bool {
 	match, _ := regexp.MatchString(pattern, str)
 	return match
 }
 
-// LeftTrim trim characters from the left-side of the input.
-// If second argument is empty, it's will be remove leading spaces.
+// LeftTrim trims characters from the left side of the input.
+// If second argument is empty, it will remove leading spaces.
 func LeftTrim(str, chars string) string {
 	if chars == "" {
 		return strings.TrimLeftFunc(str, unicode.IsSpace)
@@ -34,8 +34,8 @@
 	return r.ReplaceAllString(str, "")
 }
 
-// RightTrim trim characters from the right-side of the input.
-// If second argument is empty, it's will be remove spaces.
+// RightTrim trims characters from the right side of the input.
+// If second argument is empty, it will remove trailing spaces.
 func RightTrim(str, chars string) string {
 	if chars == "" {
 		return strings.TrimRightFunc(str, unicode.IsSpace)
@@ -44,27 +44,27 @@
 	return r.ReplaceAllString(str, "")
 }
 
-// Trim trim characters from both sides of the input.
-// If second argument is empty, it's will be remove spaces.
+// Trim trims characters from both sides of the input.
+// If second argument is empty, it will remove spaces.
 func Trim(str, chars string) string {
 	return LeftTrim(RightTrim(str, chars), chars)
 }
 
-// WhiteList remove characters that do not appear in the whitelist.
+// WhiteList removes characters that do not appear in the whitelist.
 func WhiteList(str, chars string) string {
 	pattern := "[^" + chars + "]+"
 	r, _ := regexp.Compile(pattern)
 	return r.ReplaceAllString(str, "")
 }
 
-// BlackList remove characters that appear in the blacklist.
+// BlackList removes characters that appear in the blacklist.
 func BlackList(str, chars string) string {
 	pattern := "[" + chars + "]+"
 	r, _ := regexp.Compile(pattern)
 	return r.ReplaceAllString(str, "")
 }
 
-// StripLow remove characters with a numerical value < 32 and 127, mostly control characters.
+// StripLow removes characters with a numerical value < 32 and 127, mostly control characters.
 // If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
 func StripLow(str string, keepNewLines bool) string {
 	chars := ""
@@ -76,13 +76,13 @@
 	return BlackList(str, chars)
 }
 
-// ReplacePattern replace regular expression pattern in string
+// ReplacePattern replaces regular expression pattern in string
 func ReplacePattern(str, pattern, replace string) string {
 	r, _ := regexp.Compile(pattern)
 	return r.ReplaceAllString(str, replace)
 }
 
-// Escape replace <, >, & and " with HTML entities.
+// Escape replaces <, >, & and " with HTML entities.
 var Escape = html.EscapeString
 
 func addSegment(inrune, segment []rune) []rune {
@@ -120,7 +120,7 @@
 	return string(output)
 }
 
-// Reverse return reversed string
+// Reverse returns reversed string
 func Reverse(s string) string {
 	r := []rune(s)
 	for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
@@ -129,12 +129,12 @@
 	return string(r)
 }
 
-// GetLines split string by "\n" and return array of lines
+// GetLines splits string by "\n" and return array of lines
 func GetLines(s string) []string {
 	return strings.Split(s, "\n")
 }
 
-// GetLine return specified line of multiline string
+// GetLine returns specified line of multiline string
 func GetLine(s string, index int) (string, error) {
 	lines := GetLines(s)
 	if index < 0 || index >= len(lines) {
@@ -143,12 +143,12 @@
 	return lines[index], nil
 }
 
-// RemoveTags remove all tags from HTML string
+// RemoveTags removes all tags from HTML string
 func RemoveTags(s string) string {
 	return ReplacePattern(s, "<[^>]*>", "")
 }
 
-// SafeFileName return safe string that can be used in file names
+// SafeFileName returns safe string that can be used in file names
 func SafeFileName(str string) string {
 	name := strings.ToLower(str)
 	name = path.Clean(path.Base(name))
@@ -210,23 +210,23 @@
 	return str
 }
 
-// PadLeft pad left side of string if size of string is less then indicated pad length
+// PadLeft pads left side of a string if size of string is less then indicated pad length
 func PadLeft(str string, padStr string, padLen int) string {
 	return buildPadStr(str, padStr, padLen, true, false)
 }
 
-// PadRight pad right side of string if size of string is less then indicated pad length
+// PadRight pads right side of a string if size of string is less then indicated pad length
 func PadRight(str string, padStr string, padLen int) string {
 	return buildPadStr(str, padStr, padLen, false, true)
 }
 
-// PadBoth pad sides of string if size of string is less then indicated pad length
+// PadBoth pads both sides of a string if size of string is less then indicated pad length
 func PadBoth(str string, padStr string, padLen int) string {
 	return buildPadStr(str, padStr, padLen, true, true)
 }
 
-// PadString either left, right or both sides, not the padding string can be unicode and more then one
-// character
+// PadString either left, right or both sides.
+// Note that padding string can be unicode and more then one character
 func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string {
 
 	// When padded length is less then the current string size
diff --git a/validator.go b/validator.go
index 9275c05..cb1f08a 100644
--- a/validator.go
+++ b/validator.go
@@ -323,12 +323,12 @@
 
 // HasWhitespaceOnly checks the string only contains whitespace
 func HasWhitespaceOnly(str string) bool {
-    return len(str) > 0 && rxHasWhitespaceOnly.MatchString(str)
+	return len(str) > 0 && rxHasWhitespaceOnly.MatchString(str)
 }
 
 // HasWhitespace checks if the string contains any whitespace
 func HasWhitespace(str string) bool {
-    return len(str) > 0 && rxHasWhitespace.MatchString(str)
+	return len(str) > 0 && rxHasWhitespace.MatchString(str)
 }
 
 // IsByteLength check if the string's length (in bytes) falls in a range.
@@ -513,6 +513,11 @@
 	return IsBase64(dataURI[1])
 }
 
+// IsMagnetURI checks if a string is valid magnet URI
+func IsMagnetURI(str string) bool {
+	return rxMagnetURI.MatchString(str)
+}
+
 // IsISO3166Alpha2 checks if a string is valid two-letter country code
 func IsISO3166Alpha2(str string) bool {
 	for _, entry := range ISO3166List {
@@ -589,9 +594,73 @@
 	return Matches(str, "^[a-f0-9]{"+len+"}$")
 }
 
+// IsSHA512 checks is a string is a SHA512 hash. Alias for `IsHash(str, "sha512")`
+func IsSHA512(str string) bool {
+	return IsHash(str, "sha512")
+}
+
+// IsSHA384 checks is a string is a SHA384 hash. Alias for `IsHash(str, "sha384")`
+func IsSHA384(str string) bool {
+	return IsHash(str, "sha384")
+}
+
+// IsSHA256 checks is a string is a SHA256 hash. Alias for `IsHash(str, "sha256")`
+func IsSHA256(str string) bool {
+	return IsHash(str, "sha256")
+}
+
+// IsTiger192 checks is a string is a Tiger192 hash. Alias for `IsHash(str, "tiger192")`
+func IsTiger192(str string) bool {
+	return IsHash(str, "tiger192")
+}
+
+// IsTiger160 checks is a string is a Tiger160 hash. Alias for `IsHash(str, "tiger160")`
+func IsTiger160(str string) bool {
+	return IsHash(str, "tiger160")
+}
+
+// IsRipeMD160 checks is a string is a RipeMD160 hash. Alias for `IsHash(str, "ripemd160")`
+func IsRipeMD160(str string) bool {
+	return IsHash(str, "ripemd160")
+}
+
+// IsSHA1 checks is a string is a SHA-1 hash. Alias for `IsHash(str, "sha1")`
+func IsSHA1(str string) bool {
+	return IsHash(str, "sha1")
+}
+
+// IsTiger128 checks is a string is a Tiger128 hash. Alias for `IsHash(str, "tiger128")`
+func IsTiger128(str string) bool {
+	return IsHash(str, "tiger128")
+}
+
+// IsRipeMD128 checks is a string is a RipeMD128 hash. Alias for `IsHash(str, "ripemd128")`
+func IsRipeMD128(str string) bool {
+	return IsHash(str, "ripemd128")
+}
+
+// IsCRC32 checks is a string is a CRC32 hash. Alias for `IsHash(str, "crc32")`
+func IsCRC32(str string) bool {
+	return IsHash(str, "crc32")
+}
+
+// IsCRC32b checks is a string is a CRC32b hash. Alias for `IsHash(str, "crc32b")`
+func IsCRC32b(str string) bool {
+	return IsHash(str, "crc32b")
+}
+
+// IsMD5 checks is a string is a MD5 hash. Alias for `IsHash(str, "md5")`
+func IsMD5(str string) bool {
+	return IsHash(str, "md5")
+}
+
+// IsMD4 checks is a string is a MD4 hash. Alias for `IsHash(str, "md4")`
+func IsMD4(str string) bool {
+	return IsHash(str, "md4")
+}
+
 // IsDialString validates the given string for usage with the various Dial() functions
 func IsDialString(str string) bool {
-
 	if h, p, err := net.SplitHostPort(str); err == nil && h != "" && p != "" && (IsDNSName(h) || IsIP(h)) && IsPort(p) {
 		return true
 	}
@@ -599,7 +668,7 @@
 	return false
 }
 
-// IsIP checks if a string is either IP version 4 or 6.
+// IsIP checks if a string is either IP version 4 or 6. Alias for `net.ParseIP`
 func IsIP(str string) bool {
 	return net.ParseIP(str) != nil
 }
@@ -862,6 +931,14 @@
 	return err == nil
 }
 
+// IsUnixTime check if string is valid unix timestamp value
+func IsUnixTime(str string) bool {
+	if _, err := strconv.Atoi(str); err == nil {
+		return true
+	}
+	return false
+}
+
 // IsRFC3339 check if string is valid timestamp value according to RFC3339
 func IsRFC3339(str string) bool {
 	return IsTime(str, time.RFC3339)
@@ -1014,7 +1091,7 @@
 		options = parseTagIntoMap(tag)
 	}
 
-	if isEmptyValue(v) {
+	if !isFieldSet(v) {
 		// an empty value is not validated, check only required
 		isValid, resultErr = checkRequired(v, t, options)
 		for key := range options {
@@ -1121,10 +1198,10 @@
 				delete(options, validatorSpec)
 
 				switch v.Kind() {
-                case reflect.String,
-                    reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
-                    reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
-                    reflect.Float32, reflect.Float64:
+				case reflect.String,
+					reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+					reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
+					reflect.Float32, reflect.Float64:
 					field := fmt.Sprint(v) // make value into string, then validate with regex
 					if result := validatefunc(field); !result && !negate || result && negate {
 						if customMsgExists {
@@ -1212,25 +1289,14 @@
 	return paramsRegexp.ReplaceAllString(validatorString, "")
 }
 
-func isEmptyValue(v reflect.Value) bool {
+// isFieldSet returns false for nil pointers, interfaces, maps, and slices. For all other values, it returns true.
+func isFieldSet(v reflect.Value) bool {
 	switch v.Kind() {
-	case reflect.String, reflect.Array:
-		return v.Len() == 0
-	case reflect.Map, reflect.Slice:
-		return v.Len() == 0 || v.IsNil()
-	case reflect.Bool:
-		return !v.Bool()
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return v.Int() == 0
-	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
-		return v.Uint() == 0
-	case reflect.Float32, reflect.Float64:
-		return v.Float() == 0
-	case reflect.Interface, reflect.Ptr:
-		return v.IsNil()
+	case reflect.Map, reflect.Slice, reflect.Interface, reflect.Ptr:
+		return !v.IsNil()
 	}
 
-	return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
+	return true
 }
 
 // ErrorByField returns error for specified field of the struct
diff --git a/validator_test.go b/validator_test.go
index 2b85c8d..249933b 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -645,7 +645,7 @@
 		{"foo@bar.com", true},
 		{"foo@bar.com.au", true},
 		{"foo+bar@bar.com", true},
-		{"foo@bar.coffee", true},
+		{"foo@driftaway.coffee", true},
 		{"foo@bar.coffee..coffee", false},
 		{"invalidemail@", false},
 		{"invalid.com", false},
@@ -1384,26 +1384,36 @@
 
 func TestIsCreditCard(t *testing.T) {
 	t.Parallel()
-
-	var tests = []struct {
-		param    string
-		expected bool
+	tests := []struct {
+		name   string
+		number string
+		want   bool
 	}{
-		{"", false},
-		{"foo", false},
-		{"5398228707871528", false},
-		{"375556917985515", true},
-		{"36050234196908", true},
-		{"4716461583322103", true},
-		{"4716-2210-5188-5662", true},
-		{"4929 7226 5379 7141", true},
-		{"5398228707871527", true},
+		{"empty", "", false},
+		{"not numbers", "credit card", false},
+		{"invalid luhn algorithm", "4220855426213389", false},
+
+		{"visa", "4220855426222389", true},
+		{"visa spaces", "4220 8554 2622 2389", true},
+		{"visa dashes", "4220-8554-2622-2389", true},
+		{"mastercard", "5139288802098206", true},
+		{"american express", "374953669708156", true},
+		{"discover", "6011464355444102", true},
+		{"jcb", "3548209662790989", true},
+
+		// below should be valid, do they respect international standards?
+		// is our validator logic not correct?
+		{"diners club international", "30190239451016", false},
+		{"rupay", "6521674451993089", false},
+		{"mir", "2204151414444676", false},
+		{"china unionPay", "624356436327468104", false},
 	}
-	for _, test := range tests {
-		actual := IsCreditCard(test.param)
-		if actual != test.expected {
-			t.Errorf("Expected IsCreditCard(%q) to be %v, got %v", test.param, test.expected, actual)
-		}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsCreditCard(tt.number); got != tt.want {
+				t.Errorf("IsCreditCard(%v) = %v, want %v", tt.number, got, tt.want)
+			}
+		})
 	}
 }
 
@@ -1507,6 +1517,41 @@
 	}
 }
 
+func TestIsMagnetURI(t *testing.T) {
+	t.Parallel()
+
+	var tests = []struct {
+		param    string
+		expected bool
+	}{
+		{"magnet:?xt=urn:btih:06E2A9683BF4DA92C73A661AC56F0ECC9C63C5B4&dn=helloword2000&tr=udp://helloworld:1337/announce", true},
+		{"magnet:?xt=urn:btih:3E30322D5BFC7444B7B1D8DD42404B75D0531DFB&dn=world&tr=udp://world.com:1337", true},
+		{"magnet:?xt=urn:btih:4ODKSDJBVMSDSNJVBCBFYFBKNRU875DW8D97DWC6&dn=helloworld&tr=udp://helloworld.com:1337", true},
+		{"magnet:?xt=urn:btih:1GSHJVBDVDVJFYEHKFHEFIO8573898434JBFEGHD&dn=foo&tr=udp://foo.com:1337", true},
+		{"magnet:?xt=urn:btih:MCJDCYUFHEUD6E2752T7UJNEKHSUGEJFGTFHVBJS&dn=bar&tr=udp://bar.com:1337", true},
+		{"magnet:?xt=urn:btih:LAKDHWDHEBFRFVUFJENBYYTEUY837562JH2GEFYH&dn=foobar&tr=udp://foobar.com:1337", true},
+		{"magnet:?xt=urn:btih:MKCJBHCBJDCU725TGEB3Y6RE8EJ2U267UNJFGUID&dn=test&tr=udp://test.com:1337", true},
+		{"magnet:?xt=urn:btih:UHWY2892JNEJ2GTEYOMDNU67E8ICGICYE92JDUGH&dn=baz&tr=udp://baz.com:1337", true},
+		{"magnet:?xt=urn:btih:HS263FG8U3GFIDHWD7829BYFCIXB78XIHG7CWCUG&dn=foz&tr=udp://foz.com:1337", true},
+		{"", false},
+		{":?xt=urn:btih:06E2A9683BF4DA92C73A661AC56F0ECC9C63C5B4&dn=helloword2000&tr=udp://helloworld:1337/announce", false},
+		{"magnett:?xt=urn:btih:3E30322D5BFC7444B7B1D8DD42404B75D0531DFB&dn=world&tr=udp://world.com:1337", false},
+		{"xt=urn:btih:4ODKSDJBVMSDSNJVBCBFYFBKNRU875DW8D97DWC6&dn=helloworld&tr=udp://helloworld.com:1337", false},
+		{"magneta:?xt=urn:btih:1GSHJVBDVDVJFYEHKFHEFIO8573898434JBFEGHD&dn=foo&tr=udp://foo.com:1337", false},
+		{"magnet:?xt=uarn:btih:MCJDCYUFHEUD6E2752T7UJNEKHSUGEJFGTFHVBJS&dn=bar&tr=udp://bar.com:1337", false},
+		{"magnet:?xt=urn:btihz&dn=foobar&tr=udp://foobar.com:1337", false},
+		{"magnet:?xat=urn:btih:MKCJBHCBJDCU725TGEB3Y6RE8EJ2U267UNJFGUID&dn=test&tr=udp://test.com:1337", false},
+		{"magnet::?xt=urn:btih:UHWY2892JNEJ2GTEYOMDNU67E8ICGICYE92JDUGH&dn=baz&tr=udp://baz.com:1337", false},
+		{"magnet:?xt:btih:HS263FG8U3GFIDHWD7829BYFCIXB78XIHG7CWCUG&dn=foz&tr=udp://foz.com:1337", false},
+	}
+	for _, test := range tests {
+		actual := IsMagnetURI(test.param)
+		if actual != test.expected {
+			t.Errorf("Expected IsMagnetURI(%q) to be %v, got %v", test.param, test.expected, actual)
+		}
+	}
+}
+
 func TestIsBase64(t *testing.T) {
 	t.Parallel()
 
@@ -2199,7 +2244,7 @@
 	Name     string `valid:"required"`
 	Email    string `valid:"required,email"`
 	Password string `valid:"required"`
-	Age      int    `valid:"required,numeric,@#\u0000"`
+	Age      int    `valid:"required,numeric,range(1|200),@#\u0000"`
 	Home     *Address
 	Work     []Address
 }
@@ -2249,6 +2294,10 @@
 	IsIn string `valid:"in(PRESENT|PRÉSENTE|NOTABSENT)"`
 }
 
+type IsInStructWithPointer struct {
+	IsIn *string `valid:"in(PRESENT|PRÉSENTE|NOTABSENT)"`
+}
+
 type Post struct {
 	Title    string `valid:"alpha,required"`
 	Message  string `valid:"ascii"`
@@ -2279,6 +2328,11 @@
 	Email string `valid:"optional,email"`
 }
 
+type FieldsRequiredByDefaultButExemptOrOptionalStructWithPointers struct {
+	Name  *string `valid:"-"`
+	Email *string `valid:"optional,email"`
+}
+
 type MessageWithSeveralFieldsStruct struct {
 	Title string `valid:"length(1|10)"`
 	Body  string `valid:"length(1|10)"`
@@ -2373,9 +2427,9 @@
 		param    FieldsRequiredByDefaultButExemptOrOptionalStruct
 		expected bool
 	}{
-		{FieldsRequiredByDefaultButExemptOrOptionalStruct{}, true},
-		{FieldsRequiredByDefaultButExemptOrOptionalStruct{Name: "TEST"}, true},
-		{FieldsRequiredByDefaultButExemptOrOptionalStruct{Email: ""}, true},
+		{FieldsRequiredByDefaultButExemptOrOptionalStruct{}, false},
+		{FieldsRequiredByDefaultButExemptOrOptionalStruct{Name: "TEST"}, false},
+		{FieldsRequiredByDefaultButExemptOrOptionalStruct{Email: ""}, false},
 		{FieldsRequiredByDefaultButExemptOrOptionalStruct{Email: "test@example.com"}, true},
 		{FieldsRequiredByDefaultButExemptOrOptionalStruct{Email: "test@example"}, false},
 	}
@@ -2392,6 +2446,31 @@
 	SetFieldsRequiredByDefault(false)
 }
 
+func TestFieldsRequiredByDefaultButExemptOrOptionalStructWithPointers(t *testing.T) {
+	var tests = []struct {
+		param    FieldsRequiredByDefaultButExemptOrOptionalStructWithPointers
+		expected bool
+	}{
+		{FieldsRequiredByDefaultButExemptOrOptionalStructWithPointers{}, true},
+		{FieldsRequiredByDefaultButExemptOrOptionalStructWithPointers{Name: ptrString("TEST")}, true},
+		{FieldsRequiredByDefaultButExemptOrOptionalStructWithPointers{Email: ptrString("")}, false},
+		{FieldsRequiredByDefaultButExemptOrOptionalStructWithPointers{Email: nil}, true},
+		{FieldsRequiredByDefaultButExemptOrOptionalStructWithPointers{Email: ptrString("test@example.com")}, true},
+		{FieldsRequiredByDefaultButExemptOrOptionalStructWithPointers{Email: ptrString("test@example")}, false},
+	}
+	SetFieldsRequiredByDefault(true)
+	for _, test := range tests {
+		actual, err := ValidateStruct(test.param)
+		if actual != test.expected {
+			t.Errorf("Expected ValidateStruct(%#v) to be %v, got %v", test.param, test.expected, actual)
+			if err != nil {
+				t.Errorf("Got Error on ValidateStruct(%#v): %s", test.param, err)
+			}
+		}
+	}
+	SetFieldsRequiredByDefault(false)
+}
+
 func TestInvalidValidator(t *testing.T) {
 	type InvalidStruct struct {
 		Field int `valid:"someInvalidValidator"`
@@ -2414,7 +2493,7 @@
 	}
 
 	type StructWithCustomAndBuiltinValidator struct {
-		Field int `valid:"customTrueValidator,required"`
+		Field *int `valid:"customTrueValidator,required"`
 	}
 
 	if valid, err := ValidateStruct(&ValidStruct{Field: 1}); !valid || err != nil {
@@ -2431,7 +2510,7 @@
 		t.Errorf("Got an unexpected result for invalid struct with custom and built-in validators: %t %s", valid, err)
 	}
 
-	mixedStruct.Field = 1
+	mixedStruct.Field = ptrInt(1)
 	if valid, err := ValidateStruct(&mixedStruct); !valid || err != nil {
 		t.Errorf("Got an unexpected result for valid struct with custom and built-in validators: %t %s", valid, err)
 	}
@@ -2599,9 +2678,13 @@
 		expected bool
 	}{
 		{IsInStruct{"PRESENT"}, true},
-		{IsInStruct{""}, true},
+		{IsInStruct{""}, false},
 		{IsInStruct{" "}, false},
 		{IsInStruct{"ABSENT"}, false},
+		{IsInStructWithPointer{ptrString("PRESENT")}, true},
+		{IsInStructWithPointer{nil}, true},
+		{IsInStructWithPointer{ptrString("")}, false},
+		{IsInStructWithPointer{ptrString("ABSENT")}, false},
 	}
 
 	for _, test := range tests {
@@ -2619,6 +2702,9 @@
 	type RequiredIsInStruct struct {
 		IsIn string `valid:"in(PRESENT|PRÉSENTE|NOTABSENT),required"`
 	}
+	type RequiredIsInStructWithPointer struct {
+		IsIn *string `valid:"in(PRESENT|PRÉSENTE|NOTABSENT),required"`
+	}
 
 	var tests = []struct {
 		param    interface{}
@@ -2628,6 +2714,10 @@
 		{RequiredIsInStruct{""}, false},
 		{RequiredIsInStruct{" "}, false},
 		{RequiredIsInStruct{"ABSENT"}, false},
+		{RequiredIsInStructWithPointer{ptrString("PRESENT")}, true},
+		{RequiredIsInStructWithPointer{ptrString("")}, false},
+		{RequiredIsInStructWithPointer{nil}, false},
+		{RequiredIsInStructWithPointer{ptrString("ABSENT")}, false},
 	}
 
 	for _, test := range tests {
@@ -2645,15 +2735,21 @@
 	type EmptyRequiredIsInStruct struct {
 		IsIn string `valid:"in(),required"`
 	}
+	type EmptyRequiredIsInStructWithPointer struct {
+		IsIn *string `valid:"in(),required"`
+	}
 
 	var tests = []struct {
 		param    interface{}
 		expected bool
 	}{
 		{EmptyRequiredIsInStruct{"PRESENT"}, false},
-		{EmptyRequiredIsInStruct{""}, false},
+		{EmptyRequiredIsInStruct{""}, true}, // an empty string is allowed by 'in()' !
 		{EmptyRequiredIsInStruct{" "}, false},
 		{EmptyRequiredIsInStruct{"ABSENT"}, false},
+		{EmptyRequiredIsInStructWithPointer{ptrString("PRESENT")}, false},
+		{EmptyRequiredIsInStructWithPointer{ptrString("")}, true},
+		{EmptyRequiredIsInStructWithPointer{nil}, false},
 	}
 
 	for _, test := range tests {
@@ -2681,8 +2777,8 @@
 		expected    bool
 		expectedErr string
 	}{
-		{EmptyIsInStruct{&empty}, false, "IsIn: non zero value required"},
-		{EmptyIsInStruct{nil}, true, ""},
+		{EmptyIsInStruct{&empty}, false, "IsIn:  does not validate as length(3|5)"},
+		{EmptyIsInStruct{nil}, true, ""}, // because of SetNilPtrAllowedByRequired(true)
 		{EmptyIsInStruct{&valid}, true, ""},
 		{EmptyIsInStruct{&invalid}, false, "IsIn: 123456 does not validate as length(3|5)"},
 	}
@@ -2692,14 +2788,14 @@
 		actual, err := ValidateStruct(test.param)
 
 		if actual != test.expected {
-			t.Errorf("Expected ValidateStruct(%q) to be %v, got %v", test.param, test.expected, actual)
+			t.Errorf("Expected ValidateStruct(%#v) to be %v, got %v", test.param, test.expected, actual)
 		}
 		if err != nil {
 			if err.Error() != test.expectedErr {
-				t.Errorf("Got Error on ValidateStruct(%q). Expected: %s Actual: %s", test.param, test.expectedErr, err)
+				t.Errorf("Got Error on ValidateStruct(%#v). Expected: %s Actual: %s", test.param, test.expectedErr, err)
 			}
 		} else if test.expectedErr != "" {
-			t.Errorf("Expected error on ValidateStruct(%q).", test.param)
+			t.Errorf("Expected error on ValidateStruct(%#v).", test.param)
 		}
 	}
 	SetNilPtrAllowedByRequired(false)
@@ -2728,17 +2824,17 @@
 			Nested: NestedStruct{
 				Foo: "",
 			},
-		}, false, "Nested.Foo: non zero value required"},
+		}, false, "Nested.Foo:  does not validate as length(3|5);Nested.EvenMoreNested.Bar:  does not validate as length(3|5)"},
 		{OuterStruct{
 			Nested: NestedStruct{
 				Foo: "123",
 			},
-		}, true, ""},
+		}, false, "Nested.EvenMoreNested.Bar:  does not validate as length(3|5)"},
 		{OuterStruct{
 			Nested: NestedStruct{
 				Foo: "123456",
 			},
-		}, false, "Nested.Foo: 123456 does not validate as length(3|5)"},
+		}, false, "Nested.Foo: 123456 does not validate as length(3|5);Nested.EvenMoreNested.Bar:  does not validate as length(3|5)"},
 		{OuterStruct{
 			Nested: NestedStruct{
 				Foo: "123",
@@ -2756,7 +2852,7 @@
 					},
 				},
 			},
-		}, false, "Nested.SliceEvenMoreNested.0.Bar: 123456 does not validate as length(3|5)"},
+		}, false, "Nested.EvenMoreNested.Bar:  does not validate as length(3|5);Nested.SliceEvenMoreNested.0.Bar: 123456 does not validate as length(3|5)"},
 		{OuterStruct{
 			Nested: NestedStruct{
 				Foo: "123",
@@ -2766,7 +2862,7 @@
 					},
 				},
 			},
-		}, false, "Nested.MapEvenMoreNested.Foo.Bar: 123456 does not validate as length(3|5)"},
+		}, false, "Nested.EvenMoreNested.Bar:  does not validate as length(3|5);Nested.MapEvenMoreNested.Foo.Bar: 123456 does not validate as length(3|5)"},
 	}
 
 	for _, test := range tests {
@@ -2857,9 +2953,9 @@
 	for _, test := range tests {
 		actual, err := ValidateStruct(test.param)
 		if actual != test.expected {
-			t.Errorf("Expected ValidateStruct(%q) to be %v, got %v", test.param, test.expected, actual)
+			t.Errorf("Expected ValidateStruct(%#v) to be %v, got %v", test.param, test.expected, actual)
 			if err != nil {
-				t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err)
+				t.Errorf("Got Error on ValidateStruct(%#v): %s", test.param, err)
 			}
 		}
 	}
@@ -2902,7 +2998,7 @@
 			}{
 				Pointer: &testEmptyString,
 			},
-			false,
+			true,
 		},
 		{
 			struct {
@@ -2916,7 +3012,7 @@
 			struct {
 				Addr Address `valid:"required"`
 			}{},
-			false,
+			true,
 		},
 		{
 			struct {
@@ -2943,16 +3039,10 @@
 		{
 			struct {
 				TestByteArray testByteArray `valid:"required"`
-			}{},
-			false,
-		},
-		{
-			struct {
-				TestByteArray testByteArray `valid:"required"`
 			}{
 				testByteArray{},
 			},
-			false,
+			true, // array cannot be nil
 		},
 		{
 			struct {
@@ -2994,9 +3084,9 @@
 	for _, test := range tests {
 		actual, err := ValidateStruct(test.param)
 		if actual != test.expected {
-			t.Errorf("Expected ValidateStruct(%q) to be %v, got %v", test.param, test.expected, actual)
+			t.Errorf("Expected ValidateStruct(%#v) to be %v, got %v", test.param, test.expected, actual)
 			if err != nil {
-				t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err)
+				t.Errorf("Got Error on ValidateStruct(%#v): %s", test.param, err)
 			}
 		}
 	}
@@ -3338,6 +3428,29 @@
 
 	ok, err := ValidateStruct(val)
 
+	if err == nil {
+		t.Error("Expected non-nil err with optional validation, got nil")
+	}
+
+	if ok {
+		t.Error("Expected validation to return false, got true")
+	}
+}
+
+func TestOptionalCustomValidatorsWithPointers(t *testing.T) {
+
+	CustomTypeTagMap.Set("f2", CustomTypeValidator(func(i interface{}, o interface{}) bool {
+		return false
+	}))
+
+	var val struct {
+		WithCustomError    *string `valid:"f2~boom,optional"`
+		WithoutCustomError *string `valid:"f2,optional"`
+		OptionalFirst      *string `valid:"optional,f2"`
+	}
+
+	ok, err := ValidateStruct(val)
+
 	if err != nil {
 		t.Errorf("Expected nil err with optional validation, got %v", err)
 	}
@@ -3489,6 +3602,9 @@
 	}
 }
 
+func ptrString(s string) *string { return &s }
+func ptrInt(i int) *int          { return &i }
+
 func TestDoNotRepeatStructErrors(t *testing.T) {
 	// based on the code provided by @apremalal
 	type TestB struct {
diff --git a/wercker.yml b/wercker.yml
index cac7a5f..bc5f7b0 100644
--- a/wercker.yml
+++ b/wercker.yml
@@ -12,4 +12,4 @@
     - script:
         name: go test
         code: |
-          go test -race ./...
+          go test -race -v ./...