Merge pull request #169 from joeybloggs/v8-development

New v8 Changes
pull/171/head v8
Dean Karn 9 years ago
commit 40bd6a0bc0
  1. 104
      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.
- 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.

@ -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 {

@ -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{}

@ -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

@ -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

@ -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)
}

@ -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
}

@ -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

@ -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")

Loading…
Cancel
Save