Merge pull request #169 from joeybloggs/v8-development

New v8 Changes
pull/171/head v8
Dean Karn 9 years ago
commit 40bd6a0bc0
  1. 100
      README.md
  2. 60
      baked_in.go
  3. 26
      benchmarks_test.go
  4. 25
      doc.go
  5. 22
      examples_test.go
  6. 4
      regexes.go
  7. 97
      util.go
  8. 362
      validator.go
  9. 230
      validator_test.go

@ -14,26 +14,46 @@ It has the following **unique** features:
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated. - Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
- Handles type interface by determining it's underlying type prior to validation. - Handles type interface by determining it's underlying type prior to validation.
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29) - Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs
Installation Installation
------------ ------------
Use go get. Use go get.
go get gopkg.in/bluesuncorp/validator.v7 go get gopkg.in/bluesuncorp/validator.v8
or to update or to update
go get -u gopkg.in/bluesuncorp/validator.v7 go get -u gopkg.in/bluesuncorp/validator.v8
Then import the validator package into your own code. Then import the validator package into your own code.
import "gopkg.in/bluesuncorp/validator.v7" import "gopkg.in/bluesuncorp/validator.v8"
Error Return Value
-------
Validation functions return type error
They return type error to avoid the issue discussed in the following, where err is always != nil:
* http://stackoverflow.com/a/29138676/3158232
* https://github.com/bluesuncorp/validator/issues/134
validator only returns nil or ValidationErrors as type error; so in you code all you need to do
is check if the error returned is not nil, and if it's not type cast it to type ValidationErrors
like so:
```go
err := validate.Struct(mystruct)
validationErrors := err.(validator.ValidationErrors)
```
Usage and documentation Usage and documentation
------ ------
Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v7 for detailed usage docs. Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v8 for detailed usage docs.
##### Examples: ##### Examples:
@ -44,7 +64,7 @@ package main
import ( import (
"fmt" "fmt"
"gopkg.in/bluesuncorp/validator.v7" "gopkg.in/bluesuncorp/validator.v8"
) )
// User contains user information // User contains user information
@ -69,10 +89,7 @@ var validate *validator.Validate
func main() { func main() {
config := validator.Config{ config := &validator.Config{TagName: "validate"}
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
}
validate = validator.New(config) validate = validator.New(config)
@ -98,13 +115,13 @@ func validateStruct() {
} }
// returns nil or ValidationErrors ( map[string]*FieldError ) // returns nil or ValidationErrors ( map[string]*FieldError )
errs := validate.Struct(user) err := validate.Struct(user)
if errs != nil { if errs != nil {
fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag
// Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag
err := errs["User.Addresses[0].City"] err := errs.(validator.ValidationErrors)["User.Addresses[0].City"]
fmt.Println(err.Field) // output: City fmt.Println(err.Field) // output: City
fmt.Println(err.Tag) // output: required fmt.Println(err.Tag) // output: required
fmt.Println(err.Kind) // output: string fmt.Println(err.Kind) // output: string
@ -143,7 +160,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"gopkg.in/bluesuncorp/validator.v7" "gopkg.in/bluesuncorp/validator.v8"
) )
// DbBackedUser User struct // DbBackedUser User struct
@ -154,10 +171,7 @@ type DbBackedUser struct {
func main() { func main() {
config := validator.Config{ config := &validator.Config{TagName: "validate"}
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
}
validate := validator.New(config) validate := validator.New(config)
@ -167,7 +181,7 @@ func main() {
x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}
errs := validate.Struct(x) errs := validate.Struct(x)
if len(errs) > 0 { if len(errs.(validator.ValidationErrors)) > 0 {
fmt.Printf("Errs:\n%+v\n", errs) fmt.Printf("Errs:\n%+v\n", errs)
} }
} }
@ -191,32 +205,32 @@ Benchmarks
```go ```go
$ go test -cpu=4 -bench=. -benchmem=true $ go test -cpu=4 -bench=. -benchmem=true
PASS PASS
BenchmarkFieldSuccess-4 5000000 285 ns/op 16 B/op 1 allocs/op BenchmarkFieldSuccess-4 5000000 296 ns/op 16 B/op 1 allocs/op
BenchmarkFieldFailure-4 5000000 284 ns/op 16 B/op 1 allocs/op BenchmarkFieldFailure-4 5000000 294 ns/op 16 B/op 1 allocs/op
BenchmarkFieldDiveSuccess-4 500000 2501 ns/op 384 B/op 19 allocs/op BenchmarkFieldDiveSuccess-4 500000 2529 ns/op 384 B/op 19 allocs/op
BenchmarkFieldDiveFailure-4 500000 3022 ns/op 752 B/op 23 allocs/op BenchmarkFieldDiveFailure-4 500000 3056 ns/op 768 B/op 23 allocs/op
BenchmarkFieldCustomTypeSuccess-4 3000000 445 ns/op 32 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccess-4 3000000 443 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-4 2000000 788 ns/op 416 B/op 6 allocs/op BenchmarkFieldCustomTypeFailure-4 2000000 753 ns/op 384 B/op 4 allocs/op
BenchmarkFieldOrTagSuccess-4 1000000 1377 ns/op 32 B/op 2 allocs/op BenchmarkFieldOrTagSuccess-4 1000000 1334 ns/op 32 B/op 2 allocs/op
BenchmarkFieldOrTagFailure-4 1000000 1201 ns/op 400 B/op 6 allocs/op BenchmarkFieldOrTagFailure-4 1000000 1172 ns/op 416 B/op 6 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1257 ns/op 80 B/op 5 allocs/op BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1206 ns/op 80 B/op 5 allocs/op
BenchmarkStructSimpleCustomTypeFailure-4 1000000 1776 ns/op 608 B/op 13 allocs/op BenchmarkStructSimpleCustomTypeFailure-4 1000000 1737 ns/op 592 B/op 11 allocs/op
BenchmarkStructPartialSuccess-4 1000000 1354 ns/op 400 B/op 11 allocs/op BenchmarkStructPartialSuccess-4 1000000 1367 ns/op 400 B/op 11 allocs/op
BenchmarkStructPartialFailure-4 1000000 1813 ns/op 784 B/op 16 allocs/op BenchmarkStructPartialFailure-4 1000000 1914 ns/op 800 B/op 16 allocs/op
BenchmarkStructExceptSuccess-4 2000000 916 ns/op 368 B/op 9 allocs/op BenchmarkStructExceptSuccess-4 2000000 909 ns/op 368 B/op 9 allocs/op
BenchmarkStructExceptFailure-4 1000000 1369 ns/op 400 B/op 11 allocs/op BenchmarkStructExceptFailure-4 1000000 1350 ns/op 400 B/op 11 allocs/op
BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1033 ns/op 128 B/op 6 allocs/op BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1218 ns/op 128 B/op 6 allocs/op
BenchmarkStructSimpleCrossFieldFailure-4 1000000 1569 ns/op 528 B/op 11 allocs/op BenchmarkStructSimpleCrossFieldFailure-4 1000000 1783 ns/op 544 B/op 11 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1371 ns/op 160 B/op 8 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1806 ns/op 160 B/op 8 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 1935 ns/op 560 B/op 13 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 2369 ns/op 576 B/op 13 allocs/op
BenchmarkStructSimpleSuccess-4 1000000 1161 ns/op 48 B/op 3 allocs/op BenchmarkStructSimpleSuccess-4 1000000 1161 ns/op 48 B/op 3 allocs/op
BenchmarkStructSimpleFailure-4 1000000 1720 ns/op 560 B/op 11 allocs/op BenchmarkStructSimpleFailure-4 1000000 1813 ns/op 592 B/op 11 allocs/op
BenchmarkStructSimpleSuccessParallel-4 5000000 329 ns/op 48 B/op 3 allocs/op BenchmarkStructSimpleSuccessParallel-4 5000000 353 ns/op 48 B/op 3 allocs/op
BenchmarkStructSimpleFailureParallel-4 2000000 625 ns/op 560 B/op 11 allocs/op BenchmarkStructSimpleFailureParallel-4 2000000 656 ns/op 592 B/op 11 allocs/op
BenchmarkStructComplexSuccess-4 200000 6636 ns/op 432 B/op 27 allocs/op BenchmarkStructComplexSuccess-4 200000 7637 ns/op 432 B/op 27 allocs/op
BenchmarkStructComplexFailure-4 200000 11327 ns/op 2919 B/op 69 allocs/op BenchmarkStructComplexFailure-4 100000 12775 ns/op 3128 B/op 69 allocs/op
BenchmarkStructComplexSuccessParallel-4 1000000 1991 ns/op 432 B/op 27 allocs/op BenchmarkStructComplexSuccessParallel-4 1000000 2270 ns/op 432 B/op 27 allocs/op
BenchmarkStructComplexFailureParallel-4 500000 3854 ns/op 2920 B/op 69 allocs/op BenchmarkStructComplexFailureParallel-4 300000 4328 ns/op 3128 B/op 69 allocs/op
``` ```
How to Contribute How to Contribute
@ -227,7 +241,7 @@ please make your pull requests against those branches.
If the changes being proposed or requested are breaking changes, please create an issue, for discussion If the changes being proposed or requested are breaking changes, please create an issue, for discussion
or create a pull request against the highest development branch for example this package has a or create a pull request against the highest development branch for example this package has a
v1 and v1-development branch however, there will also be a v2-development brach even though v2 doesn't exist yet. v1 and v1-development branch however, there will also be a v2-development branch even though v2 doesn't exist yet.
I strongly encourage everyone whom creates a custom validation function to contribute them and I strongly encourage everyone whom creates a custom validation function to contribute them and
help make this package even better. help make this package even better.

@ -10,10 +10,18 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// BakedInAliasValidators is a default mapping of a single validationstag that
// defines a common or complex set of validation(s) to simplify
// adding validation to structs. i.e. set key "_ageok" and the tags
// are "gt=0,lte=130" or key "_preferredname" and tags "omitempty,gt=0,lte=60"
var bakedInAliasValidators = map[string]string{
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
}
// BakedInValidators is the default map of ValidationFunc // BakedInValidators is the default map of ValidationFunc
// you can add, remove or even replace items to suite your needs, // you can add, remove or even replace items to suite your needs,
// or even disregard and use your own map if so desired. // or even disregard and use your own map if so desired.
var BakedInValidators = map[string]Func{ var bakedInValidators = map[string]Func{
"required": hasValue, "required": hasValue,
"len": hasLengthOf, "len": hasLengthOf,
"min": hasMinOf, "min": hasMinOf,
@ -107,15 +115,15 @@ func isSSN(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Va
return false return false
} }
return matchesRegex(sSNRegex, field.String()) return sSNRegex.MatchString(field.String())
} }
func isLongitude(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isLongitude(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(longitudeRegex, field.String()) return longitudeRegex.MatchString(field.String())
} }
func isLatitude(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isLatitude(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(latitudeRegex, field.String()) return latitudeRegex.MatchString(field.String())
} }
func isDataURI(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isDataURI(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
@ -126,7 +134,7 @@ func isDataURI(v *Validate, topStruct reflect.Value, currentStructOrField reflec
return false return false
} }
if !matchesRegex(dataURIRegex, uri[0]) { if !dataURIRegex.MatchString(uri[0]) {
return false return false
} }
@ -141,31 +149,31 @@ func hasMultiByteCharacter(v *Validate, topStruct reflect.Value, currentStructOr
return true return true
} }
return matchesRegex(multibyteRegex, field.String()) return multibyteRegex.MatchString(field.String())
} }
func isPrintableASCII(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isPrintableASCII(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(printableASCIIRegex, field.String()) return printableASCIIRegex.MatchString(field.String())
} }
func isASCII(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isASCII(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(aSCIIRegex, field.String()) return aSCIIRegex.MatchString(field.String())
} }
func isUUID5(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isUUID5(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(uUID5Regex, field.String()) return uUID5Regex.MatchString(field.String())
} }
func isUUID4(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isUUID4(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(uUID4Regex, field.String()) return uUID4Regex.MatchString(field.String())
} }
func isUUID3(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isUUID3(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(uUID3Regex, field.String()) return uUID3Regex.MatchString(field.String())
} }
func isUUID(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isUUID(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(uUIDRegex, field.String()) return uUIDRegex.MatchString(field.String())
} }
func isISBN(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isISBN(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
@ -176,7 +184,7 @@ func isISBN13(v *Validate, topStruct reflect.Value, currentStructOrField reflect
s := strings.Replace(strings.Replace(field.String(), "-", "", 4), " ", "", 4) s := strings.Replace(strings.Replace(field.String(), "-", "", 4), " ", "", 4)
if !matchesRegex(iSBN13Regex, s) { if !iSBN13Regex.MatchString(s) {
return false return false
} }
@ -200,7 +208,7 @@ func isISBN10(v *Validate, topStruct reflect.Value, currentStructOrField reflect
s := strings.Replace(strings.Replace(field.String(), "-", "", 3), " ", "", 3) s := strings.Replace(strings.Replace(field.String(), "-", "", 3), " ", "", 3)
if !matchesRegex(iSBN10Regex, s) { if !iSBN10Regex.MatchString(s) {
return false return false
} }
@ -617,7 +625,7 @@ func isEq(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Val
} }
func isBase64(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isBase64(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(base64Regex, field.String()) return base64Regex.MatchString(field.String())
} }
func isURI(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isURI(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
@ -655,47 +663,47 @@ func isURL(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Va
} }
func isEmail(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isEmail(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(emailRegex, field.String()) return emailRegex.MatchString(field.String())
} }
func isHsla(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isHsla(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(hslaRegex, field.String()) return hslaRegex.MatchString(field.String())
} }
func isHsl(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isHsl(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(hslRegex, field.String()) return hslRegex.MatchString(field.String())
} }
func isRgba(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isRgba(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(rgbaRegex, field.String()) return rgbaRegex.MatchString(field.String())
} }
func isRgb(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isRgb(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(rgbRegex, field.String()) return rgbRegex.MatchString(field.String())
} }
func isHexcolor(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isHexcolor(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(hexcolorRegex, field.String()) return hexcolorRegex.MatchString(field.String())
} }
func isHexadecimal(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isHexadecimal(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(hexadecimalRegex, field.String()) return hexadecimalRegex.MatchString(field.String())
} }
func isNumber(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isNumber(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(numberRegex, field.String()) return numberRegex.MatchString(field.String())
} }
func isNumeric(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isNumeric(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(numericRegex, field.String()) return numericRegex.MatchString(field.String())
} }
func isAlphanum(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isAlphanum(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(alphaNumericRegex, field.String()) return alphaNumericRegex.MatchString(field.String())
} }
func isAlpha(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func isAlpha(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return matchesRegex(alphaRegex, field.String()) return alphaRegex.MatchString(field.String())
} }
func hasValue(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func hasValue(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {

@ -2,7 +2,6 @@ package validator
import ( import (
sql "database/sql/driver" sql "database/sql/driver"
"reflect"
"testing" "testing"
"time" "time"
) )
@ -33,11 +32,7 @@ func BenchmarkFieldDiveFailure(b *testing.B) {
func BenchmarkFieldCustomTypeSuccess(b *testing.B) { func BenchmarkFieldCustomTypeSuccess(b *testing.B) {
customTypes := map[reflect.Type]CustomTypeFunc{} validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})
val := valuer{ val := valuer{
Name: "1", Name: "1",
@ -50,11 +45,8 @@ func BenchmarkFieldCustomTypeSuccess(b *testing.B) {
func BenchmarkFieldCustomTypeFailure(b *testing.B) { func BenchmarkFieldCustomTypeFailure(b *testing.B) {
customTypes := map[reflect.Type]CustomTypeFunc{} // validate := New(Config{TagName: "validate"})
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})
val := valuer{} val := valuer{}
@ -77,11 +69,7 @@ func BenchmarkFieldOrTagFailure(b *testing.B) {
func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
customTypes := map[reflect.Type]CustomTypeFunc{} validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})
val := valuer{ val := valuer{
Name: "1", Name: "1",
@ -101,11 +89,7 @@ func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {
customTypes := map[reflect.Type]CustomTypeFunc{} validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})
val := valuer{} val := valuer{}

@ -16,6 +16,19 @@ I needed to know the field and what validation failed so that I could provide an
return "Translated string based on field" return "Translated string based on field"
} }
Validation functions return type error
Doing things this way is actually the way the standard library does, see the file.Open
method here: https://golang.org/pkg/os/#Open.
They return type error to avoid the issue discussed in the following, where err is always != nil:
http://stackoverflow.com/a/29138676/3158232
https://github.com/bluesuncorp/validator/issues/134
validator only returns nil or ValidationErrors as type error; so in you code all you need to do
is check if the error returned is not nil, and if it's not type cast it to type ValidationErrors
like so err.(validator.ValidationErrors)
Custom Functions Custom Functions
Custom functions can be added Custom functions can be added
@ -445,6 +458,18 @@ Here is a list of the current built in validators:
http://golang.org/src/net/mac.go?s=866:918#L29 http://golang.org/src/net/mac.go?s=866:918#L29
(Usage: mac) (Usage: mac)
Alias Validators and Tags
NOTE: when returning an error the tag returned in FieldError will be
the alias tag unless the dive tag is part of the alias; everything after the
dive tag is not reported as the alias tag. Also the ActualTag in the before case
will be the actual tag within the alias that failed.
Here is a list of the current built in alias tags:
iscolor
alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor)
Validator notes: Validator notes:
regex regex

@ -3,14 +3,12 @@ package validator_test
import ( import (
"fmt" "fmt"
"gopkg.in/bluesuncorp/validator.v7" // "gopkg.in/bluesuncorp/validator.v7"
"../validator"
) )
func ExampleValidate_new() { func ExampleValidate_new() {
config := validator.Config{ config := &validator.Config{TagName: "validate"}
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
}
validator.New(config) validator.New(config)
} }
@ -19,16 +17,13 @@ func ExampleValidate_field() {
// This should be stored somewhere globally // This should be stored somewhere globally
var validate *validator.Validate var validate *validator.Validate
config := validator.Config{ config := &validator.Config{TagName: "validate"}
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
}
validate = validator.New(config) validate = validator.New(config)
i := 0 i := 0
errs := validate.Field(i, "gt=1,lte=10") errs := validate.Field(i, "gt=1,lte=10")
err := errs[""] err := errs.(validator.ValidationErrors)[""]
fmt.Println(err.Field) fmt.Println(err.Field)
fmt.Println(err.Tag) fmt.Println(err.Tag)
fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time
@ -48,10 +43,7 @@ func ExampleValidate_struct() {
// This should be stored somewhere globally // This should be stored somewhere globally
var validate *validator.Validate var validate *validator.Validate
config := validator.Config{ config := &validator.Config{TagName: "validate"}
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
}
validate = validator.New(config) validate = validator.New(config)
@ -81,7 +73,7 @@ func ExampleValidate_struct() {
} }
errs := validate.Struct(user) errs := validate.Struct(user)
for _, v := range errs { for _, v := range errs.(validator.ValidationErrors) {
fmt.Println(v.Field) // Phone fmt.Println(v.Field) // Phone
fmt.Println(v.Tag) // required fmt.Println(v.Tag) // required
//... and so forth //... and so forth

@ -57,7 +57,3 @@ var (
longitudeRegex = regexp.MustCompile(longitudeRegexString) longitudeRegex = regexp.MustCompile(longitudeRegexString)
sSNRegex = regexp.MustCompile(sSNRegexString) sSNRegex = regexp.MustCompile(sSNRegexString)
) )
func matchesRegex(regex *regexp.Regexp, value string) bool {
return regex.MatchString(value)
}

@ -1,15 +1,32 @@
package validator package validator
import ( import (
"fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
) )
const ( const (
blank = ""
namespaceSeparator = "." namespaceSeparator = "."
leftBracket = "[" leftBracket = "["
rightBracket = "]" rightBracket = "]"
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
restrictedAliasErr = "Alias \"%s\" either contains restricted characters or is the same as a restricted tag needed for normal operation"
restrictedTagErr = "Tag \"%s\" either contains restricted characters or is the same as a restricted tag needed for normal operation"
)
var (
restrictedTags = map[string]*struct{}{
diveTag: emptyStructPtr,
existsTag: emptyStructPtr,
structOnlyTag: emptyStructPtr,
omitempty: emptyStructPtr,
skipValidationTag: emptyStructPtr,
utf8HexComma: emptyStructPtr,
utf8Pipe: emptyStructPtr,
}
) )
func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Kind) { func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Kind) {
@ -36,8 +53,8 @@ func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Ki
default: default:
if v.config.hasCustomFuncs { if v.hasCustomFuncs {
if fn, ok := v.config.CustomTypeFuncs[current.Type()]; ok { if fn, ok := v.customTypeFuncs[current.Type()]; ok {
return v.extractType(reflect.ValueOf(fn(current))) return v.extractType(reflect.ValueOf(fn(current)))
} }
} }
@ -78,7 +95,7 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re
fld = namespace[:idx] fld = namespace[:idx]
ns = namespace[idx+1:] ns = namespace[idx+1:]
} else { } else {
ns = "" ns = blank
idx = len(namespace) idx = len(namespace)
} }
@ -214,3 +231,77 @@ func panicIf(err error) {
panic(err.Error()) panic(err.Error())
} }
} }
func (v *Validate) parseTags(tag, fieldName string) *cachedTag {
cTag := &cachedTag{}
v.parseTagsRecursive(cTag, tag, fieldName, blank, false)
return cTag
}
func (v *Validate) parseTagsRecursive(cTag *cachedTag, tag, fieldName, alias string, isAlias bool) bool {
if len(tag) == 0 {
return true
}
for _, t := range strings.Split(tag, tagSeparator) {
if v.hasAliasValidators {
// check map for alias and process new tags, otherwise process as usual
if tagsVal, ok := v.aliasValidators[t]; ok {
leave := v.parseTagsRecursive(cTag, tagsVal, fieldName, t, true)
if leave {
return leave
}
continue
}
}
if t == diveTag {
cTag.diveTag = tag
tVals := &tagVals{tagVals: [][]string{{t}}}
cTag.tags = append(cTag.tags, tVals)
return true
}
if t == omitempty {
cTag.isOmitEmpty = true
}
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator)
tagVal := &tagVals{isAlias: isAlias, isOrVal: len(orVals) > 1, tagVals: make([][]string, len(orVals))}
cTag.tags = append(cTag.tags, tagVal)
var key string
var param string
for i, val := range orVals {
vals := strings.SplitN(val, tagKeySeparator, 2)
key = vals[0]
tagVal.tag = key
if isAlias {
tagVal.tag = alias
}
if len(key) == 0 {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
}
if len(vals) > 1 {
param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
}
tagVal.tagVals[i] = []string{key, param}
}
}
return false
}

@ -20,54 +20,56 @@ import (
) )
const ( const (
utf8HexComma = "0x2C" utf8HexComma = "0x2C"
utf8Pipe = "0x7C" utf8Pipe = "0x7C"
tagSeparator = "," tagSeparator = ","
orSeparator = "|" orSeparator = "|"
tagKeySeparator = "=" tagKeySeparator = "="
structOnlyTag = "structonly" structOnlyTag = "structonly"
omitempty = "omitempty" omitempty = "omitempty"
skipValidationTag = "-" skipValidationTag = "-"
diveTag = "dive" diveTag = "dive"
existsTag = "exists" existsTag = "exists"
fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag"
arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket
mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket
invalidValidation = "Invalid validation tag on field %s" invalidValidation = "Invalid validation tag on field %s"
undefinedValidation = "Undefined validation function on field %s" undefinedValidation = "Undefined validation function on field %s"
validatorNotInitialized = "Validator instance not initialized"
) )
var ( var (
timeType = reflect.TypeOf(time.Time{}) timeType = reflect.TypeOf(time.Time{})
timePtrType = reflect.TypeOf(&time.Time{}) timePtrType = reflect.TypeOf(&time.Time{})
errsPool = &sync.Pool{New: newValidationErrors}
tagsCache = &tagCacheMap{m: map[string][]*tagCache{}}
emptyStructPtr = new(struct{}) emptyStructPtr = new(struct{})
) )
// returns new ValidationErrors to the pool type cachedTag struct {
func newValidationErrors() interface{} { isOmitEmpty bool
return ValidationErrors{} diveTag string
tags []*tagVals
} }
type tagCache struct { type tagVals struct {
tagVals [][]string tagVals [][]string
isOrVal bool isOrVal bool
isAlias bool
tag string
} }
type tagCacheMap struct { type tagCacheMap struct {
lock sync.RWMutex lock sync.RWMutex
m map[string][]*tagCache m map[string]*cachedTag
} }
func (s *tagCacheMap) Get(key string) ([]*tagCache, bool) { func (s *tagCacheMap) Get(key string) (*cachedTag, bool) {
s.lock.RLock() s.lock.RLock()
defer s.lock.RUnlock() defer s.lock.RUnlock()
value, ok := s.m[key] value, ok := s.m[key]
return value, ok return value, ok
} }
func (s *tagCacheMap) Set(key string, value []*tagCache) { func (s *tagCacheMap) Set(key string, value *cachedTag) {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
s.m[key] = value s.m[key] = value
@ -75,16 +77,26 @@ func (s *tagCacheMap) Set(key string, value []*tagCache) {
// Validate contains the validator settings passed in using the Config struct // Validate contains the validator settings passed in using the Config struct
type Validate struct { type Validate struct {
config Config tagName string
validationFuncs map[string]Func
customTypeFuncs map[reflect.Type]CustomTypeFunc
aliasValidators map[string]string
hasCustomFuncs bool
hasAliasValidators bool
tagsCache *tagCacheMap
errsPool *sync.Pool
}
func (v *Validate) initCheck() {
if v == nil {
panic(validatorNotInitialized)
}
} }
// Config contains the options that a Validator instance will use. // Config contains the options that a Validator instance will use.
// It is passed to the New() function // It is passed to the New() function
type Config struct { type Config struct {
TagName string TagName string
ValidationFuncs map[string]Func
CustomTypeFuncs map[reflect.Type]CustomTypeFunc
hasCustomFuncs bool
} }
// CustomTypeFunc allows for overriding or adding custom field type handler functions // CustomTypeFunc allows for overriding or adding custom field type handler functions
@ -111,7 +123,7 @@ type ValidationErrors map[string]*FieldError
// the FieldError found within the ValidationErrors map // the FieldError found within the ValidationErrors map
func (ve ValidationErrors) Error() string { func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString("") buff := bytes.NewBufferString(blank)
for key, err := range ve { for key, err := range ve {
buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag)) buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag))
@ -124,28 +136,49 @@ func (ve ValidationErrors) Error() string {
// FieldError contains a single field's validation error along // FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation // with other properties that may be needed for error message creation
type FieldError struct { type FieldError struct {
Field string Field string
Tag string Tag string
Kind reflect.Kind ActualTag string
Type reflect.Type Kind reflect.Kind
Param string Type reflect.Type
Value interface{} Param string
Value interface{}
} }
// New creates a new Validate instance for use. // New creates a new Validate instance for use.
func New(config Config) *Validate { func New(config *Config) *Validate {
v := &Validate{
tagName: config.TagName,
tagsCache: &tagCacheMap{m: map[string]*cachedTag{}},
errsPool: &sync.Pool{New: func() interface{} {
return ValidationErrors{}
}}}
if len(v.aliasValidators) == 0 {
// must copy alias validators for separate validations to be used in each validator instance
v.aliasValidators = map[string]string{}
for k, val := range bakedInAliasValidators {
v.RegisterAliasValidation(k, val)
}
}
if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { if len(v.validationFuncs) == 0 {
config.hasCustomFuncs = true // must copy validators for separate validations to be used in each instance
v.validationFuncs = map[string]Func{}
for k, val := range bakedInValidators {
v.RegisterValidation(k, val)
}
} }
return &Validate{config: config} return v
} }
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key // RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
// NOTE: if the key already exists, the previous validation function will be replaced. // NOTE: if the key already exists, the previous validation function will be replaced.
// NOTE: this method is not thread-safe // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterValidation(key string, f Func) error { func (v *Validate) RegisterValidation(key string, f Func) error {
v.initCheck()
if len(key) == 0 { if len(key) == 0 {
return errors.New("Function Key cannot be empty") return errors.New("Function Key cannot be empty")
@ -155,55 +188,87 @@ func (v *Validate) RegisterValidation(key string, f Func) error {
return errors.New("Function cannot be empty") return errors.New("Function cannot be empty")
} }
v.config.ValidationFuncs[key] = f _, ok := restrictedTags[key]
if ok || strings.ContainsAny(key, restrictedTagChars) {
panic(fmt.Sprintf(restrictedTagErr, key))
}
v.validationFuncs[key] = f
return nil return nil
} }
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) {
v.initCheck()
if v.config.CustomTypeFuncs == nil { if v.customTypeFuncs == nil {
v.config.CustomTypeFuncs = map[reflect.Type]CustomTypeFunc{} v.customTypeFuncs = map[reflect.Type]CustomTypeFunc{}
} }
for _, t := range types { for _, t := range types {
v.config.CustomTypeFuncs[reflect.TypeOf(t)] = fn v.customTypeFuncs[reflect.TypeOf(t)] = fn
} }
v.config.hasCustomFuncs = true v.hasCustomFuncs = true
} }
// Field validates a single field using tag style validation and returns ValidationErrors // RegisterAliasValidation registers a mapping of a single validationstag that
// defines a common or complex set of validation(s) to simplify adding validation
// to structs. NOTE: when returning an error the tag returned in FieldError will be
// the alias tag unless the dive tag is part of the alias; everything after the
// dive tag is not reported as the alias tag. Also the ActualTag in the before case
// will be the actual tag within the alias that failed.
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterAliasValidation(alias, tags string) {
v.initCheck()
_, ok := restrictedTags[alias]
if ok || strings.ContainsAny(alias, restrictedTagChars) {
panic(fmt.Sprintf(restrictedAliasErr, alias))
}
v.aliasValidators[alias] = tags
v.hasAliasValidators = true
}
// Field validates a single field using tag style validation and returns nil or ValidationErrors as type error.
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also // NOTE: it returns ValidationErrors instead of a single FieldError because this can also
// validate Array, Slice and maps fields which may contain more than one error // validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) Field(field interface{}, tag string) ValidationErrors { func (v *Validate) Field(field interface{}, tag string) error {
v.initCheck()
errs := errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
fieldVal := reflect.ValueOf(field) fieldVal := reflect.ValueOf(field)
v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "", false, false, nil) v.traverseField(fieldVal, fieldVal, fieldVal, blank, errs, false, tag, blank, false, false, nil)
if len(errs) == 0 { if len(errs) == 0 {
errsPool.Put(errs) v.errsPool.Put(errs)
return nil return nil
} }
return errs return errs
} }
// FieldWithValue validates a single field, against another fields value using tag style validation and returns ValidationErrors // FieldWithValue validates a single field, against another fields value using tag style validation and returns nil or ValidationErrors.
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also // NOTE: it returns ValidationErrors instead of a single FieldError because this can also
// validate Array, Slice and maps fields which may contain more than one error // validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) ValidationErrors { func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error {
v.initCheck()
errs := errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
topVal := reflect.ValueOf(val) topVal := reflect.ValueOf(val)
v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "", false, false, nil) v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, errs, false, tag, blank, false, false, nil)
if len(errs) == 0 { if len(errs) == 0 {
errsPool.Put(errs) v.errsPool.Put(errs)
return nil return nil
} }
@ -212,10 +277,10 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string
// StructPartial validates the fields passed in only, ignoring all others. // StructPartial validates the fields passed in only, ignoring all others.
// Fields may be provided in a namespaced fashion relative to the struct provided // Fields may be provided in a namespaced fashion relative to the struct provided
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name // i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
// NOTE: This is normally not needed, however in some specific cases such as: tied to a // You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
// legacy data structure, it will be useful func (v *Validate) StructPartial(current interface{}, fields ...string) error {
func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { v.initCheck()
sv, _ := v.extractType(reflect.ValueOf(current)) sv, _ := v.extractType(reflect.ValueOf(current))
name := sv.Type().Name() name := sv.Type().Name()
@ -256,12 +321,12 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati
} }
} }
errs := errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, false, m) v.tranverseStruct(sv, sv, sv, blank, errs, true, len(m) != 0, false, m)
if len(errs) == 0 { if len(errs) == 0 {
errsPool.Put(errs) v.errsPool.Put(errs)
return nil return nil
} }
@ -270,10 +335,10 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati
// StructExcept validates all fields except the ones passed in. // StructExcept validates all fields except the ones passed in.
// Fields may be provided in a namespaced fashion relative to the struct provided // Fields may be provided in a namespaced fashion relative to the struct provided
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name // i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
// NOTE: This is normally not needed, however in some specific cases such as: tied to a // You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
// legacy data structure, it will be useful func (v *Validate) StructExcept(current interface{}, fields ...string) error {
func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors { v.initCheck()
sv, _ := v.extractType(reflect.ValueOf(current)) sv, _ := v.extractType(reflect.ValueOf(current))
name := sv.Type().Name() name := sv.Type().Name()
@ -283,12 +348,12 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) Validatio
m[name+"."+key] = emptyStructPtr m[name+"."+key] = emptyStructPtr
} }
errs := errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, true, m) v.tranverseStruct(sv, sv, sv, blank, errs, true, len(m) != 0, true, m)
if len(errs) == 0 { if len(errs) == 0 {
errsPool.Put(errs) v.errsPool.Put(errs)
return nil return nil
} }
@ -296,15 +361,18 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) Validatio
} }
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
func (v *Validate) Struct(current interface{}) ValidationErrors { // it returns nil or ValidationErrors as error.
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
func (v *Validate) Struct(current interface{}) error {
v.initCheck()
errs := errsPool.Get().(ValidationErrors) errs := v.errsPool.Get().(ValidationErrors)
sv := reflect.ValueOf(current) sv := reflect.ValueOf(current)
v.tranverseStruct(sv, sv, sv, "", errs, true, false, false, nil) v.tranverseStruct(sv, sv, sv, blank, errs, true, false, false, nil)
if len(errs) == 0 { if len(errs) == 0 {
errsPool.Put(errs) v.errsPool.Put(errs)
return nil return nil
} }
@ -349,7 +417,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
} }
} }
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name, partial, exclude, includeExclude) v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.tagName), fld.Name, partial, exclude, includeExclude)
} }
} }
@ -360,46 +428,48 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
return return
} }
cTag, isCached := v.tagsCache.Get(tag)
if !isCached {
cTag = v.parseTags(tag, name)
v.tagsCache.Set(tag, cTag)
}
current, kind := v.extractType(current) current, kind := v.extractType(current)
var typ reflect.Type var typ reflect.Type
switch kind { switch kind {
case reflect.Ptr, reflect.Interface, reflect.Invalid: case reflect.Ptr, reflect.Interface, reflect.Invalid:
if strings.Contains(tag, omitempty) { if cTag.isOmitEmpty {
return return
} }
if len(tag) > 0 { if len(tag) > 0 {
tags := strings.Split(tag, tagSeparator)
var param string
vals := strings.SplitN(tags[0], tagKeySeparator, 2)
if len(vals) > 1 {
param = vals[1]
}
if kind == reflect.Invalid { if kind == reflect.Invalid {
errs[errPrefix+name] = &FieldError{ errs[errPrefix+name] = &FieldError{
Field: name, Field: name,
Tag: vals[0], Tag: cTag.tags[0].tag,
Param: param, ActualTag: cTag.tags[0].tagVals[0][0],
Kind: kind, Param: cTag.tags[0].tagVals[0][1],
Kind: kind,
} }
return return
} }
errs[errPrefix+name] = &FieldError{ errs[errPrefix+name] = &FieldError{
Field: name, Field: name,
Tag: vals[0], Tag: cTag.tags[0].tag,
Param: param, ActualTag: cTag.tags[0].tagVals[0][0],
Value: current.Interface(), Param: cTag.tags[0].tagVals[0][1],
Kind: kind, Value: current.Interface(),
Type: current.Type(), Kind: kind,
Type: current.Type(),
} }
return return
} }
// if we get here tag length is zero and we can leave // if we get here tag length is zero and we can leave
if kind == reflect.Invalid { if kind == reflect.Invalid {
return return
@ -427,69 +497,30 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
typ = current.Type() typ = current.Type()
tags, isCached := tagsCache.Get(tag)
if !isCached {
tags = []*tagCache{}
for _, t := range strings.Split(tag, tagSeparator) {
if t == diveTag {
tags = append(tags, &tagCache{tagVals: [][]string{{t}}})
break
}
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator)
cTag := &tagCache{isOrVal: len(orVals) > 1, tagVals: make([][]string, len(orVals))}
tags = append(tags, cTag)
var key string
var param string
for i, val := range orVals {
vals := strings.SplitN(val, tagKeySeparator, 2)
key = vals[0]
if len(key) == 0 {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, name)))
}
if len(vals) > 1 {
param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
}
cTag.tagVals[i] = []string{key, param}
}
}
tagsCache.Set(tag, tags)
}
var dive bool var dive bool
var diveSubTag string var diveSubTag string
for _, cTag := range tags { for _, valTag := range cTag.tags {
if cTag.tagVals[0][0] == existsTag { if valTag.tagVals[0][0] == existsTag {
continue continue
} }
if cTag.tagVals[0][0] == diveTag { if valTag.tagVals[0][0] == diveTag {
dive = true dive = true
diveSubTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",") diveSubTag = strings.TrimLeft(strings.SplitN(cTag.diveTag, diveTag, 2)[1], ",")
break break
} }
if cTag.tagVals[0][0] == omitempty { if valTag.tagVals[0][0] == omitempty {
if !hasValue(v, topStruct, currentStruct, current, typ, kind, "") { if !hasValue(v, topStruct, currentStruct, current, typ, kind, blank) {
return return
} }
continue continue
} }
if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, errs, cTag, name) { if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, errs, valTag, name) {
return return
} }
} }
@ -527,18 +558,18 @@ func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Va
} }
// validateField validates a field based on the provided tag's key and param values and returns true if there is an error or false if all ok // validateField validates a field based on the provided tag's key and param values and returns true if there is an error or false if all ok
func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, errs ValidationErrors, cTag *tagCache, name string) bool { func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, errs ValidationErrors, valTag *tagVals, name string) bool {
var valFunc Func var valFunc Func
var ok bool var ok bool
if cTag.isOrVal { if valTag.isOrVal {
errTag := "" errTag := blank
for _, val := range cTag.tagVals { for _, val := range valTag.tagVals {
valFunc, ok = v.config.ValidationFuncs[val[0]] valFunc, ok = v.validationFuncs[val[0]]
if !ok { if !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
} }
@ -550,33 +581,46 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.
errTag += orSeparator + val[0] errTag += orSeparator + val[0]
} }
errs[errPrefix+name] = &FieldError{ if valTag.isAlias {
Field: name, errs[errPrefix+name] = &FieldError{
Tag: errTag[1:], Field: name,
Value: current.Interface(), Tag: valTag.tag,
Type: currentType, ActualTag: errTag[1:],
Kind: currentKind, Value: current.Interface(),
Type: currentType,
Kind: currentKind,
}
} else {
errs[errPrefix+name] = &FieldError{
Field: name,
Tag: errTag[1:],
ActualTag: errTag[1:],
Value: current.Interface(),
Type: currentType,
Kind: currentKind,
}
} }
return true return true
} }
valFunc, ok = v.config.ValidationFuncs[cTag.tagVals[0][0]] valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]]
if !ok { if !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
} }
if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, cTag.tagVals[0][1]) { if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, valTag.tagVals[0][1]) {
return false return false
} }
errs[errPrefix+name] = &FieldError{ errs[errPrefix+name] = &FieldError{
Field: name, Field: name,
Tag: cTag.tagVals[0][0], Tag: valTag.tag,
Value: current.Interface(), ActualTag: valTag.tagVals[0][0],
Param: cTag.tagVals[0][1], Value: current.Interface(),
Type: currentType, Param: valTag.tagVals[0][1],
Kind: currentKind, Type: currentType,
Kind: currentKind,
} }
return true return true

@ -111,9 +111,11 @@ type TestSlice struct {
OmitEmpty []int `validate:"omitempty,min=1,max=10"` OmitEmpty []int `validate:"omitempty,min=1,max=10"`
} }
var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators}) var validate = New(&Config{TagName: "validate"})
func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) { func AssertError(t *testing.T, err error, key, field, expectedTag string) {
errs := err.(ValidationErrors)
val, ok := errs[key] val, ok := errs[key]
EqualSkip(t, 2, ok, true) EqualSkip(t, 2, ok, true)
@ -210,6 +212,73 @@ type TestPartial struct {
} }
} }
func TestAliasTags(t *testing.T) {
validate.RegisterAliasValidation("iscolor", "hexcolor|rgb|rgba|hsl|hsla")
s := "rgb(255,255,255)"
errs := validate.Field(s, "iscolor")
Equal(t, errs, nil)
s = ""
errs = validate.Field(s, "omitempty,iscolor")
Equal(t, errs, nil)
s = "rgb(255,255,0)"
errs = validate.Field(s, "iscolor,len=5")
NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "len")
type Test struct {
Color string `validate:"iscolor"`
}
tst := &Test{
Color: "#000",
}
errs = validate.Struct(tst)
Equal(t, errs, nil)
tst.Color = "cfvre"
errs = validate.Struct(tst)
NotEqual(t, errs, nil)
AssertError(t, errs, "Test.Color", "Color", "iscolor")
Equal(t, errs.(ValidationErrors)["Test.Color"].ActualTag, "hexcolor|rgb|rgba|hsl|hsla")
validate.RegisterAliasValidation("req", "required,dive,iscolor")
arr := []string{"val1", "#fff", "#000"}
errs = validate.Field(arr, "req")
NotEqual(t, errs, nil)
AssertError(t, errs, "[0]", "[0]", "iscolor")
PanicMatches(t, func() { validate.RegisterAliasValidation("exists", "gt=5,lt=10") }, "Alias \"exists\" either contains restricted characters or is the same as a restricted tag needed for normal operation")
}
func TestNilValidator(t *testing.T) {
type TestStruct struct {
Test string `validate:"required"`
}
ts := TestStruct{}
var val *Validate
fn := func(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
return current.String() == field.String()
}
PanicMatches(t, func() { val.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) }, validatorNotInitialized)
PanicMatches(t, func() { val.RegisterValidation("something", fn) }, validatorNotInitialized)
PanicMatches(t, func() { val.Field(ts.Test, "required") }, validatorNotInitialized)
PanicMatches(t, func() { val.FieldWithValue("test", ts.Test, "required") }, validatorNotInitialized)
PanicMatches(t, func() { val.Struct(ts) }, validatorNotInitialized)
PanicMatches(t, func() { val.StructExcept(ts, "Test") }, validatorNotInitialized)
PanicMatches(t, func() { val.StructPartial(ts, "Test") }, validatorNotInitialized)
}
func TestStructPartial(t *testing.T) { func TestStructPartial(t *testing.T) {
p1 := []string{ p1 := []string{
@ -354,12 +423,12 @@ func TestStructPartial(t *testing.T) {
// these will fail as unset item IS tested // these will fail as unset item IS tested
errs = validate.StructExcept(tPartial, p1...) errs = validate.StructExcept(tPartial, p1...)
AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required")
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
errs = validate.StructPartial(tPartial, p2...) errs = validate.StructPartial(tPartial, p2...)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required")
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
// Unset second slice member concurrently to test dive behavior: // Unset second slice member concurrently to test dive behavior:
tPartial.SubSlice[1].Test = "" tPartial.SubSlice[1].Test = ""
@ -374,13 +443,13 @@ func TestStructPartial(t *testing.T) {
AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required")
errs = validate.StructExcept(tPartial, p1...) errs = validate.StructExcept(tPartial, p1...)
Equal(t, len(errs), 2) Equal(t, len(errs.(ValidationErrors)), 2)
AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required")
AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required")
errs = validate.StructPartial(tPartial, p2...) errs = validate.StructPartial(tPartial, p2...)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required")
// reset struct in slice, and unset struct in slice in unset posistion // reset struct in slice, and unset struct in slice in unset posistion
@ -396,7 +465,7 @@ func TestStructPartial(t *testing.T) {
// testing for missing item by exception, yes it dives and fails // testing for missing item by exception, yes it dives and fails
errs = validate.StructExcept(tPartial, p1...) errs = validate.StructExcept(tPartial, p1...)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required")
errs = validate.StructExcept(tPartial, p2...) errs = validate.StructExcept(tPartial, p2...)
@ -1210,9 +1279,8 @@ func TestExistsValidation(t *testing.T) {
func TestSQLValue2Validation(t *testing.T) { func TestSQLValue2Validation(t *testing.T) {
config := Config{ config := &Config{
TagName: "validate", TagName: "validate",
ValidationFuncs: BakedInValidators,
} }
validate := New(config) validate := New(config)
@ -1261,20 +1329,23 @@ func TestSQLValue2Validation(t *testing.T) {
errs = validate.Struct(c) errs = validate.Struct(c)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 2) Equal(t, len(errs.(ValidationErrors)), 2)
AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required") AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required")
AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt") AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt")
} }
func TestSQLValueValidation(t *testing.T) { func TestSQLValueValidation(t *testing.T) {
customTypes := map[reflect.Type]CustomTypeFunc{} // customTypes := map[reflect.Type]CustomTypeFunc{}
customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType // customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType
customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType // customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType
customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason // customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason
validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) validate := New(&Config{TagName: "validate"})
validate.RegisterCustomTypeFunc(ValidateValuerType, (*driver.Valuer)(nil), valuer{})
validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{})
validate.RegisterCustomTypeFunc(OverrideIntTypeForSomeReason, 1)
val := valuer{ val := valuer{
Name: "", Name: "",
@ -1317,7 +1388,7 @@ func TestSQLValueValidation(t *testing.T) {
errs = validate.Struct(c) errs = validate.Struct(c)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 2) Equal(t, len(errs.(ValidationErrors)), 2)
AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required") AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required")
AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt") AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt")
} }
@ -1348,7 +1419,7 @@ func TestMACValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d mac failed Error: %s", i, errs) t.Fatalf("Index: %d mac failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "mac" { if val.Tag != "mac" {
t.Fatalf("Index: %d mac failed Error: %s", i, errs) t.Fatalf("Index: %d mac failed Error: %s", i, errs)
} }
@ -1386,7 +1457,7 @@ func TestIPValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ip failed Error: %s", i, errs) t.Fatalf("Index: %d ip failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "ip" { if val.Tag != "ip" {
t.Fatalf("Index: %d ip failed Error: %s", i, errs) t.Fatalf("Index: %d ip failed Error: %s", i, errs)
} }
@ -1424,7 +1495,7 @@ func TestIPv6Validation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "ipv6" { if val.Tag != "ipv6" {
t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs)
} }
@ -1462,7 +1533,7 @@ func TestIPv4Validation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "ipv4" { if val.Tag != "ipv4" {
t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs)
} }
@ -1619,7 +1690,7 @@ func TestInterfaceErrValidation(t *testing.T) {
errs = validate.Struct(s) errs = validate.Struct(s)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "ExternalCMD.Data", "Data", "required") AssertError(t, errs, "ExternalCMD.Data", "Data", "required")
type ExternalCMD2 struct { type ExternalCMD2 struct {
@ -1636,7 +1707,7 @@ func TestInterfaceErrValidation(t *testing.T) {
errs = validate.Struct(s2) errs = validate.Struct(s2)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "ExternalCMD2.Data", "Data", "len") AssertError(t, errs, "ExternalCMD2.Data", "Data", "len")
s3 := &ExternalCMD2{ s3 := &ExternalCMD2{
@ -1647,7 +1718,7 @@ func TestInterfaceErrValidation(t *testing.T) {
errs = validate.Struct(s3) errs = validate.Struct(s3)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "ExternalCMD2.Data", "Data", "len") AssertError(t, errs, "ExternalCMD2.Data", "Data", "len")
type Inner struct { type Inner struct {
@ -1666,7 +1737,7 @@ func TestInterfaceErrValidation(t *testing.T) {
errs = validate.Struct(s4) errs = validate.Struct(s4)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "ExternalCMD.Data.Name", "Name", "required") AssertError(t, errs, "ExternalCMD.Data.Name", "Name", "required")
type TestMapStructPtr struct { type TestMapStructPtr struct {
@ -1681,7 +1752,7 @@ func TestInterfaceErrValidation(t *testing.T) {
errs = validate.Struct(msp) errs = validate.Struct(msp)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "len") AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "len")
type TestMultiDimensionalStructs struct { type TestMultiDimensionalStructs struct {
@ -1699,7 +1770,7 @@ func TestInterfaceErrValidation(t *testing.T) {
errs = validate.Struct(tms) errs = validate.Struct(tms)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 4) Equal(t, len(errs.(ValidationErrors)), 4)
AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][1].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][2].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][1].Name", "Name", "required")
@ -1721,7 +1792,7 @@ func TestInterfaceErrValidation(t *testing.T) {
errs = validate.Struct(tmsp2) errs = validate.Struct(tmsp2)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 6) Equal(t, len(errs.(ValidationErrors)), 6)
AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "Name", "required")
@ -1733,24 +1804,24 @@ func TestInterfaceErrValidation(t *testing.T) {
errs = validate.Field(m, "len=3,dive,len=2") errs = validate.Field(m, "len=3,dive,len=2")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "[3]", "[3]", "len") AssertError(t, errs, "[3]", "[3]", "len")
errs = validate.Field(m, "len=2,dive,required") errs = validate.Field(m, "len=2,dive,required")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "", "", "len") AssertError(t, errs, "", "", "len")
arr := []interface{}{"ok", "", "ok"} arr := []interface{}{"ok", "", "ok"}
errs = validate.Field(arr, "len=3,dive,len=2") errs = validate.Field(arr, "len=3,dive,len=2")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "[1]", "[1]", "len") AssertError(t, errs, "[1]", "[1]", "len")
errs = validate.Field(arr, "len=2,dive,required") errs = validate.Field(arr, "len=2,dive,required")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "", "", "len") AssertError(t, errs, "", "", "len")
type MyStruct struct { type MyStruct struct {
@ -1777,12 +1848,12 @@ func TestMapDiveValidation(t *testing.T) {
errs = validate.Field(m, "len=3,dive,required") errs = validate.Field(m, "len=3,dive,required")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "[3]", "[3]", "required") AssertError(t, errs, "[3]", "[3]", "required")
errs = validate.Field(m, "len=2,dive,required") errs = validate.Field(m, "len=2,dive,required")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "", "", "len") AssertError(t, errs, "", "", "len")
type Inner struct { type Inner struct {
@ -1801,11 +1872,12 @@ func TestMapDiveValidation(t *testing.T) {
errs = validate.Struct(ms) errs = validate.Struct(ms)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "TestMapStruct.Errs[3].Name", "Name", "required") AssertError(t, errs, "TestMapStruct.Errs[3].Name", "Name", "required")
// for full test coverage // for full test coverage
fmt.Sprint(errs.Error()) s := fmt.Sprint(errs.Error())
NotEqual(t, s, "")
type TestMapTimeStruct struct { type TestMapTimeStruct struct {
Errs map[int]*time.Time `validate:"gt=0,dive,required"` Errs map[int]*time.Time `validate:"gt=0,dive,required"`
@ -1821,7 +1893,7 @@ func TestMapDiveValidation(t *testing.T) {
errs = validate.Struct(mt) errs = validate.Struct(mt)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 2) Equal(t, len(errs.(ValidationErrors)), 2)
AssertError(t, errs, "TestMapTimeStruct.Errs[3]", "Errs[3]", "required") AssertError(t, errs, "TestMapTimeStruct.Errs[3]", "Errs[3]", "required")
AssertError(t, errs, "TestMapTimeStruct.Errs[4]", "Errs[4]", "required") AssertError(t, errs, "TestMapTimeStruct.Errs[4]", "Errs[4]", "required")
@ -1837,7 +1909,7 @@ func TestMapDiveValidation(t *testing.T) {
errs = validate.Struct(msp) errs = validate.Struct(msp)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "required") AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "required")
type TestMapStructPtr2 struct { type TestMapStructPtr2 struct {
@ -1860,12 +1932,12 @@ func TestArrayDiveValidation(t *testing.T) {
errs := validate.Field(arr, "len=3,dive,required") errs := validate.Field(arr, "len=3,dive,required")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "[1]", "[1]", "required") AssertError(t, errs, "[1]", "[1]", "required")
errs = validate.Field(arr, "len=2,dive,required") errs = validate.Field(arr, "len=2,dive,required")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "", "", "len") AssertError(t, errs, "", "", "len")
type BadDive struct { type BadDive struct {
@ -1888,7 +1960,7 @@ func TestArrayDiveValidation(t *testing.T) {
errs = validate.Struct(test) errs = validate.Struct(test)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "Test.Errs[1]", "Errs[1]", "required") AssertError(t, errs, "Test.Errs[1]", "Errs[1]", "required")
test = &Test{ test = &Test{
@ -1897,7 +1969,7 @@ func TestArrayDiveValidation(t *testing.T) {
errs = validate.Struct(test) errs = validate.Struct(test)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 1) Equal(t, len(errs.(ValidationErrors)), 1)
AssertError(t, errs, "Test.Errs[2]", "Errs[2]", "required") AssertError(t, errs, "Test.Errs[2]", "Errs[2]", "required")
type TestMultiDimensional struct { type TestMultiDimensional struct {
@ -1915,7 +1987,7 @@ func TestArrayDiveValidation(t *testing.T) {
errs = validate.Struct(tm) errs = validate.Struct(tm)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 4) Equal(t, len(errs.(ValidationErrors)), 4)
AssertError(t, errs, "TestMultiDimensional.Errs[0][1]", "Errs[0][1]", "required") AssertError(t, errs, "TestMultiDimensional.Errs[0][1]", "Errs[0][1]", "required")
AssertError(t, errs, "TestMultiDimensional.Errs[0][2]", "Errs[0][2]", "required") AssertError(t, errs, "TestMultiDimensional.Errs[0][2]", "Errs[0][2]", "required")
AssertError(t, errs, "TestMultiDimensional.Errs[1][1]", "Errs[1][1]", "required") AssertError(t, errs, "TestMultiDimensional.Errs[1][1]", "Errs[1][1]", "required")
@ -1940,7 +2012,7 @@ func TestArrayDiveValidation(t *testing.T) {
errs = validate.Struct(tms) errs = validate.Struct(tms)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 4) Equal(t, len(errs.(ValidationErrors)), 4)
AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][1].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][2].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][1].Name", "Name", "required")
@ -1962,14 +2034,16 @@ func TestArrayDiveValidation(t *testing.T) {
errs = validate.Struct(tmsp) errs = validate.Struct(tmsp)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 5) Equal(t, len(errs.(ValidationErrors)), 5)
AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[0][1].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[0][2].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[1][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[1][1].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[1][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[1][2].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[2][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[2][1].Name", "Name", "required")
// for full test coverage // for full test coverage
fmt.Sprint(errs.Error()) s := fmt.Sprint(errs.Error())
NotEqual(t, s, "")
type TestMultiDimensionalStructsPtr2 struct { type TestMultiDimensionalStructsPtr2 struct {
Errs [][]*Inner `validate:"gt=0,dive,dive,required"` Errs [][]*Inner `validate:"gt=0,dive,dive,required"`
@ -1987,7 +2061,7 @@ func TestArrayDiveValidation(t *testing.T) {
errs = validate.Struct(tmsp2) errs = validate.Struct(tmsp2)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 6) Equal(t, len(errs.(ValidationErrors)), 6)
AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "Name", "required")
@ -2011,7 +2085,7 @@ func TestArrayDiveValidation(t *testing.T) {
errs = validate.Struct(tmsp3) errs = validate.Struct(tmsp3)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 5) Equal(t, len(errs.(ValidationErrors)), 5)
AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[0][1].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[0][2].Name", "Name", "required")
AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[1][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[1][1].Name", "Name", "required")
@ -2038,7 +2112,7 @@ func TestArrayDiveValidation(t *testing.T) {
errs = validate.Struct(tmtp3) errs = validate.Struct(tmtp3)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 3) Equal(t, len(errs.(ValidationErrors)), 3)
AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[1][2]", "Errs[1][2]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[1][2]", "Errs[1][2]", "required")
AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[2][1]", "Errs[2][1]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[2][1]", "Errs[2][1]", "required")
AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[2][2]", "Errs[2][2]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[2][2]", "Errs[2][2]", "required")
@ -2063,7 +2137,7 @@ func TestArrayDiveValidation(t *testing.T) {
errs = validate.Struct(tmtp) errs = validate.Struct(tmtp)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 3) Equal(t, len(errs.(ValidationErrors)), 3)
AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[1][2]", "Errs[1][2]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[1][2]", "Errs[1][2]", "required")
AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[2][1]", "Errs[2][1]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[2][1]", "Errs[2][1]", "required")
AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[2][2]", "Errs[2][2]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[2][2]", "Errs[2][2]", "required")
@ -2186,7 +2260,7 @@ func TestSSNValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d SSN failed Error: %s", i, errs) t.Fatalf("Index: %d SSN failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "ssn" { if val.Tag != "ssn" {
t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) t.Fatalf("Index: %d Latitude failed Error: %s", i, errs)
} }
@ -2220,7 +2294,7 @@ func TestLongitudeValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) t.Fatalf("Index: %d Longitude failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "longitude" { if val.Tag != "longitude" {
t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) t.Fatalf("Index: %d Latitude failed Error: %s", i, errs)
} }
@ -2254,7 +2328,7 @@ func TestLatitudeValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) t.Fatalf("Index: %d Latitude failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "latitude" { if val.Tag != "latitude" {
t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) t.Fatalf("Index: %d Latitude failed Error: %s", i, errs)
} }
@ -2294,7 +2368,7 @@ func TestDataURIValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) t.Fatalf("Index: %d DataURI failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "datauri" { if val.Tag != "datauri" {
t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) t.Fatalf("Index: %d DataURI failed Error: %s", i, errs)
} }
@ -2332,7 +2406,7 @@ func TestMultibyteValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "multibyte" { if val.Tag != "multibyte" {
t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs)
} }
@ -2371,7 +2445,7 @@ func TestPrintableASCIIValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "printascii" { if val.Tag != "printascii" {
t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs)
} }
@ -2409,7 +2483,7 @@ func TestASCIIValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) t.Fatalf("Index: %d ASCII failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "ascii" { if val.Tag != "ascii" {
t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) t.Fatalf("Index: %d ASCII failed Error: %s", i, errs)
} }
@ -2444,7 +2518,7 @@ func TestUUID5Validation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "uuid5" { if val.Tag != "uuid5" {
t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs)
} }
@ -2478,7 +2552,7 @@ func TestUUID4Validation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "uuid4" { if val.Tag != "uuid4" {
t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs)
} }
@ -2511,7 +2585,7 @@ func TestUUID3Validation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "uuid3" { if val.Tag != "uuid3" {
t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs)
} }
@ -2547,7 +2621,7 @@ func TestUUIDValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d UUID failed Error: %s", i, errs) t.Fatalf("Index: %d UUID failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "uuid" { if val.Tag != "uuid" {
t.Fatalf("Index: %d UUID failed Error: %s", i, errs) t.Fatalf("Index: %d UUID failed Error: %s", i, errs)
} }
@ -2585,7 +2659,7 @@ func TestISBNValidation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "isbn" { if val.Tag != "isbn" {
t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN failed Error: %s", i, errs)
} }
@ -2622,7 +2696,7 @@ func TestISBN13Validation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "isbn13" { if val.Tag != "isbn13" {
t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs)
} }
@ -2660,7 +2734,7 @@ func TestISBN10Validation(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "isbn10" { if val.Tag != "isbn10" {
t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs)
} }
@ -3825,9 +3899,8 @@ func TestAddFunctions(t *testing.T) {
return true return true
} }
config := Config{ config := &Config{
TagName: "validateme", TagName: "validateme",
ValidationFuncs: BakedInValidators,
} }
validate := New(config) validate := New(config)
@ -3843,13 +3916,14 @@ func TestAddFunctions(t *testing.T) {
errs = validate.RegisterValidation("new", fn) errs = validate.RegisterValidation("new", fn)
Equal(t, errs, nil) Equal(t, errs, nil)
PanicMatches(t, func() { validate.RegisterValidation("dive", fn) }, "Tag \"dive\" either contains restricted characters or is the same as a restricted tag needed for normal operation")
} }
func TestChangeTag(t *testing.T) { func TestChangeTag(t *testing.T) {
config := Config{ config := &Config{
TagName: "val", TagName: "val",
ValidationFuncs: BakedInValidators,
} }
validate := New(config) validate := New(config)
@ -4129,7 +4203,7 @@ func TestUrl(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d URL failed Error: %s", i, errs) t.Fatalf("Index: %d URL failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "url" { if val.Tag != "url" {
t.Fatalf("Index: %d URL failed Error: %s", i, errs) t.Fatalf("Index: %d URL failed Error: %s", i, errs)
} }
@ -4193,7 +4267,7 @@ func TestUri(t *testing.T) {
if IsEqual(errs, nil) { if IsEqual(errs, nil) {
t.Fatalf("Index: %d URI failed Error: %s", i, errs) t.Fatalf("Index: %d URI failed Error: %s", i, errs)
} else { } else {
val := errs[""] val := errs.(ValidationErrors)[""]
if val.Tag != "uri" { if val.Tag != "uri" {
t.Fatalf("Index: %d URI failed Error: %s", i, errs) t.Fatalf("Index: %d URI failed Error: %s", i, errs)
} }
@ -4659,7 +4733,7 @@ func TestStructStringValidation(t *testing.T) {
// Assert Top Level // Assert Top Level
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 13) Equal(t, len(errs.(ValidationErrors)), 13)
// Assert Fields // Assert Fields
AssertError(t, errs, "TestString.Required", "Required", "required") AssertError(t, errs, "TestString.Required", "Required", "required")
@ -4714,7 +4788,7 @@ func TestStructInt32Validation(t *testing.T) {
// Assert Top Level // Assert Top Level
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 10) Equal(t, len(errs.(ValidationErrors)), 10)
// Assert Fields // Assert Fields
AssertError(t, errs, "TestInt32.Required", "Required", "required") AssertError(t, errs, "TestInt32.Required", "Required", "required")
@ -4756,7 +4830,7 @@ func TestStructUint64Validation(t *testing.T) {
// Assert Top Level // Assert Top Level
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 6) Equal(t, len(errs.(ValidationErrors)), 6)
// Assert Fields // Assert Fields
AssertError(t, errs, "TestUint64.Required", "Required", "required") AssertError(t, errs, "TestUint64.Required", "Required", "required")
@ -4794,7 +4868,7 @@ func TestStructFloat64Validation(t *testing.T) {
// Assert Top Level // Assert Top Level
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 6) Equal(t, len(errs.(ValidationErrors)), 6)
// Assert Fields // Assert Fields
AssertError(t, errs, "TestFloat64.Required", "Required", "required") AssertError(t, errs, "TestFloat64.Required", "Required", "required")
@ -4830,7 +4904,7 @@ func TestStructSliceValidation(t *testing.T) {
errs = validate.Struct(tFail) errs = validate.Struct(tFail)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs), 6) Equal(t, len(errs.(ValidationErrors)), 6)
// Assert Field Errors // Assert Field Errors
AssertError(t, errs, "TestSlice.Required", "Required", "required") AssertError(t, errs, "TestSlice.Required", "Required", "required")

Loading…
Cancel
Save