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 ./...