diff --git a/README.md b/README.md index 87aff6ebc..a31698297 100644 --- a/README.md +++ b/README.md @@ -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. - 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) +- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs Installation ------------ Use go get. - go get gopkg.in/bluesuncorp/validator.v7 + go get gopkg.in/bluesuncorp/validator.v8 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. - 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 ------ -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: @@ -44,7 +64,7 @@ package main import ( "fmt" - "gopkg.in/bluesuncorp/validator.v7" + "gopkg.in/bluesuncorp/validator.v8" ) // User contains user information @@ -69,10 +89,7 @@ var validate *validator.Validate func main() { - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := &validator.Config{TagName: "validate"} validate = validator.New(config) @@ -98,13 +115,13 @@ func validateStruct() { } // returns nil or ValidationErrors ( map[string]*FieldError ) - errs := validate.Struct(user) + err := validate.Struct(user) if errs != nil { 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 - err := errs["User.Addresses[0].City"] + err := errs.(validator.ValidationErrors)["User.Addresses[0].City"] fmt.Println(err.Field) // output: City fmt.Println(err.Tag) // output: required fmt.Println(err.Kind) // output: string @@ -143,7 +160,7 @@ import ( "fmt" "reflect" - "gopkg.in/bluesuncorp/validator.v7" + "gopkg.in/bluesuncorp/validator.v8" ) // DbBackedUser User struct @@ -154,10 +171,7 @@ type DbBackedUser struct { func main() { - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := &validator.Config{TagName: "validate"} 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}} errs := validate.Struct(x) - if len(errs) > 0 { + if len(errs.(validator.ValidationErrors)) > 0 { fmt.Printf("Errs:\n%+v\n", errs) } } @@ -191,32 +205,32 @@ Benchmarks ```go $ go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkFieldSuccess-4 5000000 285 ns/op 16 B/op 1 allocs/op -BenchmarkFieldFailure-4 5000000 284 ns/op 16 B/op 1 allocs/op -BenchmarkFieldDiveSuccess-4 500000 2501 ns/op 384 B/op 19 allocs/op -BenchmarkFieldDiveFailure-4 500000 3022 ns/op 752 B/op 23 allocs/op -BenchmarkFieldCustomTypeSuccess-4 3000000 445 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 2000000 788 ns/op 416 B/op 6 allocs/op -BenchmarkFieldOrTagSuccess-4 1000000 1377 ns/op 32 B/op 2 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1201 ns/op 400 B/op 6 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1257 ns/op 80 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1776 ns/op 608 B/op 13 allocs/op -BenchmarkStructPartialSuccess-4 1000000 1354 ns/op 400 B/op 11 allocs/op -BenchmarkStructPartialFailure-4 1000000 1813 ns/op 784 B/op 16 allocs/op -BenchmarkStructExceptSuccess-4 2000000 916 ns/op 368 B/op 9 allocs/op -BenchmarkStructExceptFailure-4 1000000 1369 ns/op 400 B/op 11 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1033 ns/op 128 B/op 6 allocs/op -BenchmarkStructSimpleCrossFieldFailure-4 1000000 1569 ns/op 528 B/op 11 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1371 ns/op 160 B/op 8 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 1935 ns/op 560 B/op 13 allocs/op +BenchmarkFieldSuccess-4 5000000 296 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 294 ns/op 16 B/op 1 allocs/op +BenchmarkFieldDiveSuccess-4 500000 2529 ns/op 384 B/op 19 allocs/op +BenchmarkFieldDiveFailure-4 500000 3056 ns/op 768 B/op 23 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 443 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 753 ns/op 384 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-4 1000000 1334 ns/op 32 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1172 ns/op 416 B/op 6 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1206 ns/op 80 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1737 ns/op 592 B/op 11 allocs/op +BenchmarkStructPartialSuccess-4 1000000 1367 ns/op 400 B/op 11 allocs/op +BenchmarkStructPartialFailure-4 1000000 1914 ns/op 800 B/op 16 allocs/op +BenchmarkStructExceptSuccess-4 2000000 909 ns/op 368 B/op 9 allocs/op +BenchmarkStructExceptFailure-4 1000000 1350 ns/op 400 B/op 11 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1218 ns/op 128 B/op 6 allocs/op +BenchmarkStructSimpleCrossFieldFailure-4 1000000 1783 ns/op 544 B/op 11 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1806 ns/op 160 B/op 8 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 -BenchmarkStructSimpleFailure-4 1000000 1720 ns/op 560 B/op 11 allocs/op -BenchmarkStructSimpleSuccessParallel-4 5000000 329 ns/op 48 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 625 ns/op 560 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 200000 6636 ns/op 432 B/op 27 allocs/op -BenchmarkStructComplexFailure-4 200000 11327 ns/op 2919 B/op 69 allocs/op -BenchmarkStructComplexSuccessParallel-4 1000000 1991 ns/op 432 B/op 27 allocs/op -BenchmarkStructComplexFailureParallel-4 500000 3854 ns/op 2920 B/op 69 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1813 ns/op 592 B/op 11 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 353 ns/op 48 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 656 ns/op 592 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 7637 ns/op 432 B/op 27 allocs/op +BenchmarkStructComplexFailure-4 100000 12775 ns/op 3128 B/op 69 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2270 ns/op 432 B/op 27 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 4328 ns/op 3128 B/op 69 allocs/op ``` How to Contribute @@ -225,9 +239,9 @@ How to Contribute There will always be a development branch for each version i.e. `v1-development`. In order to contribute, please make your pull requests against those branches. -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 -v1 and v1-development branch however, there will also be a v2-development brach even though v2 doesn't exist yet. +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 +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 help make this package even better. diff --git a/baked_in.go b/baked_in.go index f9a0245ec..59746e3fd 100644 --- a/baked_in.go +++ b/baked_in.go @@ -10,10 +10,18 @@ import ( "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 // you can add, remove or even replace items to suite your needs, // or even disregard and use your own map if so desired. -var BakedInValidators = map[string]Func{ +var bakedInValidators = map[string]Func{ "required": hasValue, "len": hasLengthOf, "min": hasMinOf, @@ -107,15 +115,15 @@ func isSSN(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Va 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 { - 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 { - 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 { @@ -126,7 +134,7 @@ func isDataURI(v *Validate, topStruct reflect.Value, currentStructOrField reflec return false } - if !matchesRegex(dataURIRegex, uri[0]) { + if !dataURIRegex.MatchString(uri[0]) { return false } @@ -141,31 +149,31 @@ func hasMultiByteCharacter(v *Validate, topStruct reflect.Value, currentStructOr 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { @@ -176,7 +184,7 @@ func isISBN13(v *Validate, topStruct reflect.Value, currentStructOrField reflect s := strings.Replace(strings.Replace(field.String(), "-", "", 4), " ", "", 4) - if !matchesRegex(iSBN13Regex, s) { + if !iSBN13Regex.MatchString(s) { return false } @@ -200,7 +208,7 @@ func isISBN10(v *Validate, topStruct reflect.Value, currentStructOrField reflect s := strings.Replace(strings.Replace(field.String(), "-", "", 3), " ", "", 3) - if !matchesRegex(iSBN10Regex, s) { + if !iSBN10Regex.MatchString(s) { 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 { - 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 { @@ -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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { diff --git a/benchmarks_test.go b/benchmarks_test.go index 3649bf98e..3a2a1fc45 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -2,7 +2,6 @@ package validator import ( sql "database/sql/driver" - "reflect" "testing" "time" ) @@ -33,11 +32,7 @@ func BenchmarkFieldDiveFailure(b *testing.B) { func BenchmarkFieldCustomTypeSuccess(b *testing.B) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - - validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{ Name: "1", @@ -50,11 +45,8 @@ func BenchmarkFieldCustomTypeSuccess(b *testing.B) { func BenchmarkFieldCustomTypeFailure(b *testing.B) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - - validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + // validate := New(Config{TagName: "validate"}) + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{} @@ -77,11 +69,7 @@ func BenchmarkFieldOrTagFailure(b *testing.B) { func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - - validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{ Name: "1", @@ -101,11 +89,7 @@ func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - - validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{} diff --git a/doc.go b/doc.go index 13bb66e78..fd8ab914c 100644 --- a/doc.go +++ b/doc.go @@ -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" } +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 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 (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: regex diff --git a/examples_test.go b/examples_test.go index 860516835..3144bae69 100644 --- a/examples_test.go +++ b/examples_test.go @@ -3,14 +3,12 @@ package validator_test import ( "fmt" - "gopkg.in/bluesuncorp/validator.v7" + // "gopkg.in/bluesuncorp/validator.v7" + "../validator" ) func ExampleValidate_new() { - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := &validator.Config{TagName: "validate"} validator.New(config) } @@ -19,16 +17,13 @@ func ExampleValidate_field() { // This should be stored somewhere globally var validate *validator.Validate - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := &validator.Config{TagName: "validate"} validate = validator.New(config) i := 0 errs := validate.Field(i, "gt=1,lte=10") - err := errs[""] + err := errs.(validator.ValidationErrors)[""] fmt.Println(err.Field) fmt.Println(err.Tag) 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 var validate *validator.Validate - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := &validator.Config{TagName: "validate"} validate = validator.New(config) @@ -81,7 +73,7 @@ func ExampleValidate_struct() { } errs := validate.Struct(user) - for _, v := range errs { + for _, v := range errs.(validator.ValidationErrors) { fmt.Println(v.Field) // Phone fmt.Println(v.Tag) // required //... and so forth diff --git a/regexes.go b/regexes.go index d061b0369..83ae1982a 100644 --- a/regexes.go +++ b/regexes.go @@ -57,7 +57,3 @@ var ( longitudeRegex = regexp.MustCompile(longitudeRegexString) sSNRegex = regexp.MustCompile(sSNRegexString) ) - -func matchesRegex(regex *regexp.Regexp, value string) bool { - return regex.MatchString(value) -} diff --git a/util.go b/util.go index 641c2d4d5..404bb7843 100644 --- a/util.go +++ b/util.go @@ -1,15 +1,32 @@ package validator import ( + "fmt" "reflect" "strconv" "strings" ) const ( + blank = "" namespaceSeparator = "." leftBracket = "[" 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) { @@ -36,8 +53,8 @@ func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Ki default: - if v.config.hasCustomFuncs { - if fn, ok := v.config.CustomTypeFuncs[current.Type()]; ok { + if v.hasCustomFuncs { + if fn, ok := v.customTypeFuncs[current.Type()]; ok { return v.extractType(reflect.ValueOf(fn(current))) } } @@ -78,7 +95,7 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re fld = namespace[:idx] ns = namespace[idx+1:] } else { - ns = "" + ns = blank idx = len(namespace) } @@ -214,3 +231,77 @@ func panicIf(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 +} diff --git a/validator.go b/validator.go index 3542569e5..c36371d16 100644 --- a/validator.go +++ b/validator.go @@ -20,54 +20,56 @@ import ( ) const ( - utf8HexComma = "0x2C" - utf8Pipe = "0x7C" - tagSeparator = "," - orSeparator = "|" - tagKeySeparator = "=" - structOnlyTag = "structonly" - omitempty = "omitempty" - skipValidationTag = "-" - diveTag = "dive" - existsTag = "exists" - fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" - arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket - mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket - invalidValidation = "Invalid validation tag on field %s" - undefinedValidation = "Undefined validation function on field %s" + utf8HexComma = "0x2C" + utf8Pipe = "0x7C" + tagSeparator = "," + orSeparator = "|" + tagKeySeparator = "=" + structOnlyTag = "structonly" + omitempty = "omitempty" + skipValidationTag = "-" + diveTag = "dive" + existsTag = "exists" + fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" + arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket + mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket + invalidValidation = "Invalid validation tag on field %s" + undefinedValidation = "Undefined validation function on field %s" + validatorNotInitialized = "Validator instance not initialized" ) var ( timeType = reflect.TypeOf(time.Time{}) timePtrType = reflect.TypeOf(&time.Time{}) - errsPool = &sync.Pool{New: newValidationErrors} - tagsCache = &tagCacheMap{m: map[string][]*tagCache{}} emptyStructPtr = new(struct{}) ) -// returns new ValidationErrors to the pool -func newValidationErrors() interface{} { - return ValidationErrors{} +type cachedTag struct { + isOmitEmpty bool + diveTag string + tags []*tagVals } -type tagCache struct { +type tagVals struct { tagVals [][]string isOrVal bool + isAlias bool + tag string } type tagCacheMap struct { 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() defer s.lock.RUnlock() value, ok := s.m[key] return value, ok } -func (s *tagCacheMap) Set(key string, value []*tagCache) { +func (s *tagCacheMap) Set(key string, value *cachedTag) { s.lock.Lock() defer s.lock.Unlock() 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 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. // It is passed to the New() function type Config struct { - TagName string - ValidationFuncs map[string]Func - CustomTypeFuncs map[reflect.Type]CustomTypeFunc - hasCustomFuncs bool + TagName string } // 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 func (ve ValidationErrors) Error() string { - buff := bytes.NewBufferString("") + buff := bytes.NewBufferString(blank) for key, err := range ve { 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 // with other properties that may be needed for error message creation type FieldError struct { - Field string - Tag string - Kind reflect.Kind - Type reflect.Type - Param string - Value interface{} + Field string + Tag string + ActualTag string + Kind reflect.Kind + Type reflect.Type + Param string + Value interface{} } // 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 { - config.hasCustomFuncs = true + if len(v.validationFuncs) == 0 { + // 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 // 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 { + v.initCheck() if len(key) == 0 { 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") } - 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 } // 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{}) { + v.initCheck() - if v.config.CustomTypeFuncs == nil { - v.config.CustomTypeFuncs = map[reflect.Type]CustomTypeFunc{} + if v.customTypeFuncs == nil { + v.customTypeFuncs = map[reflect.Type]CustomTypeFunc{} } 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 // 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) - 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 { - errsPool.Put(errs) + v.errsPool.Put(errs) return nil } 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 // 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) - 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 { - errsPool.Put(errs) + v.errsPool.Put(errs) 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. // Fields may be provided in a namespaced fashion relative to the struct provided -// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name -// NOTE: This is normally not needed, however in some specific cases such as: tied to a -// legacy data structure, it will be useful -func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and 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) StructPartial(current interface{}, fields ...string) error { + v.initCheck() sv, _ := v.extractType(reflect.ValueOf(current)) 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 { - errsPool.Put(errs) + v.errsPool.Put(errs) return nil } @@ -270,10 +335,10 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati // StructExcept validates all fields except the ones passed in. // Fields may be provided in a namespaced fashion relative to the struct provided -// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name -// NOTE: This is normally not needed, however in some specific cases such as: tied to a -// legacy data structure, it will be useful -func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors { +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and 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) StructExcept(current interface{}, fields ...string) error { + v.initCheck() sv, _ := v.extractType(reflect.ValueOf(current)) name := sv.Type().Name() @@ -283,12 +348,12 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) Validatio 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 { - errsPool.Put(errs) + v.errsPool.Put(errs) 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. -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) - 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 { - errsPool.Put(errs) + v.errsPool.Put(errs) 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 } + cTag, isCached := v.tagsCache.Get(tag) + + if !isCached { + cTag = v.parseTags(tag, name) + v.tagsCache.Set(tag, cTag) + } + current, kind := v.extractType(current) var typ reflect.Type switch kind { case reflect.Ptr, reflect.Interface, reflect.Invalid: - if strings.Contains(tag, omitempty) { + if cTag.isOmitEmpty { return } 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 { errs[errPrefix+name] = &FieldError{ - Field: name, - Tag: vals[0], - Param: param, - Kind: kind, + Field: name, + Tag: cTag.tags[0].tag, + ActualTag: cTag.tags[0].tagVals[0][0], + Param: cTag.tags[0].tagVals[0][1], + Kind: kind, } return } errs[errPrefix+name] = &FieldError{ - Field: name, - Tag: vals[0], - Param: param, - Value: current.Interface(), - Kind: kind, - Type: current.Type(), + Field: name, + Tag: cTag.tags[0].tag, + ActualTag: cTag.tags[0].tagVals[0][0], + Param: cTag.tags[0].tagVals[0][1], + Value: current.Interface(), + Kind: kind, + Type: current.Type(), } return } + // if we get here tag length is zero and we can leave if kind == reflect.Invalid { return @@ -427,69 +497,30 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. 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 diveSubTag string - for _, cTag := range tags { + for _, valTag := range cTag.tags { - if cTag.tagVals[0][0] == existsTag { + if valTag.tagVals[0][0] == existsTag { continue } - if cTag.tagVals[0][0] == diveTag { + if valTag.tagVals[0][0] == diveTag { dive = true - diveSubTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",") + diveSubTag = strings.TrimLeft(strings.SplitN(cTag.diveTag, diveTag, 2)[1], ",") 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 } 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 } } @@ -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 -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 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 { panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } @@ -550,33 +581,46 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. errTag += orSeparator + val[0] } - errs[errPrefix+name] = &FieldError{ - Field: name, - Tag: errTag[1:], - Value: current.Interface(), - Type: currentType, - Kind: currentKind, + if valTag.isAlias { + errs[errPrefix+name] = &FieldError{ + Field: name, + Tag: valTag.tag, + ActualTag: errTag[1:], + 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 } - valFunc, ok = v.config.ValidationFuncs[cTag.tagVals[0][0]] + valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]] if !ok { 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 } errs[errPrefix+name] = &FieldError{ - Field: name, - Tag: cTag.tagVals[0][0], - Value: current.Interface(), - Param: cTag.tagVals[0][1], - Type: currentType, - Kind: currentKind, + Field: name, + Tag: valTag.tag, + ActualTag: valTag.tagVals[0][0], + Value: current.Interface(), + Param: valTag.tagVals[0][1], + Type: currentType, + Kind: currentKind, } return true diff --git a/validator_test.go b/validator_test.go index 22084b467..63b79c1dc 100644 --- a/validator_test.go +++ b/validator_test.go @@ -111,9 +111,11 @@ type TestSlice struct { 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] 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) { p1 := []string{ @@ -354,12 +423,12 @@ func TestStructPartial(t *testing.T) { // these will fail as unset item IS tested errs = validate.StructExcept(tPartial, p1...) 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...) NotEqual(t, errs, nil) 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: tPartial.SubSlice[1].Test = "" @@ -374,13 +443,13 @@ func TestStructPartial(t *testing.T) { AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") 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[1].Test", "Test", "required") errs = validate.StructPartial(tPartial, p2...) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") // 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 errs = validate.StructExcept(tPartial, p1...) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") errs = validate.StructExcept(tPartial, p2...) @@ -1210,9 +1279,8 @@ func TestExistsValidation(t *testing.T) { func TestSQLValue2Validation(t *testing.T) { - config := Config{ - TagName: "validate", - ValidationFuncs: BakedInValidators, + config := &Config{ + TagName: "validate", } validate := New(config) @@ -1261,20 +1329,23 @@ func TestSQLValue2Validation(t *testing.T) { errs = validate.Struct(c) 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.OverriddenInt", "OverriddenInt", "gt") } func TestSQLValueValidation(t *testing.T) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType - customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason + // customTypes := map[reflect.Type]CustomTypeFunc{} + // customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType + // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + // customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType + // 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{ Name: "", @@ -1317,7 +1388,7 @@ func TestSQLValueValidation(t *testing.T) { errs = validate.Struct(c) 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.OverriddenInt", "OverriddenInt", "gt") } @@ -1348,7 +1419,7 @@ func TestMACValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d mac failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "mac" { t.Fatalf("Index: %d mac failed Error: %s", i, errs) } @@ -1386,7 +1457,7 @@ func TestIPValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ip failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ip" { t.Fatalf("Index: %d ip failed Error: %s", i, errs) } @@ -1424,7 +1495,7 @@ func TestIPv6Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ipv6" { t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) } @@ -1462,7 +1533,7 @@ func TestIPv4Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ipv4" { t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) } @@ -1619,7 +1690,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "ExternalCMD.Data", "Data", "required") type ExternalCMD2 struct { @@ -1636,7 +1707,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s2) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "ExternalCMD2.Data", "Data", "len") s3 := &ExternalCMD2{ @@ -1647,7 +1718,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s3) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "ExternalCMD2.Data", "Data", "len") type Inner struct { @@ -1666,7 +1737,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s4) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "ExternalCMD.Data.Name", "Name", "required") type TestMapStructPtr struct { @@ -1681,7 +1752,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(msp) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "len") type TestMultiDimensionalStructs struct { @@ -1699,7 +1770,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(tms) 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][2].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) 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][2].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") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "[3]", "[3]", "len") errs = validate.Field(m, "len=2,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "", "", "len") arr := []interface{}{"ok", "", "ok"} errs = validate.Field(arr, "len=3,dive,len=2") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "[1]", "[1]", "len") errs = validate.Field(arr, "len=2,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "", "", "len") type MyStruct struct { @@ -1777,12 +1848,12 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Field(m, "len=3,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "[3]", "[3]", "required") errs = validate.Field(m, "len=2,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "", "", "len") type Inner struct { @@ -1801,11 +1872,12 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Struct(ms) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestMapStruct.Errs[3].Name", "Name", "required") // for full test coverage - fmt.Sprint(errs.Error()) + s := fmt.Sprint(errs.Error()) + NotEqual(t, s, "") type TestMapTimeStruct struct { Errs map[int]*time.Time `validate:"gt=0,dive,required"` @@ -1821,7 +1893,7 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Struct(mt) 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[4]", "Errs[4]", "required") @@ -1837,7 +1909,7 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Struct(msp) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "required") type TestMapStructPtr2 struct { @@ -1860,12 +1932,12 @@ func TestArrayDiveValidation(t *testing.T) { errs := validate.Field(arr, "len=3,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "[1]", "[1]", "required") errs = validate.Field(arr, "len=2,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "", "", "len") type BadDive struct { @@ -1888,7 +1960,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(test) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "Test.Errs[1]", "Errs[1]", "required") test = &Test{ @@ -1897,7 +1969,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(test) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "Test.Errs[2]", "Errs[2]", "required") type TestMultiDimensional struct { @@ -1915,7 +1987,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tm) 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][2]", "Errs[0][2]", "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) 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][2].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) 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][2].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[2][1].Name", "Name", "required") + // for full test coverage - fmt.Sprint(errs.Error()) + s := fmt.Sprint(errs.Error()) + NotEqual(t, s, "") type TestMultiDimensionalStructsPtr2 struct { Errs [][]*Inner `validate:"gt=0,dive,dive,required"` @@ -1987,7 +2061,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmsp2) 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][2].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) 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][2].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) 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[2][1]", "Errs[2][1]", "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) 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[2][1]", "Errs[2][1]", "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) { t.Fatalf("Index: %d SSN failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ssn" { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } @@ -2220,7 +2294,7 @@ func TestLongitudeValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "longitude" { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } @@ -2254,7 +2328,7 @@ func TestLatitudeValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "latitude" { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } @@ -2294,7 +2368,7 @@ func TestDataURIValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "datauri" { t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) } @@ -2332,7 +2406,7 @@ func TestMultibyteValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "multibyte" { t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) } @@ -2371,7 +2445,7 @@ func TestPrintableASCIIValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "printascii" { t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) } @@ -2409,7 +2483,7 @@ func TestASCIIValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ascii" { t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) } @@ -2444,7 +2518,7 @@ func TestUUID5Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uuid5" { t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) } @@ -2478,7 +2552,7 @@ func TestUUID4Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uuid4" { t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) } @@ -2511,7 +2585,7 @@ func TestUUID3Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uuid3" { t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) } @@ -2547,7 +2621,7 @@ func TestUUIDValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uuid" { t.Fatalf("Index: %d UUID failed Error: %s", i, errs) } @@ -2585,7 +2659,7 @@ func TestISBNValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "isbn" { t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) } @@ -2622,7 +2696,7 @@ func TestISBN13Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "isbn13" { t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) } @@ -2660,7 +2734,7 @@ func TestISBN10Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "isbn10" { t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) } @@ -3825,9 +3899,8 @@ func TestAddFunctions(t *testing.T) { return true } - config := Config{ - TagName: "validateme", - ValidationFuncs: BakedInValidators, + config := &Config{ + TagName: "validateme", } validate := New(config) @@ -3843,13 +3916,14 @@ func TestAddFunctions(t *testing.T) { errs = validate.RegisterValidation("new", fn) 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) { - config := Config{ - TagName: "val", - ValidationFuncs: BakedInValidators, + config := &Config{ + TagName: "val", } validate := New(config) @@ -4129,7 +4203,7 @@ func TestUrl(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d URL failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "url" { t.Fatalf("Index: %d URL failed Error: %s", i, errs) } @@ -4193,7 +4267,7 @@ func TestUri(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d URI failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uri" { t.Fatalf("Index: %d URI failed Error: %s", i, errs) } @@ -4659,7 +4733,7 @@ func TestStructStringValidation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs), 13) + Equal(t, len(errs.(ValidationErrors)), 13) // Assert Fields AssertError(t, errs, "TestString.Required", "Required", "required") @@ -4714,7 +4788,7 @@ func TestStructInt32Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs), 10) + Equal(t, len(errs.(ValidationErrors)), 10) // Assert Fields AssertError(t, errs, "TestInt32.Required", "Required", "required") @@ -4756,7 +4830,7 @@ func TestStructUint64Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs), 6) + Equal(t, len(errs.(ValidationErrors)), 6) // Assert Fields AssertError(t, errs, "TestUint64.Required", "Required", "required") @@ -4794,7 +4868,7 @@ func TestStructFloat64Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs), 6) + Equal(t, len(errs.(ValidationErrors)), 6) // Assert Fields AssertError(t, errs, "TestFloat64.Required", "Required", "required") @@ -4830,7 +4904,7 @@ func TestStructSliceValidation(t *testing.T) { errs = validate.Struct(tFail) NotEqual(t, errs, nil) - Equal(t, len(errs), 6) + Equal(t, len(errs.(ValidationErrors)), 6) // Assert Field Errors AssertError(t, errs, "TestSlice.Required", "Required", "required")