diff --git a/README.md b/README.md index 36ac9da..c4da807 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Package validator ================ [![Join the chat at https://gitter.im/bluesuncorp/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bluesuncorp/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/bluesuncorp/validator.svg?branch=v5.1)](https://travis-ci.org/bluesuncorp/validator) -[![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v5)](https://coveralls.io/r/bluesuncorp/validator?branch=v5) -[![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v5?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v5) +[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487374/badge.svg)](https://semaphoreci.com/joeybloggs/validator) +[![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v6)](https://coveralls.io/r/bluesuncorp/validator?branch=v6) +[![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v6?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v6) Package validator implements value validations for structs and individual fields based on tags. @@ -19,20 +19,20 @@ Installation Use go get. - go get gopkg.in/bluesuncorp/validator.v5 + go get gopkg.in/bluesuncorp/validator.v6 or to update - go get -u gopkg.in/bluesuncorp/validator.v5 + go get -u gopkg.in/bluesuncorp/validator.v6 Then import the validator package into your own code. - import "gopkg.in/bluesuncorp/validator.v5" + import "gopkg.in/bluesuncorp/validator.v6" Usage and documentation ------ -Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v5 for detailed usage docs. +Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs. ##### Example: ```go @@ -41,7 +41,7 @@ package main import ( "fmt" - "gopkg.in/bluesuncorp/validator.v5" + "gopkg.in/bluesuncorp/validator.v6" ) // User contains user information @@ -66,7 +66,12 @@ var validate *validator.Validate func main() { - validate = validator.New("validate", validator.BakedInValidators) + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate = validator.New(config) address := &Address{ Street: "Eavesdown Docks", @@ -83,31 +88,14 @@ func main() { Addresses: []*Address{address}, } - // returns nil or *StructErrors + // returns nil or ValidationErrors ( map[string]*FieldError ) errs := validate.Struct(user) if errs != nil { - // err will be of type *FieldError - err := errs.Errors["Age"] - fmt.Println(err.Error()) // output: Field validation for "Age" failed on the "lte" tag - fmt.Println(err.Field) // output: Age - fmt.Println(err.Tag) // output: lte - fmt.Println(err.Kind) // output: uint8 - fmt.Println(err.Type) // output: uint8 - fmt.Println(err.Param) // output: 130 - fmt.Println(err.Value) // output: 135 - - // or if you prefer you can use the Flatten function - // NOTE: I find this usefull when using a more hard static approach of checking field errors. - // The above, is best for passing to some generic code to say parse the errors. i.e. I pass errs - // to a routine which loops through the errors, creates and translates the error message into the - // users locale and returns a map of map[string]string // field and error which I then use - // within the HTML rendering. - - flat := errs.Flatten() - fmt.Println(flat) // output: map[Age:Field validation for "Age" failed on the "lte" tag Addresses[0].Address.City:Field validation for "City" failed on the "required" tag] - err = flat["Addresses[0].Address.City"] + 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"] fmt.Println(err.Field) // output: City fmt.Println(err.Tag) // output: required fmt.Println(err.Kind) // output: string @@ -126,14 +114,18 @@ func main() { Benchmarks ------ ###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 +NOTE: allocations for structs are up from v5, however ns/op for parallel operations are way down. +It was a decicion not to cache struct info because although it reduced allocation to v5 levels, it +hurt parallel performance too much. ```go $ go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkValidateField-4 3000000 429 ns/op 192 B/op 2 allocs/op -BenchmarkValidateStructSimple-4 500000 2877 ns/op 657 B/op 10 allocs/op -BenchmarkTemplateParallelSimple-4 500000 3097 ns/op 657 B/op 10 allocs/op -BenchmarkValidateStructLarge-4 100000 15228 ns/op 4350 B/op 62 allocs/op -BenchmarkTemplateParallelLarge-4 100000 14257 ns/op 4354 B/op 62 allocs/op +BenchmarkField-4 5000000 314 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTag-4 500000 2425 ns/op 20 B/op 2 allocs/op +BenchmarkStructSimple-4 500000 3117 ns/op 553 B/op 14 allocs/op +BenchmarkStructSimpleParallel-4 1000000 1149 ns/op 553 B/op 14 allocs/op +BenchmarkStructComplex-4 100000 19580 ns/op 3230 B/op 102 allocs/op +BenchmarkStructComplexParallel-4 200000 6686 ns/op 3232 B/op 102 allocs/op ``` How to Contribute diff --git a/baked_in.go b/baked_in.go index 22746ad..bf9e512 100644 --- a/baked_in.go +++ b/baked_in.go @@ -66,26 +66,26 @@ var BakedInValidators = map[string]Func{ "ssn": isSSN, } -func isSSN(top interface{}, current interface{}, field interface{}, param string) bool { +func isSSN(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if len(field.(string)) != 11 { + if field.Len() != 11 { return false } - return matchesRegex(sSNRegex, field) + return matchesRegex(sSNRegex, field.String()) } -func isLongitude(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(longitudeRegex, field) +func isLongitude(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(longitudeRegex, field.String()) } -func isLatitude(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(latitudeRegex, field) +func isLatitude(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(latitudeRegex, field.String()) } -func isDataURI(top interface{}, current interface{}, field interface{}, param string) bool { +func isDataURI(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - uri := strings.SplitN(field.(string), ",", 2) + uri := strings.SplitN(field.String(), ",", 2) if len(uri) != 2 { return false @@ -95,49 +95,51 @@ func isDataURI(top interface{}, current interface{}, field interface{}, param st return false } - return isBase64(top, current, uri[1], param) + fld := reflect.ValueOf(uri[1]) + + return isBase64(topStruct, currentStruct, fld, fld.Type(), fld.Kind(), param) } -func hasMultiByteCharacter(top interface{}, current interface{}, field interface{}, param string) bool { +func hasMultiByteCharacter(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if len(field.(string)) == 0 { + if field.Len() == 0 { return true } - return matchesRegex(multibyteRegex, field) + return matchesRegex(multibyteRegex, field.String()) } -func isPrintableASCII(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(printableASCIIRegex, field) +func isPrintableASCII(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(printableASCIIRegex, field.String()) } -func isASCII(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(aSCIIRegex, field) +func isASCII(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(aSCIIRegex, field.String()) } -func isUUID5(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(uUID5Regex, field) +func isUUID5(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(uUID5Regex, field.String()) } -func isUUID4(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(uUID4Regex, field) +func isUUID4(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(uUID4Regex, field.String()) } -func isUUID3(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(uUID3Regex, field) +func isUUID3(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(uUID3Regex, field.String()) } -func isUUID(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(uUIDRegex, field) +func isUUID(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(uUIDRegex, field.String()) } -func isISBN(top interface{}, current interface{}, field interface{}, param string) bool { - return isISBN10(top, current, field, param) || isISBN13(top, current, field, param) +func isISBN(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return isISBN10(topStruct, currentStruct, field, fieldType, fieldKind, param) || isISBN13(topStruct, currentStruct, field, fieldType, fieldKind, param) } -func isISBN13(top interface{}, current interface{}, field interface{}, param string) bool { +func isISBN13(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - s := strings.Replace(strings.Replace(field.(string), "-", "", 4), " ", "", 4) + s := strings.Replace(strings.Replace(field.String(), "-", "", 4), " ", "", 4) if !matchesRegex(iSBN13Regex, s) { return false @@ -159,9 +161,9 @@ func isISBN13(top interface{}, current interface{}, field interface{}, param str return false } -func isISBN10(top interface{}, current interface{}, field interface{}, param string) bool { +func isISBN10(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - s := strings.Replace(strings.Replace(field.(string), "-", "", 3), " ", "", 3) + s := strings.Replace(strings.Replace(field.String(), "-", "", 3), " ", "", 3) if !matchesRegex(iSBN10Regex, s) { return false @@ -187,179 +189,158 @@ func isISBN10(top interface{}, current interface{}, field interface{}, param str return false } -func excludesRune(top interface{}, current interface{}, field interface{}, param string) bool { - return !containsRune(top, current, field, param) +func excludesRune(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !containsRune(topStruct, currentStruct, field, fieldType, fieldKind, param) } -func excludesAll(top interface{}, current interface{}, field interface{}, param string) bool { - return !containsAny(top, current, field, param) +func excludesAll(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !containsAny(topStruct, currentStruct, field, fieldType, fieldKind, param) } -func excludes(top interface{}, current interface{}, field interface{}, param string) bool { - return !contains(top, current, field, param) +func excludes(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !contains(topStruct, currentStruct, field, fieldType, fieldKind, param) } -func containsRune(top interface{}, current interface{}, field interface{}, param string) bool { +func containsRune(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { r, _ := utf8.DecodeRuneInString(param) - return strings.ContainsRune(field.(string), r) + return strings.ContainsRune(field.String(), r) } -func containsAny(top interface{}, current interface{}, field interface{}, param string) bool { - return strings.ContainsAny(field.(string), param) +func containsAny(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return strings.ContainsAny(field.String(), param) } -func contains(top interface{}, current interface{}, field interface{}, param string) bool { - return strings.Contains(field.(string), param) +func contains(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return strings.Contains(field.String(), param) } -func isNeField(top interface{}, current interface{}, field interface{}, param string) bool { - return !isEqField(top, current, field, param) +func isNeField(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !isEqField(topStruct, currentStruct, field, fieldType, fieldKind, param) } -func isNe(top interface{}, current interface{}, field interface{}, param string) bool { - return !isEq(top, current, field, param) +func isNe(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !isEq(topStruct, currentStruct, field, fieldType, fieldKind, param) } -func isEqField(top interface{}, current interface{}, field interface{}, param string) bool { +func isEqField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if current == nil { - panic("struct not passed for cross validation") + // if current == nil { + if !current.IsValid() { + panic("struct or field value not passed for cross validation") } - currentVal := reflect.ValueOf(current) - - if currentVal.Kind() == reflect.Ptr && !currentVal.IsNil() { - currentVal = reflect.ValueOf(currentVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - var currentFielVal reflect.Value - - switch currentVal.Kind() { + switch current.Kind() { case reflect.Struct: - if currentVal.Type() == reflect.TypeOf(time.Time{}) { - currentFielVal = currentVal + if current.Type() == timeType || current.Type() == timePtrType { break } - f := currentVal.FieldByName(param) + current = current.FieldByName(param) - if f.Kind() == reflect.Invalid { + if current.Kind() == reflect.Invalid { panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) } - - currentFielVal = f - - default: - - currentFielVal = currentVal } - if currentFielVal.Kind() == reflect.Ptr && !currentFielVal.IsNil() { - - currentFielVal = reflect.ValueOf(currentFielVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - fv := reflect.ValueOf(field) - - switch fv.Kind() { + switch fieldKind { case reflect.String: - return fv.String() == currentFielVal.String() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.String() == current.String() - return fv.Int() == currentFielVal.Int() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() == current.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - - return fv.Uint() == currentFielVal.Uint() + return field.Uint() == current.Uint() case reflect.Float32, reflect.Float64: + return field.Float() == current.Float() - return fv.Float() == currentFielVal.Float() case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) == int64(current.Len()) - return int64(fv.Len()) == int64(currentFielVal.Len()) case reflect.Struct: + if fieldType == timeType || fieldType == timePtrType { - if fv.Type() == reflect.TypeOf(time.Time{}) { - - if currentFielVal.Type() != reflect.TypeOf(time.Time{}) { + if current.Type() != timeType && current.Type() != timePtrType { panic("Bad Top Level field type") } - t := currentFielVal.Interface().(time.Time) - fieldTime := field.(time.Time) + t := current.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) return fieldTime.Equal(t) } } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isEq(top interface{}, current interface{}, field interface{}, param string) bool { +func isEq(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - st := reflect.ValueOf(field) - - switch st.Kind() { + switch fieldKind { case reflect.String: - - return st.String() == param + return field.String() == param case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) - return int64(st.Len()) == p + return int64(field.Len()) == p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) - return st.Int() == p + return field.Int() == p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) - return st.Uint() == p + return field.Uint() == p case reflect.Float32, reflect.Float64: p := asFloat(param) - return st.Float() == p + return field.Float() == p } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isBase64(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(base64Regex, field) +func isBase64(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(base64Regex, field.String()) } -func isURI(top interface{}, current interface{}, field interface{}, param string) bool { +func isURI(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - st := reflect.ValueOf(field) - - switch st.Kind() { + switch fieldKind { case reflect.String: - _, err := url.ParseRequestURI(field.(string)) + _, err := url.ParseRequestURI(field.String()) return err == nil } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isURL(top interface{}, current interface{}, field interface{}, param string) bool { - st := reflect.ValueOf(field) +func isURL(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - switch st.Kind() { + switch fieldKind { case reflect.String: - url, err := url.ParseRequestURI(field.(string)) + url, err := url.ParseRequestURI(field.String()) if err != nil { return false @@ -372,594 +353,526 @@ func isURL(top interface{}, current interface{}, field interface{}, param string return err == nil } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isEmail(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(emailRegex, field) +func isEmail(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(emailRegex, field.String()) } -func isHsla(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(hslaRegex, field) +func isHsla(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(hslaRegex, field.String()) } -func isHsl(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(hslRegex, field) +func isHsl(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(hslRegex, field.String()) } -func isRgba(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(rgbaRegex, field) +func isRgba(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(rgbaRegex, field.String()) } -func isRgb(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(rgbRegex, field) +func isRgb(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(rgbRegex, field.String()) } -func isHexcolor(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(hexcolorRegex, field) +func isHexcolor(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(hexcolorRegex, field.String()) } -func isHexadecimal(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(hexadecimalRegex, field) +func isHexadecimal(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(hexadecimalRegex, field.String()) } -func isNumber(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(numberRegex, field) +func isNumber(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(numberRegex, field.String()) } -func isNumeric(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(numericRegex, field) +func isNumeric(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(numericRegex, field.String()) } -func isAlphanum(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(alphaNumericRegex, field) +func isAlphanum(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(alphaNumericRegex, field.String()) } -func isAlpha(top interface{}, current interface{}, field interface{}, param string) bool { - return matchesRegex(alphaRegex, field) +func isAlpha(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return matchesRegex(alphaRegex, field.String()) } -func hasValue(top interface{}, current interface{}, field interface{}, param string) bool { - - st := reflect.ValueOf(field) +func hasValue(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - switch st.Kind() { + switch fieldKind { case reflect.Slice, reflect.Map, reflect.Array: - return field != nil && int64(st.Len()) > 0 + return !field.IsNil() && int64(field.Len()) > 0 default: - return field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface() + return field.IsValid() && field.Interface() != reflect.Zero(fieldType).Interface() } } -func isGteField(top interface{}, current interface{}, field interface{}, param string) bool { +func isGteField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if current == nil { + if !current.IsValid() { panic("struct not passed for cross validation") } - currentVal := reflect.ValueOf(current) - - if currentVal.Kind() == reflect.Ptr && !currentVal.IsNil() { - currentVal = reflect.ValueOf(currentVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - var currentFielVal reflect.Value - - switch currentVal.Kind() { + switch current.Kind() { case reflect.Struct: - if currentVal.Type() == reflect.TypeOf(time.Time{}) { - currentFielVal = currentVal + if current.Type() == timeType || current.Type() == timePtrType { break } - f := currentVal.FieldByName(param) + current = current.FieldByName(param) - if f.Kind() == reflect.Invalid { + if current.Kind() == reflect.Invalid { panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) } - - currentFielVal = f - - default: - - currentFielVal = currentVal } - if currentFielVal.Kind() == reflect.Ptr && !currentFielVal.IsNil() { - - currentFielVal = reflect.ValueOf(currentFielVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - fv := reflect.ValueOf(field) - - switch fv.Kind() { + switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return fv.Int() >= currentFielVal.Int() + return field.Int() >= current.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return fv.Uint() >= currentFielVal.Uint() + return field.Uint() >= current.Uint() case reflect.Float32, reflect.Float64: - return fv.Float() >= currentFielVal.Float() + return field.Float() >= current.Float() case reflect.Struct: - if fv.Type() == reflect.TypeOf(time.Time{}) { + if field.Type() == timeType || field.Type() == timePtrType { - if currentFielVal.Type() != reflect.TypeOf(time.Time{}) { + if current.Type() != timeType && current.Type() != timePtrType { panic("Bad Top Level field type") } - t := currentFielVal.Interface().(time.Time) - fieldTime := field.(time.Time) + t := current.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) return fieldTime.After(t) || fieldTime.Equal(t) } } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isGtField(top interface{}, current interface{}, field interface{}, param string) bool { +func isGtField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if current == nil { + if !current.IsValid() { panic("struct not passed for cross validation") } - currentVal := reflect.ValueOf(current) - - if currentVal.Kind() == reflect.Ptr && !currentVal.IsNil() { - currentVal = reflect.ValueOf(currentVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - var currentFielVal reflect.Value - - switch currentVal.Kind() { + switch current.Kind() { case reflect.Struct: - if currentVal.Type() == reflect.TypeOf(time.Time{}) { - currentFielVal = currentVal + if current.Type() == timeType || current.Type() == timePtrType { break } - f := currentVal.FieldByName(param) + current = current.FieldByName(param) - if f.Kind() == reflect.Invalid { + if current.Kind() == reflect.Invalid { panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) } - - currentFielVal = f - - default: - - currentFielVal = currentVal } - if currentFielVal.Kind() == reflect.Ptr && !currentFielVal.IsNil() { - - currentFielVal = reflect.ValueOf(currentFielVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - fv := reflect.ValueOf(field) - - switch fv.Kind() { + switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return fv.Int() > currentFielVal.Int() + return field.Int() > current.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return fv.Uint() > currentFielVal.Uint() + return field.Uint() > current.Uint() case reflect.Float32, reflect.Float64: - return fv.Float() > currentFielVal.Float() + return field.Float() > current.Float() case reflect.Struct: - if fv.Type() == reflect.TypeOf(time.Time{}) { + if field.Type() == timeType || field.Type() == timePtrType { - if currentFielVal.Type() != reflect.TypeOf(time.Time{}) { + if current.Type() != timeType && current.Type() != timePtrType { panic("Bad Top Level field type") } - t := currentFielVal.Interface().(time.Time) - fieldTime := field.(time.Time) + t := current.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) return fieldTime.After(t) } } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isGte(top interface{}, current interface{}, field interface{}, param string) bool { - - st := reflect.ValueOf(field) +func isGte(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - switch st.Kind() { + switch fieldKind { case reflect.String: p := asInt(param) - return int64(len(st.String())) >= p + return int64(utf8.RuneCountInString(field.String())) >= p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) - return int64(st.Len()) >= p + return int64(field.Len()) >= p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) - return st.Int() >= p + return field.Int() >= p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) - return st.Uint() >= p + return field.Uint() >= p case reflect.Float32, reflect.Float64: p := asFloat(param) - return st.Float() >= p + return field.Float() >= p case reflect.Struct: - if st.Type() == reflect.TypeOf(time.Time{}) { + if fieldType == timeType || fieldType == timePtrType { now := time.Now().UTC() - t := field.(time.Time) + t := field.Interface().(time.Time) return t.After(now) || t.Equal(now) } } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isGt(top interface{}, current interface{}, field interface{}, param string) bool { +func isGt(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - st := reflect.ValueOf(field) - - switch st.Kind() { + switch fieldKind { case reflect.String: p := asInt(param) - return int64(len(st.String())) > p + return int64(utf8.RuneCountInString(field.String())) > p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) - return int64(st.Len()) > p + return int64(field.Len()) > p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) - return st.Int() > p + return field.Int() > p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) - return st.Uint() > p + return field.Uint() > p case reflect.Float32, reflect.Float64: p := asFloat(param) - return st.Float() > p + return field.Float() > p case reflect.Struct: - if st.Type() == reflect.TypeOf(time.Time{}) { + if field.Type() == timeType || field.Type() == timePtrType { - return field.(time.Time).After(time.Now().UTC()) + return field.Interface().(time.Time).After(time.Now().UTC()) } } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // length tests whether a variable's length is equal to a given // value. For strings it tests the number of characters whereas // for maps and slices it tests the number of items. -func hasLengthOf(top interface{}, current interface{}, field interface{}, param string) bool { - - st := reflect.ValueOf(field) +func hasLengthOf(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - switch st.Kind() { + switch fieldKind { case reflect.String: p := asInt(param) - return int64(len(st.String())) == p + return int64(utf8.RuneCountInString(field.String())) == p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) - return int64(st.Len()) == p + return int64(field.Len()) == p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) - return st.Int() == p + return field.Int() == p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) - return st.Uint() == p + return field.Uint() == p case reflect.Float32, reflect.Float64: p := asFloat(param) - return st.Float() == p + return field.Float() == p } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // min tests whether a variable value is larger or equal to a given // number. For number types, it's a simple lesser-than test; for // strings it tests the number of characters whereas for maps // and slices it tests the number of items. -func hasMinOf(top interface{}, current interface{}, field interface{}, param string) bool { +func hasMinOf(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return isGte(top, current, field, param) + return isGte(topStruct, currentStruct, field, fieldType, fieldKind, param) } -func isLteField(top interface{}, current interface{}, field interface{}, param string) bool { +func isLteField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if current == nil { + if !current.IsValid() { panic("struct not passed for cross validation") } - currentVal := reflect.ValueOf(current) - - if currentVal.Kind() == reflect.Ptr && !currentVal.IsNil() { - currentVal = reflect.ValueOf(currentVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - var currentFielVal reflect.Value - - switch currentVal.Kind() { + switch current.Kind() { case reflect.Struct: - if currentVal.Type() == reflect.TypeOf(time.Time{}) { - currentFielVal = currentVal + if current.Type() == timeType || current.Type() == timePtrType { break } - f := currentVal.FieldByName(param) + current = current.FieldByName(param) - if f.Kind() == reflect.Invalid { + if current.Kind() == reflect.Invalid { panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) } - - currentFielVal = f - - default: - - currentFielVal = currentVal } - if currentFielVal.Kind() == reflect.Ptr && !currentFielVal.IsNil() { - - currentFielVal = reflect.ValueOf(currentFielVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - fv := reflect.ValueOf(field) - - switch fv.Kind() { + switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return fv.Int() <= currentFielVal.Int() + return field.Int() <= current.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return fv.Uint() <= currentFielVal.Uint() + return field.Uint() <= current.Uint() case reflect.Float32, reflect.Float64: - return fv.Float() <= currentFielVal.Float() + return field.Float() <= current.Float() case reflect.Struct: - if fv.Type() == reflect.TypeOf(time.Time{}) { + if field.Type() == timeType || field.Type() == timePtrType { - if currentFielVal.Type() != reflect.TypeOf(time.Time{}) { + if current.Type() != timeType && current.Type() != timePtrType { panic("Bad Top Level field type") } - t := currentFielVal.Interface().(time.Time) - fieldTime := field.(time.Time) + t := current.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) return fieldTime.Before(t) || fieldTime.Equal(t) } } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isLtField(top interface{}, current interface{}, field interface{}, param string) bool { +func isLtField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if current == nil { + if !current.IsValid() { panic("struct not passed for cross validation") } - currentVal := reflect.ValueOf(current) - - if currentVal.Kind() == reflect.Ptr && !currentVal.IsNil() { - currentVal = reflect.ValueOf(currentVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - var currentFielVal reflect.Value - - switch currentVal.Kind() { + switch current.Kind() { case reflect.Struct: - if currentVal.Type() == reflect.TypeOf(time.Time{}) { - currentFielVal = currentVal + if current.Type() == timeType || current.Type() == timePtrType { break } - f := currentVal.FieldByName(param) + current = current.FieldByName(param) - if f.Kind() == reflect.Invalid { + if current.Kind() == reflect.Invalid { panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) } - - currentFielVal = f - - default: - - currentFielVal = currentVal } - if currentFielVal.Kind() == reflect.Ptr && !currentFielVal.IsNil() { - - currentFielVal = reflect.ValueOf(currentFielVal.Elem().Interface()) + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - fv := reflect.ValueOf(field) - - switch fv.Kind() { + switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return fv.Int() < currentFielVal.Int() + return field.Int() < current.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return fv.Uint() < currentFielVal.Uint() + return field.Uint() < current.Uint() case reflect.Float32, reflect.Float64: - return fv.Float() < currentFielVal.Float() + return field.Float() < current.Float() case reflect.Struct: - if fv.Type() == reflect.TypeOf(time.Time{}) { + if field.Type() == timeType || field.Type() == timePtrType { - if currentFielVal.Type() != reflect.TypeOf(time.Time{}) { + if current.Type() != timeType && current.Type() != timePtrType { panic("Bad Top Level field type") } - t := currentFielVal.Interface().(time.Time) - fieldTime := field.(time.Time) + t := current.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) return fieldTime.Before(t) } } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isLte(top interface{}, current interface{}, field interface{}, param string) bool { - - st := reflect.ValueOf(field) +func isLte(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - switch st.Kind() { + switch fieldKind { case reflect.String: p := asInt(param) - return int64(len(st.String())) <= p + return int64(utf8.RuneCountInString(field.String())) <= p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) - return int64(st.Len()) <= p + return int64(field.Len()) <= p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) - return st.Int() <= p + return field.Int() <= p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) - return st.Uint() <= p + return field.Uint() <= p case reflect.Float32, reflect.Float64: p := asFloat(param) - return st.Float() <= p + return field.Float() <= p case reflect.Struct: - if st.Type() == reflect.TypeOf(time.Time{}) { + if fieldType == timeType || fieldType == timePtrType { now := time.Now().UTC() - t := field.(time.Time) + t := field.Interface().(time.Time) return t.Before(now) || t.Equal(now) } } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isLt(top interface{}, current interface{}, field interface{}, param string) bool { - - st := reflect.ValueOf(field) +func isLt(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - switch st.Kind() { + switch fieldKind { case reflect.String: p := asInt(param) - return int64(len(st.String())) < p + return int64(utf8.RuneCountInString(field.String())) < p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) - return int64(st.Len()) < p + return int64(field.Len()) < p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) - return st.Int() < p + return field.Int() < p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) - return st.Uint() < p + return field.Uint() < p case reflect.Float32, reflect.Float64: p := asFloat(param) - return st.Float() < p + return field.Float() < p case reflect.Struct: - if st.Type() == reflect.TypeOf(time.Time{}) { + if field.Type() == timeType || field.Type() == timePtrType { - return field.(time.Time).Before(time.Now().UTC()) + return field.Interface().(time.Time).Before(time.Now().UTC()) } } - panic(fmt.Sprintf("Bad field type %T", field)) + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // max tests whether a variable value is lesser than a given // value. For numbers, it's a simple lesser-than test; for // strings it tests the number of characters whereas for maps // and slices it tests the number of items. -func hasMaxOf(top interface{}, current interface{}, field interface{}, param string) bool { +func hasMaxOf(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return isLte(top, current, field, param) + return isLte(topStruct, currentStruct, field, fieldType, fieldKind, param) } // asInt retuns the parameter as a int64 diff --git a/benchmarks_test.go b/benchmarks_test.go index ee836c2..14c5d2b 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -2,13 +2,19 @@ package validator import "testing" -func BenchmarkValidateField(b *testing.B) { +func BenchmarkField(b *testing.B) { for n := 0; n < b.N; n++ { validate.Field("1", "len=1") } } -func BenchmarkValidateStructSimple(b *testing.B) { +func BenchmarkFieldOrTag(b *testing.B) { + for n := 0; n < b.N; n++ { + validate.Field("rgba(0,0,0,1)", "rgb|rgba") + } +} + +func BenchmarkStructSimple(b *testing.B) { type Foo struct { StringValue string `validate:"min=5,max=10"` @@ -24,7 +30,7 @@ func BenchmarkValidateStructSimple(b *testing.B) { } } -func BenchmarkTemplateParallelSimple(b *testing.B) { +func BenchmarkStructSimpleParallel(b *testing.B) { type Foo struct { StringValue string `validate:"min=5,max=10"` @@ -42,7 +48,7 @@ func BenchmarkTemplateParallelSimple(b *testing.B) { }) } -func BenchmarkValidateStructLarge(b *testing.B) { +func BenchmarkStructComplex(b *testing.B) { tFail := &TestString{ Required: "", @@ -101,7 +107,7 @@ func BenchmarkValidateStructLarge(b *testing.B) { } } -func BenchmarkTemplateParallelLarge(b *testing.B) { +func BenchmarkStructComplexParallel(b *testing.B) { tFail := &TestString{ Required: "", diff --git a/doc.go b/doc.go index f45ef34..df2db9f 100644 --- a/doc.go +++ b/doc.go @@ -1,59 +1,9 @@ /* -Package validator implements value validations for structs and individual fields based on tags. It can also handle Cross Field and Cross Struct validation for nested structs. +Package validator implements value validations for structs and individual fields based on tags. +It can also handle Cross Field and Cross Struct validation for nested structs and has the ability +to dive into arrays and maps of any type. -Validate - - validate := validator.New("validate", validator.BakedInValidators) - - errs := validate.Struct(//your struct) - valErr := validate.Field(field, "omitempty,min=1,max=10") - -A simple example usage: - - type UserDetail struct { - Details string `validate:"-"` - } - - type User struct { - Name string `validate:"required,max=60"` - PreferedName string `validate:"omitempty,max=60"` - Sub UserDetail - } - - user := &User { - Name: "", - } - - // errs will contain a hierarchical list of errors - // using the StructErrors struct - // or nil if no errors exist - errs := validate.Struct(user) - - // in this case 1 error Name is required - errs.Struct will be "User" - errs.StructErrors will be empty <-- fields that were structs - errs.Errors will have 1 error of type FieldError - - NOTE: Anonymous Structs - they don't have names so expect the Struct name - within StructErrors to be blank. - -Error Handling - -The error can be used like so - - fieldErr, _ := errs["Name"] - fieldErr.Field // "Name" - fieldErr.ErrorTag // "required" - -Both StructErrors and FieldError implement the Error interface but it's -intended use is for development + debugging, not a production error message. - - fieldErr.Error() // Field validation for "Name" failed on the "required" tag - errs.Error() - // Struct: User - // Field validation for "Name" failed on the "required" tag - -Why not a better error message? because this library intends for you to handle your own error messages +Why not a better error message? because this library intends for you to handle your own error messages. Why should I handle my own errors? Many reasons, for us building an internationalized application I needed to know the field and what validation failed so that I could provide an error in the users specific language. @@ -66,21 +16,12 @@ I needed to know the field and what validation failed so that I could provide an return "Translated string based on field" } -The hierarchical error structure is hard to work with sometimes.. Agreed Flatten function to the rescue! -Flatten will return a map of FieldError's but the field name will be namespaced. - - // if UserDetail Details field failed validation - Field will be "Sub.Details" - - // for Name - Field will be "Name" - Custom Functions Custom functions can be added - //Structure - func customFunc(top interface{}, current interface{}, field interface{}, param string) bool { + // Structure + func customFunc(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { if whatever { return false @@ -89,7 +30,7 @@ Custom functions can be added return true } - validate.AddFunction("custom tag name", customFunc) + validate.RegisterValidation("custom tag name", customFunc) // NOTES: using the same tag name as an existing function // will overwrite the existing one @@ -97,7 +38,7 @@ Cross Field Validation Cross Field Validation can be implemented, for example Start & End Date range validation - // NOTE: when calling validate.Struct(val) val will be the top level struct passed + // NOTE: when calling validate.Struct(val) topStruct will be the top level struct passed // into the function // when calling validate.FieldWithValue(val, field, tag) val will be // whatever you pass, struct, field... @@ -106,18 +47,7 @@ Cross Field Validation can be implemented, for example Start & End Date range va // Because of the specific requirements and field names within each persons project that // uses this library it is likely that custom functions will need to be created for your // Cross Field Validation needs, however there are some build in Generic Cross Field validations, - // see Baked In Validators and Tags below - - func isDateRangeValid(val interface{}, field interface{}, param string) bool { - - myStruct := val.(myStructType) - - if myStruct.Start.After(field.(time.Time)) { - return false - } - - return true - } + // see Baked In Validators eqfield, nefield, gtfield, gtefield, ltfield, ltefield and Tags below Multiple Validators @@ -135,7 +65,7 @@ Bad Validator definitions are not handled by the library Field `validate:"min=10,max=0"` } - // this definition of min max will never validate + // this definition of min max will never succeed Baked In Validators and Tags @@ -148,6 +78,11 @@ included within the parameter i.e. excludesall=, you will need to use the UTF-8 representation 0x2C, which is replaced in the code as a comma, so the above will become excludesall=0x2C +NOTE3: pipe is the default separator of or validation tags, if you wish to have a pipe +included within the parameter i.e. excludesall=| you will need to use the UTF-8 hex +representation 0x7C, which is replaced in the code as a pipe, so the above will +become excludesall=0x7C + Here is a list of the current built in validators: - @@ -162,14 +97,14 @@ Here is a list of the current built in validators: structonly When a field that is a nest struct in encountered and contains this flag - any validation on the nested struct such as "required" will be run, but - none of the nested struct fields will be validated. This is usefull if - inside of you program you know the struct will be valid, but need to - verify it has been assigned. + any validation on the nested struct will be run, but none of the nested + struct fields will be validated. This is usefull if inside of you program + you know the struct will be valid, but need to verify it has been assigned. + NOTE: only "required" and "omitempty" can be used on a struct itself. omitempty Allows conditional validation, for example if a field is not set with - a value (Determined by the required validator) then other validation + a value (Determined by the "required" validator) then other validation such as min or max won't run, but if a value is set validation will run. (Usage: omitempty) diff --git a/examples/simple.go b/examples/simple.go index 59cb1a9..98dabed 100644 --- a/examples/simple.go +++ b/examples/simple.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "gopkg.in/bluesuncorp/validator.v5" + "gopkg.in/bluesuncorp/validator.v6" ) // User contains user information @@ -28,7 +28,12 @@ var validate *validator.Validate func main() { - validate = validator.New("validate", validator.BakedInValidators) + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate = validator.New(config) address := &Address{ Street: "Eavesdown Docks", @@ -45,31 +50,14 @@ func main() { Addresses: []*Address{address}, } - // returns nil or *StructErrors + // returns nil or ValidationErrors ( map[string]*FieldError ) errs := validate.Struct(user) if errs != nil { - // err will be of type *FieldError - err := errs.Errors["Age"] - fmt.Println(err.Error()) // output: Field validation for "Age" failed on the "lte" tag - fmt.Println(err.Field) // output: Age - fmt.Println(err.Tag) // output: lte - fmt.Println(err.Kind) // output: uint8 - fmt.Println(err.Type) // output: uint8 - fmt.Println(err.Param) // output: 130 - fmt.Println(err.Value) // output: 135 - - // or if you prefer you can use the Flatten function - // NOTE: I find this usefull when using a more hard static approach of checking field errors. - // The above, is best for passing to some generic code to say parse the errors. i.e. I pass errs - // to a routine which loops through the errors, creates and translates the error message into the - // users locale and returns a map of map[string]string // field and error which I then use - // within the HTML rendering. - - flat := errs.Flatten() - fmt.Println(flat) // output: map[Age:Field validation for "Age" failed on the "lte" tag Addresses[0].Address.City:Field validation for "City" failed on the "required" tag] - err = flat["Addresses[0].Address.City"] + 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"] fmt.Println(err.Field) // output: City fmt.Println(err.Tag) // output: required fmt.Println(err.Kind) // output: string diff --git a/examples_test.go b/examples_test.go index c5dd351..9d01450 100644 --- a/examples_test.go +++ b/examples_test.go @@ -7,36 +7,28 @@ import ( ) func ExampleValidate_new() { - validator.New("validate", validator.BakedInValidators) -} - -func ExampleValidate_addFunction() { - // This should be stored somewhere globally - var validate *validator.Validate - - validate = validator.New("validate", validator.BakedInValidators) - - fn := func(top interface{}, current interface{}, field interface{}, param string) bool { - return field.(string) == "hello" + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, } - validate.AddFunction("valueishello", fn) - - message := "hello" - err := validate.Field(message, "valueishello") - fmt.Println(err) - //Output: - // + validator.New(config) } func ExampleValidate_field() { // This should be stored somewhere globally var validate *validator.Validate - validate = validator.New("validate", validator.BakedInValidators) + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate = validator.New(config) i := 0 - err := validate.Field(i, "gt=1,lte=10") + errs := validate.Field(i, "gt=1,lte=10") + err := errs[""] 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 @@ -56,7 +48,12 @@ func ExampleValidate_struct() { // This should be stored somewhere globally var validate *validator.Validate - validate = validator.New("validate", validator.BakedInValidators) + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate = validator.New(config) type ContactInformation struct { Phone string `validate:"required"` @@ -83,10 +80,10 @@ func ExampleValidate_struct() { ContactInformation: []*ContactInformation{contactInfo}, } - structError := validate.Struct(user) - for _, fieldError := range structError.Errors { - fmt.Println(fieldError.Field) // Phone - fmt.Println(fieldError.Tag) // required + errs := validate.Struct(user) + for _, v := range errs { + fmt.Println(v.Field) // Phone + fmt.Println(v.Tag) // required //... and so forth //Output: //Phone diff --git a/regexes.go b/regexes.go index d3e8d80..d061b03 100644 --- a/regexes.go +++ b/regexes.go @@ -58,7 +58,6 @@ var ( sSNRegex = regexp.MustCompile(sSNRegexString) ) -func matchesRegex(regex *regexp.Regexp, field interface{}) bool { - fieldAsString := field.(string) //this will panic inherently - return regex.MatchString(fieldAsString) +func matchesRegex(regex *regexp.Regexp, value string) bool { + return regex.MatchString(value) } diff --git a/validator.go b/validator.go index ccd2a55..6c44f2b 100644 --- a/validator.go +++ b/validator.go @@ -21,969 +21,464 @@ import ( const ( utf8HexComma = "0x2C" + utf8Pipe = "0x7C" tagSeparator = "," orSeparator = "|" - noValidationTag = "-" tagKeySeparator = "=" structOnlyTag = "structonly" omitempty = "omitempty" - required = "required" - fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag" - sliceErrMsg = "Field validation for \"%s\" failed at index \"%d\" with error(s): %s" - mapErrMsg = "Field validation for \"%s\" failed on key \"%v\" with error(s): %s" - structErrMsg = "Struct:%s\n" + skipValidationTag = "-" diveTag = "dive" + fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" arrayIndexFieldName = "%s[%d]" mapIndexFieldName = "%s[%v]" + invalidValidation = "Invalid validation tag on field %s" + undefinedValidation = "Undefined validation function on field %s" ) -var structPool *sync.Pool +var ( + timeType = reflect.TypeOf(time.Time{}) + timePtrType = reflect.TypeOf(&time.Time{}) + errsPool = &sync.Pool{New: newValidationErrors} + tagsCache = &tagCacheMap{m: map[string][]*tagCache{}} +) -// returns new *StructErrors to the pool -func newStructErrors() interface{} { - return &StructErrors{ - Errors: map[string]*FieldError{}, - StructErrors: map[string]*StructErrors{}, - } +// returns new ValidationErrors to the pool +func newValidationErrors() interface{} { + return map[string]*FieldError{} } -type cachedTags struct { - keyVals [][]string +type tagCache struct { + tagVals [][]string isOrVal bool } -type cachedField struct { - index int - name string - tags []*cachedTags - tag string - kind reflect.Kind - typ reflect.Type - isTime bool - isSliceOrArray bool - isMap bool - isTimeSubtype bool - sliceSubtype reflect.Type - mapSubtype reflect.Type - sliceSubKind reflect.Kind - mapSubKind reflect.Kind - dive bool - diveTag string -} - -type cachedStruct struct { - children int - name string - kind reflect.Kind - fields []*cachedField -} - -type structsCacheMap struct { +type tagCacheMap struct { lock sync.RWMutex - m map[reflect.Type]*cachedStruct + m map[string][]*tagCache } -func (s *structsCacheMap) Get(key reflect.Type) (*cachedStruct, bool) { +func (s *tagCacheMap) Get(key string) ([]*tagCache, bool) { s.lock.RLock() defer s.lock.RUnlock() value, ok := s.m[key] return value, ok } -func (s *structsCacheMap) Set(key reflect.Type, value *cachedStruct) { +func (s *tagCacheMap) Set(key string, value []*tagCache) { s.lock.Lock() defer s.lock.Unlock() s.m[key] = value } -var structCache = &structsCacheMap{m: map[reflect.Type]*cachedStruct{}} - -type fieldsCacheMap struct { - lock sync.RWMutex - m map[string][]*cachedTags +// Validate contains the validator settings passed in using the Config struct +type Validate struct { + config Config } -func (s *fieldsCacheMap) Get(key string) ([]*cachedTags, bool) { - s.lock.RLock() - defer s.lock.RUnlock() - value, ok := s.m[key] - return value, ok +// 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 } -func (s *fieldsCacheMap) Set(key string, value []*cachedTags) { - s.lock.Lock() - defer s.lock.Unlock() - s.m[key] = value -} +// Func accepts all values needed for file and cross field validation +// topStruct = top level struct when validating by struct otherwise nil +// currentStruct = current level struct when validating by struct otherwise optional comparison value +// field = field value for validation +// param = parameter used in validation i.e. gt=0 param would be 0 +type Func func(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool + +// ValidationErrors is a type of map[string]*FieldError +// it exists to allow for multiple errors to be passed from this library +// and yet still subscribe to the error interface +type ValidationErrors map[string]*FieldError + +// Error is intended for use in development + debugging and not intended to be a production error message. +// It allows ValidationErrors to subscribe to the Error interface. +// All information to create an error message specific to your application is contained within +// the FieldError found within the ValidationErrors map +func (ve ValidationErrors) Error() string { + + buff := bytes.NewBufferString("") + + for key, err := range ve { + buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag)) + buff.WriteString("\n") + } -var fieldsCache = &fieldsCacheMap{m: map[string][]*cachedTags{}} + return strings.TrimSpace(buff.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{} - IsPlaceholderErr bool - IsSliceOrArray bool - IsMap bool - SliceOrArrayErrs map[int]error // counld be FieldError, StructErrors - MapErrs map[interface{}]error // counld be FieldError, StructErrors + Field string + Tag string + Kind reflect.Kind + Type reflect.Type + Param string + Value interface{} } -// This is intended for use in development + debugging and not intended to be a production error message. -// it also allows FieldError to be used as an Error interface -func (e *FieldError) Error() string { - - if e.IsPlaceholderErr { - - buff := bytes.NewBufferString("") - - if e.IsSliceOrArray { - - for j, err := range e.SliceOrArrayErrs { - buff.WriteString("\n") - buff.WriteString(fmt.Sprintf(sliceErrMsg, e.Field, j, "\n"+err.Error())) - } - - } else if e.IsMap { - - for key, err := range e.MapErrs { - buff.WriteString(fmt.Sprintf(mapErrMsg, e.Field, key, "\n"+err.Error())) - } - } - - return strings.TrimSpace(buff.String()) - } - - return fmt.Sprintf(fieldErrMsg, e.Field, e.Tag) +// New creates a new Validate instance for use. +func New(config Config) *Validate { + return &Validate{config: config} } -// Flatten flattens the FieldError hierarchical structure into a flat namespace style field name -// for those that want/need it. -// This is now needed because of the new dive functionality -func (e *FieldError) Flatten() map[string]*FieldError { - - errs := map[string]*FieldError{} - - if e.IsPlaceholderErr { - - if e.IsSliceOrArray { - for key, err := range e.SliceOrArrayErrs { - - fe, ok := err.(*FieldError) - - if ok { - - if flat := fe.Flatten(); flat != nil && len(flat) > 0 { - for k, v := range flat { - if fe.IsPlaceholderErr { - errs[fmt.Sprintf("[%#v]%s", key, k)] = v - } else { - errs[fmt.Sprintf("[%#v]", key)] = v - } - - } - } - } else { - - se := err.(*StructErrors) - - if flat := se.Flatten(); flat != nil && len(flat) > 0 { - for k, v := range flat { - errs[fmt.Sprintf("[%#v].%s.%s", key, se.Struct, k)] = v - } - } - } - } - } - - if e.IsMap { - for key, err := range e.MapErrs { - - fe, ok := err.(*FieldError) - - if ok { - - if flat := fe.Flatten(); flat != nil && len(flat) > 0 { - for k, v := range flat { - if fe.IsPlaceholderErr { - errs[fmt.Sprintf("[%#v]%s", key, k)] = v - } else { - errs[fmt.Sprintf("[%#v]", key)] = v - } - } - } - } else { - - se := err.(*StructErrors) - - if flat := se.Flatten(); flat != nil && len(flat) > 0 { - for k, v := range flat { - errs[fmt.Sprintf("[%#v].%s.%s", key, se.Struct, k)] = 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 +func (v *Validate) RegisterValidation(key string, f Func) error { - return errs + if len(key) == 0 { + return errors.New("Function Key cannot be empty") } - errs[e.Field] = e + if f == nil { + return errors.New("Function cannot be empty") + } - return errs -} + v.config.ValidationFuncs[key] = f -// StructErrors is hierarchical list of field and struct validation errors -// for a non hierarchical representation please see the Flatten method for StructErrors -type StructErrors struct { - // Name of the Struct - Struct string - // Struct Field Errors - Errors map[string]*FieldError - // Struct Fields of type struct and their errors - // key = Field Name of current struct, but internally Struct will be the actual struct name unless anonymous struct, it will be blank - StructErrors map[string]*StructErrors + return nil } -// This is intended for use in development + debugging and not intended to be a production error message. -// it also allows StructErrors to be used as an Error interface -func (e *StructErrors) Error() string { - buff := bytes.NewBufferString(fmt.Sprintf(structErrMsg, e.Struct)) - - for _, err := range e.Errors { - buff.WriteString(err.Error()) - buff.WriteString("\n") - } - - for _, err := range e.StructErrors { - buff.WriteString(err.Error()) - } +// Field validates a single field using tag style validation and returns ValidationErrors +// 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 { - return strings.TrimSpace(buff.String()) -} + errs := errsPool.Get().(map[string]*FieldError) + fieldVal := reflect.ValueOf(field) -// Flatten flattens the StructErrors hierarchical structure into a flat namespace style field name -// for those that want/need it -func (e *StructErrors) Flatten() map[string]*FieldError { + v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "") - if e == nil { + if len(errs) == 0 { + errsPool.Put(errs) return nil } - errs := map[string]*FieldError{} - - for _, f := range e.Errors { - - if flat := f.Flatten(); flat != nil && len(flat) > 0 { - - for k, fe := range flat { - - if f.IsPlaceholderErr { - errs[f.Field+k] = fe - } else { - errs[k] = fe - } - } - } - } + return errs +} - for key, val := range e.StructErrors { +// FieldWithValue validates a single field, against another fields value using tag style validation and returns ValidationErrors +// 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 { - otherErrs := val.Flatten() + errs := errsPool.Get().(map[string]*FieldError) + topVal := reflect.ValueOf(val) - for _, f2 := range otherErrs { + v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "") - f2.Field = fmt.Sprintf("%s.%s", key, f2.Field) - errs[f2.Field] = f2 - } + if len(errs) == 0 { + errsPool.Put(errs) + return nil } return errs } -// Func accepts all values needed for file and cross field validation -// top = top level struct when validating by struct otherwise nil -// current = current level struct when validating by struct otherwise optional comparison value -// f = field value for validation -// param = parameter used in validation i.e. gt=0 param would be 0 -type Func func(top interface{}, current interface{}, f interface{}, param string) bool - -// Validate implements the Validate Struct -// NOTE: Fields within are not thread safe and that is on purpose -// Functions and Tags should all be predifined before use, so subscribe to the philosiphy -// or make it thread safe on your end -type Validate struct { - // tagName being used. - tagName string - // validateFuncs is a map of validation functions and the tag keys - validationFuncs map[string]Func -} +// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. +func (v *Validate) Struct(current interface{}) ValidationErrors { -// New creates a new Validate instance for use. -func New(tagName string, funcs map[string]Func) *Validate { + errs := errsPool.Get().(map[string]*FieldError) + sv := reflect.ValueOf(current) - structPool = &sync.Pool{New: newStructErrors} + v.tranverseStruct(sv, sv, sv, "", errs, true) - return &Validate{ - tagName: tagName, - validationFuncs: funcs, + if len(errs) == 0 { + errsPool.Put(errs) + return nil } -} -// SetTag sets tagName of the Validator to one of your choosing after creation -// perhaps to dodge a tag name conflict in a specific section of code -// NOTE: this method is not thread-safe -func (v *Validate) SetTag(tagName string) { - v.tagName = tagName -} - -// SetMaxStructPoolSize sets the struct pools max size. this may be usefull for fine grained -// performance tuning towards your application, however, the default should be fine for -// nearly all cases. only increase if you have a deeply nested struct structure. -// NOTE: this method is not thread-safe -// NOTE: this is only here to keep compatibility with v5, in v6 the method will be removed -func (v *Validate) SetMaxStructPoolSize(max int) { - structPool = &sync.Pool{New: newStructErrors} + return errs } -// AddFunction adds a validation Func to a Validate's map of validators denoted by the key -// NOTE: if the key already exists, it will get replaced. -// NOTE: this method is not thread-safe -func (v *Validate) AddFunction(key string, f Func) error { +// tranverseStruct traverses a structs fields and then passes them to be validated by traverseField +func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, useStructName bool) { - if len(key) == 0 { - return errors.New("Function Key cannot be empty") + if current.Kind() == reflect.Ptr && !current.IsNil() { + current = current.Elem() } - if f == nil { - return errors.New("Function cannot be empty") + if current.Kind() != reflect.Struct && current.Kind() != reflect.Interface { + panic("value passed for validation is not a struct") } - v.validationFuncs[key] = f + typ := current.Type() - return nil -} + if useStructName { + errPrefix += typ.Name() + "." + } -// Struct validates a struct, even it's nested structs, and returns a struct containing the errors -// NOTE: Nested Arrays, or Maps of structs do not get validated only the Array or Map itself; the reason is that there is no good -// way to represent or report which struct within the array has the error, besides can validate the struct prior to adding it to -// the Array or Map. -func (v *Validate) Struct(s interface{}) *StructErrors { + numFields := current.NumField() - return v.structRecursive(s, s, s) -} + var fld reflect.StructField -// structRecursive validates a struct recursivly and passes the top level and current struct around for use in validator functions and returns a struct containing the errors -func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}) *StructErrors { - - structValue := reflect.ValueOf(s) + for i := 0; i < numFields; i++ { + fld = typ.Field(i) - if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { - return v.structRecursive(top, current, structValue.Elem().Interface()) - } + if !unicode.IsUpper(rune(fld.Name[0])) { + continue + } - if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface { - panic("interface passed for validation is not a struct") + v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name) } +} - structType := reflect.TypeOf(s) - - var structName string - var numFields int - var cs *cachedStruct - var isCached bool - - cs, isCached = structCache.Get(structType) +// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options +func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string) { - if isCached { - structName = cs.name - numFields = cs.children - } else { - structName = structType.Name() - numFields = structValue.NumField() - cs = &cachedStruct{name: structName, children: numFields} + if tag == skipValidationTag { + return } - validationErrors := structPool.Get().(*StructErrors) - validationErrors.Struct = structName + kind := current.Kind() - for i := 0; i < numFields; i++ { + if kind == reflect.Ptr && !current.IsNil() { + current = current.Elem() + kind = current.Kind() + } - var valueField reflect.Value - var cField *cachedField - var typeField reflect.StructField + typ := current.Type() - if isCached { - cField = cs.fields[i] - valueField = structValue.Field(cField.index) + // this also allows for tags 'required' and 'omitempty' to be used on + // nested struct fields because when len(tags) > 0 below and the value is nil + // then required failes and we check for omitempty just before that + if (kind == reflect.Ptr || kind == reflect.Interface) && current.IsNil() { - if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - valueField = valueField.Elem() - } - } else { - valueField = structValue.Field(i) + if strings.Contains(tag, omitempty) { + return + } - if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - valueField = valueField.Elem() - } + tags := strings.Split(tag, tagSeparator) - typeField = structType.Field(i) + if len(tags) > 0 { - cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName), isTime: (valueField.Type() == reflect.TypeOf(time.Time{}) || valueField.Type() == reflect.TypeOf(&time.Time{}))} + var param string + vals := strings.SplitN(tags[0], tagKeySeparator, 2) - if cField.tag == noValidationTag { - cs.children-- - continue + if len(vals) > 1 { + param = vals[1] } - // if no validation and not a struct (which may containt fields for validation) - if cField.tag == "" && ((valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface) || valueField.Type() == reflect.TypeOf(time.Time{})) { - cs.children-- - continue + errs[errPrefix+name] = &FieldError{ + Field: name, + Tag: vals[0], + Param: param, + Value: current.Interface(), + Kind: kind, + Type: typ, } - cField.name = typeField.Name - cField.kind = valueField.Kind() - cField.typ = valueField.Type() - } - - // this can happen if the first cache value was nil - // but the second actually has a value - if cField.kind == reflect.Ptr { - cField.kind = valueField.Kind() + return } + } - switch cField.kind { - - case reflect.Struct, reflect.Interface: - - if !unicode.IsUpper(rune(cField.name[0])) { - cs.children-- - continue - } - - if cField.isTime { - - if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { - validationErrors.Errors[fieldError.Field] = fieldError - // free up memory reference - fieldError = nil - } - - } else { - - if strings.Contains(cField.tag, structOnlyTag) { - cs.children-- - continue - } - - if (valueField.Kind() == reflect.Ptr || cField.kind == reflect.Interface) && valueField.IsNil() { - - if strings.Contains(cField.tag, omitempty) { - goto CACHEFIELD - } - - tags := strings.Split(cField.tag, tagSeparator) - - if len(tags) > 0 { - - var param string - vals := strings.SplitN(tags[0], tagKeySeparator, 2) - - if len(vals) > 1 { - param = vals[1] - } - - validationErrors.Errors[cField.name] = &FieldError{ - Field: cField.name, - Tag: vals[0], - Param: param, - Value: valueField.Interface(), - Kind: valueField.Kind(), - Type: valueField.Type(), - } - - goto CACHEFIELD - } - } - - // if we get here, the field is interface and could be a struct or a field - // and we need to check the inner type and validate - if cField.kind == reflect.Interface { - - valueField = valueField.Elem() - - if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - valueField = valueField.Elem() - } + switch kind { + case reflect.Struct, reflect.Interface: - if valueField.Kind() == reflect.Struct { - goto VALIDATESTRUCT - } + if kind == reflect.Interface { - // sending nil for cField as it was type interface and could be anything - // each time and so must be calculated each time and can't be cached reliably - if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, nil); fieldError != nil { - validationErrors.Errors[fieldError.Field] = fieldError - // free up memory reference - fieldError = nil - } + current = current.Elem() + kind = current.Kind() - goto CACHEFIELD - } - - VALIDATESTRUCT: - if structErrors := v.structRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil { - validationErrors.StructErrors[cField.name] = structErrors - // free up memory map no longer needed - structErrors = nil - } + if kind == reflect.Ptr && !current.IsNil() { + current = current.Elem() + kind = current.Kind() } - case reflect.Slice, reflect.Array: - cField.isSliceOrArray = true - cField.sliceSubtype = cField.typ.Elem() - cField.isTimeSubtype = (cField.sliceSubtype == reflect.TypeOf(time.Time{}) || cField.sliceSubtype == reflect.TypeOf(&time.Time{})) - cField.sliceSubKind = cField.sliceSubtype.Kind() - - if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { - validationErrors.Errors[fieldError.Field] = fieldError - // free up memory reference - fieldError = nil - } - - case reflect.Map: - cField.isMap = true - cField.mapSubtype = cField.typ.Elem() - cField.isTimeSubtype = (cField.mapSubtype == reflect.TypeOf(time.Time{}) || cField.mapSubtype == reflect.TypeOf(&time.Time{})) - cField.mapSubKind = cField.mapSubtype.Kind() - - if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { - validationErrors.Errors[fieldError.Field] = fieldError - // free up memory reference - fieldError = nil - } + // changed current, so have to get inner type again + typ = current.Type() - default: - if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { - validationErrors.Errors[fieldError.Field] = fieldError - // free up memory reference - fieldError = nil + if kind != reflect.Struct { + goto FALLTHROUGH } } - CACHEFIELD: - if !isCached { - cs.fields = append(cs.fields, cField) - } - } - - structCache.Set(structType, cs) + if typ != timeType && typ != timePtrType { - if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 { - structPool.Put(validationErrors) - return nil - } - - return validationErrors -} + if kind == reflect.Struct { -// Field allows validation of a single field, still using tag style validation to check multiple errors -func (v *Validate) Field(f interface{}, tag string) *FieldError { - return v.FieldWithValue(nil, f, tag) -} - -// FieldWithValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors -func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError { - return v.fieldWithNameAndValue(nil, val, f, tag, "", true, nil) -} - -func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, tag string, name string, isSingleField bool, cacheField *cachedField) *FieldError { - - var cField *cachedField - var isCached bool - var valueField reflect.Value - - // This is a double check if coming from validate.Struct but need to be here in case function is called directly - if tag == noValidationTag || tag == "" { - return nil - } - - if strings.Contains(tag, omitempty) && !hasValue(val, current, f, "") { - return nil - } - - valueField = reflect.ValueOf(f) - - if cacheField == nil { + // required passed validationa above so stop here + // if only validating the structs existance. + if strings.Contains(tag, structOnlyTag) { + return + } - if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - valueField = valueField.Elem() - f = valueField.Interface() + v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false) + return + } } - - cField = &cachedField{name: name, kind: valueField.Kind(), tag: tag, typ: valueField.Type()} - - switch cField.kind { - case reflect.Slice, reflect.Array: - isSingleField = false // cached tags mean nothing because it will be split up while diving - cField.isSliceOrArray = true - cField.sliceSubtype = cField.typ.Elem() - cField.isTimeSubtype = (cField.sliceSubtype == reflect.TypeOf(time.Time{}) || cField.sliceSubtype == reflect.TypeOf(&time.Time{})) - cField.sliceSubKind = cField.sliceSubtype.Kind() - case reflect.Map: - isSingleField = false // cached tags mean nothing because it will be split up while diving - cField.isMap = true - cField.mapSubtype = cField.typ.Elem() - cField.isTimeSubtype = (cField.mapSubtype == reflect.TypeOf(time.Time{}) || cField.mapSubtype == reflect.TypeOf(&time.Time{})) - cField.mapSubKind = cField.mapSubtype.Kind() + FALLTHROUGH: + fallthrough + default: + if len(tag) == 0 { + return } - } else { - cField = cacheField } - switch cField.kind { + tags, isCached := tagsCache.Get(tag) - case reflect.Struct, reflect.Interface, reflect.Invalid: + if !isCached { - if cField.typ != reflect.TypeOf(time.Time{}) { - panic("Invalid field passed to fieldWithNameAndValue") - } - } + tags = []*tagCache{} - if len(cField.tags) == 0 { + for _, t := range strings.Split(tag, tagSeparator) { - if isSingleField { - cField.tags, isCached = fieldsCache.Get(tag) - } + if t == diveTag { + tags = append(tags, &tagCache{tagVals: [][]string{[]string{t}}}) + break + } - if !isCached { + // 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) - for _, t := range strings.Split(tag, tagSeparator) { + var key string + var param string - if t == diveTag { + for i, val := range orVals { + vals := strings.SplitN(val, tagKeySeparator, 2) + key = vals[0] - cField.dive = true - cField.diveTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",") - break + if len(key) == 0 { + panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, name))) } - orVals := strings.Split(t, orSeparator) - cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))} - cField.tags = append(cField.tags, cTag) - - for i, val := range orVals { - vals := strings.SplitN(val, tagKeySeparator, 2) - - key := strings.TrimSpace(vals[0]) - - if len(key) == 0 { - panic(fmt.Sprintf("Invalid validation tag on field %s", name)) - } - - param := "" - if len(vals) > 1 { - param = strings.Replace(vals[1], utf8HexComma, ",", -1) - } - - cTag.keyVals[i] = []string{key, param} + if len(vals) > 1 { + param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1) } - } - if isSingleField { - fieldsCache.Set(cField.tag, cField.tags) + cTag.tagVals[i] = []string{key, param} } } + tagsCache.Set(tag, tags) } - var fieldErr *FieldError - var err error - - for _, cTag := range cField.tags { + var dive bool + var diveSubTag string - if cTag.isOrVal { + for _, cTag := range tags { - errTag := "" - - for _, val := range cTag.keyVals { - - fieldErr, err = v.fieldWithNameAndSingleTag(val, current, f, val[0], val[1], name) + if cTag.tagVals[0][0] == diveTag { + dive = true + diveSubTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",") + break + } - if err == nil { - return nil - } + if cTag.tagVals[0][0] == omitempty { - errTag += orSeparator + fieldErr.Tag + if !hasValue(topStruct, currentStruct, current, typ, kind, "") { + return } - - errTag = strings.TrimLeft(errTag, orSeparator) - - fieldErr.Tag = errTag - fieldErr.Kind = cField.kind - fieldErr.Type = cField.typ - - return fieldErr + continue } - if fieldErr, err = v.fieldWithNameAndSingleTag(val, current, f, cTag.keyVals[0][0], cTag.keyVals[0][1], name); err != nil { - - fieldErr.Kind = cField.kind - fieldErr.Type = cField.typ - - return fieldErr + if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, errs, cTag, name) { + return } } - if cField.dive { - - if cField.isSliceOrArray { - - if errs := v.traverseSliceOrArray(val, current, valueField, cField); errs != nil && len(errs) > 0 { - - return &FieldError{ - Field: cField.name, - Kind: cField.kind, - Type: cField.typ, - Value: f, - IsPlaceholderErr: true, - IsSliceOrArray: true, - SliceOrArrayErrs: errs, - } - } - - } else if cField.isMap { - if errs := v.traverseMap(val, current, valueField, cField); errs != nil && len(errs) > 0 { - - return &FieldError{ - Field: cField.name, - Kind: cField.kind, - Type: cField.typ, - Value: f, - IsPlaceholderErr: true, - IsMap: true, - MapErrs: errs, - } - } - } else { + if dive { + // traverse slice or map here + // or panic ;) + switch kind { + case reflect.Slice, reflect.Array: + v.traverseSlice(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name) + case reflect.Map: + v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name) + default: // throw error, if not a slice or map then should not have gotten here + // bad dive tag panic("dive error! can't dive on a non slice or map") } } - - return nil } -func (v *Validate) traverseMap(val interface{}, current interface{}, valueField reflect.Value, cField *cachedField) map[interface{}]error { - - errs := map[interface{}]error{} +// traverseSlice traverses a Slice or Array's elements and passes them to traverseField for validation +func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string) { - for _, key := range valueField.MapKeys() { + for i := 0; i < current.Len(); i++ { - idxField := valueField.MapIndex(key) + idxField := current.Index(i) - if cField.mapSubKind == reflect.Ptr && !idxField.IsNil() { + if idxField.Kind() == reflect.Ptr && !idxField.IsNil() { idxField = idxField.Elem() - cField.mapSubKind = idxField.Kind() } - switch cField.mapSubKind { - case reflect.Struct, reflect.Interface: - - if cField.isTimeSubtype { - - if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()), false, nil); fieldError != nil { - errs[key.Interface()] = fieldError - } - - continue - } - - if (idxField.Kind() == reflect.Ptr || idxField.Kind() == reflect.Interface) && idxField.IsNil() { - - if strings.Contains(cField.diveTag, omitempty) { - continue - } - - tags := strings.Split(cField.diveTag, tagSeparator) - - if len(tags) > 0 { - - var param string - vals := strings.SplitN(tags[0], tagKeySeparator, 2) - - if len(vals) > 1 { - param = vals[1] - } - - errs[key.Interface()] = &FieldError{ - Field: fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()), - Tag: vals[0], - Param: param, - Value: idxField.Interface(), - Kind: idxField.Kind(), - Type: cField.mapSubtype, - } - } - - continue - } - - // if we get here, the field is interface and could be a struct or a field - // and we need to check the inner type and validate - if idxField.Kind() == reflect.Interface { - - idxField = idxField.Elem() - - if idxField.Kind() == reflect.Ptr && !idxField.IsNil() { - idxField = idxField.Elem() - } - - if idxField.Kind() == reflect.Struct { - goto VALIDATESTRUCT - } - - // sending nil for cField as it was type interface and could be anything - // each time and so must be calculated each time and can't be cached reliably - if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()), false, nil); fieldError != nil { - errs[key.Interface()] = fieldError - } - - continue - } - - VALIDATESTRUCT: - if structErrors := v.structRecursive(val, current, idxField.Interface()); structErrors != nil { - errs[key.Interface()] = structErrors - } - - default: - if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()), false, nil); fieldError != nil { - errs[key.Interface()] = fieldError - } - } + v.traverseField(topStruct, currentStruct, idxField, errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i)) } - - return errs } -func (v *Validate) traverseSliceOrArray(val interface{}, current interface{}, valueField reflect.Value, cField *cachedField) map[int]error { - - errs := map[int]error{} +// traverseMap traverses a map's elements and passes them to traverseField for validation +func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string) { - for i := 0; i < valueField.Len(); i++ { + for _, key := range current.MapKeys() { - idxField := valueField.Index(i) + idxField := current.MapIndex(key) - if cField.sliceSubKind == reflect.Ptr && !idxField.IsNil() { + if idxField.Kind() == reflect.Ptr && !idxField.IsNil() { idxField = idxField.Elem() - cField.sliceSubKind = idxField.Kind() } - switch cField.sliceSubKind { - case reflect.Struct, reflect.Interface: - - if cField.isTimeSubtype { - - if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(arrayIndexFieldName, cField.name, i), false, nil); fieldError != nil { - errs[i] = fieldError - } - - continue - } - - if (idxField.Kind() == reflect.Ptr || idxField.Kind() == reflect.Interface) && idxField.IsNil() { - - if strings.Contains(cField.diveTag, omitempty) { - continue - } - - tags := strings.Split(cField.diveTag, tagSeparator) - - if len(tags) > 0 { - - var param string - vals := strings.SplitN(tags[0], tagKeySeparator, 2) - - if len(vals) > 1 { - param = vals[1] - } - - errs[i] = &FieldError{ - Field: fmt.Sprintf(arrayIndexFieldName, cField.name, i), - Tag: vals[0], - Param: param, - Value: idxField.Interface(), - Kind: idxField.Kind(), - Type: cField.sliceSubtype, - } - } - - continue - } + v.traverseField(topStruct, currentStruct, idxField, errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface())) + } +} - // if we get here, the field is interface and could be a struct or a field - // and we need to check the inner type and validate - if idxField.Kind() == reflect.Interface { +// 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 { - idxField = idxField.Elem() + if cTag.isOrVal { - if idxField.Kind() == reflect.Ptr && !idxField.IsNil() { - idxField = idxField.Elem() - } + errTag := "" - if idxField.Kind() == reflect.Struct { - goto VALIDATESTRUCT - } - - // sending nil for cField as it was type interface and could be anything - // each time and so must be calculated each time and can't be cached reliably - if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(arrayIndexFieldName, cField.name, i), false, nil); fieldError != nil { - errs[i] = fieldError - } + for _, val := range cTag.tagVals { - continue + valFunc, ok := v.config.ValidationFuncs[val[0]] + if !ok { + panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } - VALIDATESTRUCT: - if structErrors := v.structRecursive(val, current, idxField.Interface()); structErrors != nil { - errs[i] = structErrors + if valFunc(topStruct, currentStruct, current, currentType, currentKind, val[1]) { + return false } - default: - if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(arrayIndexFieldName, cField.name, i), false, nil); fieldError != nil { - errs[i] = fieldError - } + errTag += orSeparator + val[0] } - } - - return errs -} -func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, key string, param string, name string) (*FieldError, error) { + errs[errPrefix+name] = &FieldError{ + Field: name, + Tag: errTag[1:], + Value: current.Interface(), + Type: currentType, + Kind: currentKind, + } - // OK to continue because we checked it's existance before getting into this loop - if key == omitempty { - return nil, nil + return true } - valFunc, ok := v.validationFuncs[key] + valFunc, ok := v.config.ValidationFuncs[cTag.tagVals[0][0]] if !ok { - panic(fmt.Sprintf("Undefined validation function on field %s", name)) + panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } - if err := valFunc(val, current, f, param); err { - return nil, nil + if valFunc(topStruct, currentStruct, current, currentType, currentKind, cTag.tagVals[0][1]) { + return false } - return &FieldError{ + errs[errPrefix+name] = &FieldError{ Field: name, - Tag: key, - Value: f, - Param: param, - }, errors.New(key) + Tag: cTag.tagVals[0][0], + Value: current.Interface(), + Param: cTag.tagVals[0][1], + Type: currentType, + Kind: currentKind, + } + + return true } diff --git a/validator_test.go b/validator_test.go index 8bfff9b..f9a042c 100644 --- a/validator_test.go +++ b/validator_test.go @@ -108,7 +108,7 @@ type TestSlice struct { OmitEmpty []int `validate:"omitempty,min=1,max=10"` } -var validate = New("validate", BakedInValidators) +var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators}) func IsEqual(t *testing.T, val1, val2 interface{}) bool { v1 := reflect.ValueOf(val1) @@ -126,6 +126,20 @@ func IsEqual(t *testing.T, val1, val2 interface{}) bool { return true } + switch v1.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + if v1.IsNil() { + v1 = reflect.ValueOf(nil) + } + } + + switch v2.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + if v2.IsNil() { + v2 = reflect.ValueOf(nil) + } + } + v1Underlying := reflect.Zero(reflect.TypeOf(v1)).Interface() v2Underlying := reflect.Zero(reflect.TypeOf(v2)).Interface() @@ -144,13 +158,16 @@ func IsEqual(t *testing.T, val1, val2 interface{}) bool { } CASE1: + // fmt.Println("CASE 1") return reflect.DeepEqual(v1.Interface(), v2.Interface()) - CASE2: + // fmt.Println("CASE 2") return reflect.DeepEqual(v1.Interface(), v2) CASE3: + // fmt.Println("CASE 3") return reflect.DeepEqual(v1, v2.Interface()) CASE4: + // fmt.Println("CASE 4") return reflect.DeepEqual(v1, v2) } @@ -203,32 +220,25 @@ func PanicMatchesSkip(t *testing.T, skip int, fn func(), matches string) { fn() } -func AssertStruct(t *testing.T, s *StructErrors, structFieldName string, expectedStructName string) *StructErrors { +func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) { - val, ok := s.StructErrors[structFieldName] - EqualSkip(t, 2, ok, true) - NotEqualSkip(t, 2, val, nil) - EqualSkip(t, 2, val.Struct, expectedStructName) - - return val -} - -func AssertFieldError(t *testing.T, s *StructErrors, field string, expectedTag string) { - - val, ok := s.Errors[field] + val, ok := errs[key] EqualSkip(t, 2, ok, true) NotEqualSkip(t, 2, val, nil) EqualSkip(t, 2, val.Field, field) EqualSkip(t, 2, val.Tag, expectedTag) } -func AssertMapFieldError(t *testing.T, s map[string]*FieldError, field string, expectedTag string) { +func TestCommaAndPipeObfuscationValidation(t *testing.T) { + s := "My Name Is, |joeybloggs|" - val, ok := s[field] - EqualSkip(t, 2, ok, true) - NotEqualSkip(t, 2, val, nil) - EqualSkip(t, 2, val.Field, field) - EqualSkip(t, 2, val.Tag, expectedTag) + errs := validate.Field(s, "excludesall=0x2C") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "excludesall") + + errs = validate.Field(s, "excludesall=0x7C") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "excludesall") } func TestBadKeyValidation(t *testing.T) { @@ -240,164 +250,17 @@ func TestBadKeyValidation(t *testing.T) { Name: "test", } - PanicMatches(t, func() { validate.Struct(tst) }, "Invalid validation tag on field Name") -} - -func TestFlattenValidation(t *testing.T) { - - type Inner struct { - Name string `validate:"required"` - } - - type TestMultiDimensionalStructsPtr struct { - Errs [][]*Inner `validate:"gt=0,dive,dive,required"` - } - - var errStructPtrArray [][]*Inner - - errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{"ok"}}) - - tmsp := &TestMultiDimensionalStructsPtr{ - Errs: errStructPtrArray, - } - - errs := validate.Struct(tmsp) - NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - // for full test coverage - fmt.Sprint(errs.Error()) - - fieldErr := errs.Errors["Errs"] - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, fieldErr.Field, "Errs") - Equal(t, len(fieldErr.SliceOrArrayErrs), 1) - - innerSlice1, ok := fieldErr.SliceOrArrayErrs[0].(*FieldError) - Equal(t, ok, true) - Equal(t, innerSlice1.IsPlaceholderErr, true) - Equal(t, innerSlice1.Field, "Errs[0]") - - flatFieldErr, ok := fieldErr.Flatten()["[0][1].Inner.Name"] - Equal(t, ok, true) - Equal(t, flatFieldErr.Field, "Name") - Equal(t, flatFieldErr.Tag, "required") - - structErrFlatten, ok := errs.Flatten()["Errs[0][1].Inner.Name"] - Equal(t, ok, true) - Equal(t, structErrFlatten.Field, "Name") - Equal(t, structErrFlatten.Tag, "required") - - errStructPtrArray = [][]*Inner{} - errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, nil, &Inner{"ok"}}) - - tmsp = &TestMultiDimensionalStructsPtr{ - Errs: errStructPtrArray, - } - - errs = validate.Struct(tmsp) - NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - // for full test coverage - fmt.Sprint(errs.Error()) - - fieldErr = errs.Errors["Errs"] - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, fieldErr.Field, "Errs") - Equal(t, len(fieldErr.SliceOrArrayErrs), 1) - - innerSlice1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError) - Equal(t, ok, true) - Equal(t, innerSlice1.IsPlaceholderErr, true) - Equal(t, innerSlice1.Field, "Errs[0]") - - flatFieldErr, ok = fieldErr.Flatten()["[0][1]"] - Equal(t, ok, true) - Equal(t, flatFieldErr.Field, "Errs[0][1]") - Equal(t, flatFieldErr.Tag, "required") + PanicMatches(t, func() { validate.Struct(tst) }, "Undefined validation function on field Name") - type TestMapStructPtr struct { - Errs map[int]*Inner `validate:"gt=0,dive,required"` - } - - mip := map[int]*Inner{0: &Inner{"ok"}, 3: &Inner{""}, 4: &Inner{"ok"}} - - msp := &TestMapStructPtr{ - Errs: mip, - } - - errs = validate.Struct(msp) - NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldError := errs.Errors["Errs"] - Equal(t, fieldError.IsPlaceholderErr, true) - Equal(t, fieldError.IsMap, true) - Equal(t, len(fieldError.MapErrs), 1) - - innerStructError, ok := fieldError.MapErrs[3].(*StructErrors) - Equal(t, ok, true) - Equal(t, innerStructError.Struct, "Inner") - Equal(t, len(innerStructError.Errors), 1) - - innerInnerFieldError, ok := innerStructError.Errors["Name"] - Equal(t, ok, true) - Equal(t, innerInnerFieldError.IsPlaceholderErr, false) - Equal(t, innerInnerFieldError.IsSliceOrArray, false) - Equal(t, innerInnerFieldError.Field, "Name") - Equal(t, innerInnerFieldError.Tag, "required") - - flatErrs, ok := errs.Flatten()["Errs[3].Inner.Name"] - Equal(t, ok, true) - Equal(t, flatErrs.Field, "Name") - Equal(t, flatErrs.Tag, "required") - - mip2 := map[int]*Inner{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}} - - msp2 := &TestMapStructPtr{ - Errs: mip2, - } - - errs = validate.Struct(msp2) - NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldError = errs.Errors["Errs"] - Equal(t, fieldError.IsPlaceholderErr, true) - Equal(t, fieldError.IsMap, true) - Equal(t, len(fieldError.MapErrs), 1) - - innerFieldError, ok := fieldError.MapErrs[3].(*FieldError) - Equal(t, ok, true) - Equal(t, innerFieldError.IsPlaceholderErr, false) - Equal(t, innerFieldError.IsSliceOrArray, false) - Equal(t, innerFieldError.Field, "Errs[3]") - Equal(t, innerFieldError.Tag, "required") - - flatErrs, ok = errs.Flatten()["Errs[3]"] - Equal(t, ok, true) - Equal(t, flatErrs.Field, "Errs[3]") - Equal(t, flatErrs.Tag, "required") - - type TestMapInnerArrayStruct struct { - Errs map[int][]string `validate:"gt=0,dive,dive,required"` + type Test2 struct { + Name string `validate:"required,,len=2"` } - mias := map[int][]string{0: []string{"ok"}, 3: []string{"ok", ""}, 4: []string{"ok"}} - - mia := &TestMapInnerArrayStruct{ - Errs: mias, + tst2 := &Test2{ + Name: "test", } - errs = validate.Struct(mia) - NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - flatErrs, ok = errs.Flatten()["Errs[3][1]"] - Equal(t, ok, true) - Equal(t, flatErrs.Field, "Errs[3][1]") - Equal(t, flatErrs.Tag, "required") + PanicMatches(t, func() { validate.Struct(tst2) }, "Invalid validation tag on field Name") } func TestInterfaceErrValidation(t *testing.T) { @@ -408,10 +271,11 @@ func TestInterfaceErrValidation(t *testing.T) { v2 = 1 v1 = v2 - err := validate.Field(v1, "len=1") - Equal(t, err, nil) - err = validate.Field(v2, "len=1") - Equal(t, err, nil) + errs := validate.Field(v1, "len=1") + Equal(t, errs, nil) + + errs = validate.Field(v2, "len=1") + Equal(t, errs, nil) type ExternalCMD struct { Userid string `json:"userid"` @@ -425,10 +289,10 @@ func TestInterfaceErrValidation(t *testing.T) { // Data: 1, } - errs := validate.Struct(s) + errs = validate.Struct(s) NotEqual(t, errs, nil) - Equal(t, errs.Errors["Data"].Field, "Data") - Equal(t, errs.Errors["Data"].Tag, "required") + Equal(t, len(errs), 1) + AssertError(t, errs, "ExternalCMD.Data", "Data", "required") type ExternalCMD2 struct { Userid string `json:"userid"` @@ -444,9 +308,8 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s2) NotEqual(t, errs, nil) - Equal(t, errs.Errors["Data"].Field, "Data") - Equal(t, errs.Errors["Data"].Tag, "len") - Equal(t, errs.Errors["Data"].Param, "1") + Equal(t, len(errs), 1) + AssertError(t, errs, "ExternalCMD2.Data", "Data", "len") s3 := &ExternalCMD2{ Userid: "123456", @@ -456,9 +319,8 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s3) NotEqual(t, errs, nil) - Equal(t, errs.Errors["Data"].Field, "Data") - Equal(t, errs.Errors["Data"].Tag, "len") - Equal(t, errs.Errors["Data"].Param, "1") + Equal(t, len(errs), 1) + AssertError(t, errs, "ExternalCMD2.Data", "Data", "len") type Inner struct { Name string `validate:"required"` @@ -476,9 +338,8 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s4) NotEqual(t, errs, nil) - Equal(t, errs.StructErrors["Data"].Struct, "Inner") - Equal(t, errs.StructErrors["Data"].Errors["Name"].Field, "Name") - Equal(t, errs.StructErrors["Data"].Errors["Name"].Tag, "required") + Equal(t, len(errs), 1) + AssertError(t, errs, "ExternalCMD.Data.Name", "Name", "required") type TestMapStructPtr struct { Errs map[int]interface{} `validate:"gt=0,dive,len=2"` @@ -492,23 +353,11 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(msp) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldError := errs.Errors["Errs"] - Equal(t, fieldError.IsPlaceholderErr, true) - Equal(t, fieldError.IsMap, true) - Equal(t, len(fieldError.MapErrs), 1) - - innerFieldError, ok := fieldError.MapErrs[3].(*FieldError) - Equal(t, ok, true) - Equal(t, innerFieldError.IsPlaceholderErr, false) - Equal(t, innerFieldError.IsMap, false) - Equal(t, len(innerFieldError.MapErrs), 0) - Equal(t, innerFieldError.Field, "Errs[3]") - Equal(t, innerFieldError.Tag, "len") + Equal(t, len(errs), 1) + AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "len") type TestMultiDimensionalStructs struct { - Errs [][]interface{} `validate:"gt=0,dive,dive,len=2"` + Errs [][]interface{} `validate:"gt=0,dive,dive"` } var errStructArray [][]interface{} @@ -522,31 +371,14 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(tms) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldErr, ok := errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 2) - - sliceError1, ok := fieldErr.SliceOrArrayErrs[0].(*FieldError) - Equal(t, ok, true) - Equal(t, sliceError1.IsPlaceholderErr, true) - Equal(t, sliceError1.IsSliceOrArray, true) - Equal(t, len(sliceError1.SliceOrArrayErrs), 2) - - innerSliceStructError1, ok := sliceError1.SliceOrArrayErrs[1].(*StructErrors) - Equal(t, ok, true) - Equal(t, len(innerSliceStructError1.Errors), 1) - - innerInnersliceError1 := innerSliceStructError1.Errors["Name"] - Equal(t, innerInnersliceError1.IsPlaceholderErr, false) - Equal(t, innerInnersliceError1.IsSliceOrArray, false) - Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0) + Equal(t, len(errs), 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") + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][2].Name", "Name", "required") type TestMultiDimensionalStructsPtr2 struct { - Errs [][]*Inner `validate:"gt=0,dive,dive,len=2"` + Errs [][]*Inner `validate:"gt=0,dive,dive,required"` } var errStructPtr2Array [][]*Inner @@ -561,63 +393,37 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(tmsp2) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldErr, ok = errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 3) - - sliceError1, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError) - Equal(t, ok, true) - Equal(t, sliceError1.IsPlaceholderErr, true) - Equal(t, sliceError1.IsSliceOrArray, true) - Equal(t, len(sliceError1.SliceOrArrayErrs), 2) - - innerSliceStructError1, ok = sliceError1.SliceOrArrayErrs[1].(*StructErrors) - Equal(t, ok, true) - Equal(t, len(innerSliceStructError1.Errors), 1) - - innerSliceStructError2, ok := sliceError1.SliceOrArrayErrs[2].(*FieldError) - Equal(t, ok, true) - Equal(t, innerSliceStructError2.IsPlaceholderErr, false) - Equal(t, innerSliceStructError2.IsSliceOrArray, false) - Equal(t, len(innerSliceStructError2.SliceOrArrayErrs), 0) - Equal(t, innerSliceStructError2.Field, "Errs[2][2]") - - innerInnersliceError1 = innerSliceStructError1.Errors["Name"] - Equal(t, innerInnersliceError1.IsPlaceholderErr, false) - Equal(t, innerInnersliceError1.IsSliceOrArray, false) - Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0) + Equal(t, len(errs), 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") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][2].Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[2][1].Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[2][2]", "Errs[2][2]", "required") m := map[int]interface{}{0: "ok", 3: "", 4: "ok"} - err = validate.Field(m, "len=3,dive,len=2") - NotEqual(t, err, nil) - Equal(t, err.IsPlaceholderErr, true) - Equal(t, err.IsMap, true) - Equal(t, len(err.MapErrs), 1) + errs = validate.Field(m, "len=3,dive,len=2") + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "[3]", "[3]", "len") - err = validate.Field(m, "len=2,dive,required") - NotEqual(t, err, nil) - Equal(t, err.IsPlaceholderErr, false) - Equal(t, err.IsMap, false) - Equal(t, len(err.MapErrs), 0) + errs = validate.Field(m, "len=2,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "", "", "len") arr := []interface{}{"ok", "", "ok"} - err = validate.Field(arr, "len=3,dive,len=2") - NotEqual(t, err, nil) - Equal(t, err.IsPlaceholderErr, true) - Equal(t, err.IsSliceOrArray, true) - Equal(t, len(err.SliceOrArrayErrs), 1) + errs = validate.Field(arr, "len=3,dive,len=2") + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "[1]", "[1]", "len") - err = validate.Field(arr, "len=2,dive,required") - NotEqual(t, err, nil) - Equal(t, err.IsPlaceholderErr, false) - Equal(t, err.IsSliceOrArray, false) - Equal(t, len(err.SliceOrArrayErrs), 0) + errs = validate.Field(arr, "len=2,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "", "", "len") type MyStruct struct { A, B string @@ -636,21 +442,20 @@ func TestInterfaceErrValidation(t *testing.T) { func TestMapDiveValidation(t *testing.T) { n := map[int]interface{}{0: nil} - err := validate.Field(n, "omitempty,required") + errs := validate.Field(n, "omitempty,required") + Equal(t, errs, nil) m := map[int]string{0: "ok", 3: "", 4: "ok"} - err = validate.Field(m, "len=3,dive,required") - NotEqual(t, err, nil) - Equal(t, err.IsPlaceholderErr, true) - Equal(t, err.IsMap, true) - Equal(t, len(err.MapErrs), 1) + errs = validate.Field(m, "len=3,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "[3]", "[3]", "required") - err = validate.Field(m, "len=2,dive,required") - NotEqual(t, err, nil) - Equal(t, err.IsPlaceholderErr, false) - Equal(t, err.IsMap, false) - Equal(t, len(err.MapErrs), 0) + errs = validate.Field(m, "len=2,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "", "", "len") type Inner struct { Name string `validate:"required"` @@ -666,28 +471,14 @@ func TestMapDiveValidation(t *testing.T) { Errs: mi, } - errs := validate.Struct(ms) + errs = validate.Struct(ms) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) + Equal(t, len(errs), 1) + AssertError(t, errs, "TestMapStruct.Errs[3].Name", "Name", "required") + // for full test coverage fmt.Sprint(errs.Error()) - fieldError := errs.Errors["Errs"] - Equal(t, fieldError.IsPlaceholderErr, true) - Equal(t, fieldError.IsMap, true) - Equal(t, len(fieldError.MapErrs), 1) - - structErr, ok := fieldError.MapErrs[3].(*StructErrors) - Equal(t, ok, true) - Equal(t, len(structErr.Errors), 1) - - innerErr := structErr.Errors["Name"] - Equal(t, innerErr.IsPlaceholderErr, false) - Equal(t, innerErr.IsMap, false) - Equal(t, len(innerErr.MapErrs), 0) - Equal(t, innerErr.Field, "Name") - Equal(t, innerErr.Tag, "required") - type TestMapTimeStruct struct { Errs map[int]*time.Time `validate:"gt=0,dive,required"` } @@ -702,20 +493,9 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Struct(mt) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldError = errs.Errors["Errs"] - Equal(t, fieldError.IsPlaceholderErr, true) - Equal(t, fieldError.IsMap, true) - Equal(t, len(fieldError.MapErrs), 2) - - innerErr, ok = fieldError.MapErrs[3].(*FieldError) - Equal(t, ok, true) - Equal(t, innerErr.IsPlaceholderErr, false) - Equal(t, innerErr.IsMap, false) - Equal(t, len(innerErr.MapErrs), 0) - Equal(t, innerErr.Field, "Errs[3]") - Equal(t, innerErr.Tag, "required") + Equal(t, len(errs), 2) + AssertError(t, errs, "TestMapTimeStruct.Errs[3]", "Errs[3]", "required") + AssertError(t, errs, "TestMapTimeStruct.Errs[4]", "Errs[4]", "required") type TestMapStructPtr struct { Errs map[int]*Inner `validate:"gt=0,dive,required"` @@ -729,20 +509,8 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Struct(msp) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldError = errs.Errors["Errs"] - Equal(t, fieldError.IsPlaceholderErr, true) - Equal(t, fieldError.IsMap, true) - Equal(t, len(fieldError.MapErrs), 1) - - innerFieldError, ok := fieldError.MapErrs[3].(*FieldError) - Equal(t, ok, true) - Equal(t, innerFieldError.IsPlaceholderErr, false) - Equal(t, innerFieldError.IsMap, false) - Equal(t, len(innerFieldError.MapErrs), 0) - Equal(t, innerFieldError.Field, "Errs[3]") - Equal(t, innerFieldError.Tag, "required") + Equal(t, len(errs), 1) + AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "required") type TestMapStructPtr2 struct { Errs map[int]*Inner `validate:"gt=0,dive,omitempty,required"` @@ -762,22 +530,15 @@ func TestArrayDiveValidation(t *testing.T) { arr := []string{"ok", "", "ok"} - err := validate.Field(arr, "len=3,dive,required") - NotEqual(t, err, nil) - Equal(t, err.IsPlaceholderErr, true) - Equal(t, err.IsSliceOrArray, true) - Equal(t, len(err.SliceOrArrayErrs), 1) - - // flat := err.Flatten() - // fe, ok := flat["[1]"] - // Equal(t, ok, true) - // Equal(t, fe.Tag, "required") + errs := validate.Field(arr, "len=3,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "[1]", "[1]", "required") - err = validate.Field(arr, "len=2,dive,required") - NotEqual(t, err, nil) - Equal(t, err.IsPlaceholderErr, false) - Equal(t, err.IsSliceOrArray, false) - Equal(t, len(err.SliceOrArrayErrs), 0) + errs = validate.Field(arr, "len=2,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "", "", "len") type BadDive struct { Name string `validate:"dive"` @@ -797,27 +558,10 @@ func TestArrayDiveValidation(t *testing.T) { Errs: []string{"ok", "", "ok"}, } - errs := validate.Struct(test) + errs = validate.Struct(test) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - // flat = errs.Flatten() - // me, ok := flat["Errs[1]"] - // Equal(t, ok, true) - // Equal(t, me.Field, "Errs[1]") - // Equal(t, me.Tag, "required") - - fieldErr, ok := errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 1) - - innerErr, ok := fieldErr.SliceOrArrayErrs[1].(*FieldError) - Equal(t, ok, true) - Equal(t, innerErr.Tag, required) - Equal(t, innerErr.IsPlaceholderErr, false) - Equal(t, innerErr.Field, "Errs[1]") + Equal(t, len(errs), 1) + AssertError(t, errs, "Test.Errs[1]", "Errs[1]", "required") test = &Test{ Errs: []string{"ok", "ok", ""}, @@ -825,19 +569,8 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(test) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldErr, ok = errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 1) - - innerErr, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError) - Equal(t, ok, true) - Equal(t, innerErr.Tag, required) - Equal(t, innerErr.IsPlaceholderErr, false) - Equal(t, innerErr.Field, "Errs[2]") + Equal(t, len(errs), 1) + AssertError(t, errs, "Test.Errs[2]", "Errs[2]", "required") type TestMultiDimensional struct { Errs [][]string `validate:"gt=0,dive,dive,required"` @@ -854,28 +587,11 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tm) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldErr, ok = errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 2) - - sliceError1, ok := fieldErr.SliceOrArrayErrs[0].(*FieldError) - Equal(t, ok, true) - Equal(t, sliceError1.IsPlaceholderErr, true) - Equal(t, sliceError1.IsSliceOrArray, true) - Equal(t, len(sliceError1.SliceOrArrayErrs), 2) - Equal(t, sliceError1.Field, "Errs[0]") - - innerSliceError1, ok := sliceError1.SliceOrArrayErrs[1].(*FieldError) - Equal(t, ok, true) - Equal(t, innerSliceError1.IsPlaceholderErr, false) - Equal(t, innerSliceError1.Tag, required) - Equal(t, innerSliceError1.IsSliceOrArray, false) - Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0) - Equal(t, innerSliceError1.Field, "Errs[0][1]") + Equal(t, len(errs), 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") + AssertError(t, errs, "TestMultiDimensional.Errs[1][2]", "Errs[1][2]", "required") type Inner struct { Name string `validate:"required"` @@ -896,28 +612,11 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tms) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldErr, ok = errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 2) - - sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError) - Equal(t, ok, true) - Equal(t, sliceError1.IsPlaceholderErr, true) - Equal(t, sliceError1.IsSliceOrArray, true) - Equal(t, len(sliceError1.SliceOrArrayErrs), 2) - - innerSliceStructError1, ok := sliceError1.SliceOrArrayErrs[1].(*StructErrors) - Equal(t, ok, true) - Equal(t, len(innerSliceStructError1.Errors), 1) - - innerInnersliceError1 := innerSliceStructError1.Errors["Name"] - Equal(t, innerInnersliceError1.IsPlaceholderErr, false) - Equal(t, innerInnersliceError1.IsSliceOrArray, false) - Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0) + Equal(t, len(errs), 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") + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][2].Name", "Name", "required") type TestMultiDimensionalStructsPtr struct { Errs [][]*Inner `validate:"gt=0,dive,dive"` @@ -935,44 +634,16 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmsp) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) + Equal(t, len(errs), 6) + 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") + AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[2][2]", "Errs[2][2]", "") // for full test coverage fmt.Sprint(errs.Error()) - // flat := errs.Flatten() - // // fmt.Println(errs) - // fmt.Println(flat) - // expect Errs[0][1].Inner.Name - // me, ok := flat["Errs[1]"] - // Equal(t, ok, true) - // Equal(t, me.Field, "Errs[1]") - // Equal(t, me.Tag, "required") - - fieldErr, ok = errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 3) - - // flat := fieldErr.Flatten() - // fmt.Println(errs) - // fmt.Println(flat) - - sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError) - Equal(t, ok, true) - Equal(t, sliceError1.IsPlaceholderErr, true) - Equal(t, sliceError1.IsSliceOrArray, true) - Equal(t, len(sliceError1.SliceOrArrayErrs), 2) - - innerSliceStructError1, ok = sliceError1.SliceOrArrayErrs[1].(*StructErrors) - Equal(t, ok, true) - Equal(t, len(innerSliceStructError1.Errors), 1) - - innerInnersliceError1 = innerSliceStructError1.Errors["Name"] - Equal(t, innerInnersliceError1.IsPlaceholderErr, false) - Equal(t, innerInnersliceError1.IsSliceOrArray, false) - Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0) - type TestMultiDimensionalStructsPtr2 struct { Errs [][]*Inner `validate:"gt=0,dive,dive,required"` } @@ -989,35 +660,13 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmsp2) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldErr, ok = errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 3) - - sliceError1, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError) - Equal(t, ok, true) - Equal(t, sliceError1.IsPlaceholderErr, true) - Equal(t, sliceError1.IsSliceOrArray, true) - Equal(t, len(sliceError1.SliceOrArrayErrs), 2) - - innerSliceStructError1, ok = sliceError1.SliceOrArrayErrs[1].(*StructErrors) - Equal(t, ok, true) - Equal(t, len(innerSliceStructError1.Errors), 1) - - innerSliceStructError2, ok := sliceError1.SliceOrArrayErrs[2].(*FieldError) - Equal(t, ok, true) - Equal(t, innerSliceStructError2.IsPlaceholderErr, false) - Equal(t, innerSliceStructError2.IsSliceOrArray, false) - Equal(t, len(innerSliceStructError2.SliceOrArrayErrs), 0) - Equal(t, innerSliceStructError2.Field, "Errs[2][2]") - - innerInnersliceError1 = innerSliceStructError1.Errors["Name"] - Equal(t, innerInnersliceError1.IsPlaceholderErr, false) - Equal(t, innerInnersliceError1.IsSliceOrArray, false) - Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0) + Equal(t, len(errs), 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") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][2].Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[2][1].Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[2][2]", "Errs[2][2]", "required") type TestMultiDimensionalStructsPtr3 struct { Errs [][]*Inner `validate:"gt=0,dive,dive,omitempty"` @@ -1035,28 +684,12 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmsp3) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldErr, ok = errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 3) - - sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError) - Equal(t, ok, true) - Equal(t, sliceError1.IsPlaceholderErr, true) - Equal(t, sliceError1.IsSliceOrArray, true) - Equal(t, len(sliceError1.SliceOrArrayErrs), 2) - - innerSliceStructError1, ok = sliceError1.SliceOrArrayErrs[1].(*StructErrors) - Equal(t, ok, true) - Equal(t, len(innerSliceStructError1.Errors), 1) - - innerInnersliceError1 = innerSliceStructError1.Errors["Name"] - Equal(t, innerInnersliceError1.IsPlaceholderErr, false) - Equal(t, innerInnersliceError1.IsSliceOrArray, false) - Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0) + Equal(t, len(errs), 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") + AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[1][2].Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[2][1].Name", "Name", "required") type TestMultiDimensionalTimeTime struct { Errs [][]*time.Time `validate:"gt=0,dive,dive,required"` @@ -1078,27 +711,10 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmtp3) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldErr, ok = errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 2) - - sliceError1, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError) - Equal(t, ok, true) - Equal(t, sliceError1.IsPlaceholderErr, true) - Equal(t, sliceError1.IsSliceOrArray, true) - Equal(t, len(sliceError1.SliceOrArrayErrs), 2) - - innerSliceError1, ok = sliceError1.SliceOrArrayErrs[1].(*FieldError) - Equal(t, ok, true) - Equal(t, innerSliceError1.IsPlaceholderErr, false) - Equal(t, innerSliceError1.IsSliceOrArray, false) - Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0) - Equal(t, innerSliceError1.Field, "Errs[2][1]") - Equal(t, innerSliceError1.Tag, required) + Equal(t, len(errs), 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") type TestMultiDimensionalTimeTime2 struct { Errs [][]*time.Time `validate:"gt=0,dive,dive,required"` @@ -1120,27 +736,10 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmtp) NotEqual(t, errs, nil) - Equal(t, len(errs.Errors), 1) - - fieldErr, ok = errs.Errors["Errs"] - Equal(t, ok, true) - Equal(t, fieldErr.IsPlaceholderErr, true) - Equal(t, fieldErr.IsSliceOrArray, true) - Equal(t, len(fieldErr.SliceOrArrayErrs), 2) - - sliceError1, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError) - Equal(t, ok, true) - Equal(t, sliceError1.IsPlaceholderErr, true) - Equal(t, sliceError1.IsSliceOrArray, true) - Equal(t, len(sliceError1.SliceOrArrayErrs), 2) - - innerSliceError1, ok = sliceError1.SliceOrArrayErrs[1].(*FieldError) - Equal(t, ok, true) - Equal(t, innerSliceError1.IsPlaceholderErr, false) - Equal(t, innerSliceError1.IsSliceOrArray, false) - Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0) - Equal(t, innerSliceError1.Field, "Errs[2][1]") - Equal(t, innerSliceError1.Tag, required) + Equal(t, len(errs), 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") } func TestNilStructPointerValidation(t *testing.T) { @@ -1195,6 +794,7 @@ func TestNilStructPointerValidation(t *testing.T) { errs = validate.Struct(outer2) NotEqual(t, errs, nil) + AssertError(t, errs, "Outer2.Inner2", "Inner2", "required") type Inner3 struct { Data string @@ -1249,15 +849,20 @@ func TestSSNValidation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "ssn") + errs := validate.Field(test.param, "ssn") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d SSN failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d SSN failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "ssn") { - t.Fatalf("Index: %d SSN failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d SSN failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "ssn" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } } } } @@ -1278,15 +883,20 @@ func TestLongitudeValidation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "longitude") + errs := validate.Field(test.param, "longitude") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d Longitude failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "longitude") { - t.Fatalf("Index: %d Longitude failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "longitude" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } } } } @@ -1307,15 +917,20 @@ func TestLatitudeValidation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "latitude") + errs := validate.Field(test.param, "latitude") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d Latitude failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "latitude") { - t.Fatalf("Index: %d Latitude failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "latitude" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } } } } @@ -1342,15 +957,20 @@ func TestDataURIValidation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "datauri") + errs := validate.Field(test.param, "datauri") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d DataURI failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "datauri") { - t.Fatalf("Index: %d DataURI failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "datauri" { + t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) + } } } } @@ -1375,15 +995,20 @@ func TestMultibyteValidation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "multibyte") + errs := validate.Field(test.param, "multibyte") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d Multibyte failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "multibyte") { - t.Fatalf("Index: %d Multibyte failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "multibyte" { + t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) + } } } } @@ -1409,15 +1034,20 @@ func TestPrintableASCIIValidation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "printascii") + errs := validate.Field(test.param, "printascii") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "printascii") { - t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "printascii" { + t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) + } } } } @@ -1442,15 +1072,20 @@ func TestASCIIValidation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "ascii") + errs := validate.Field(test.param, "ascii") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d ASCII failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "ascii") { - t.Fatalf("Index: %d ASCII failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "ascii" { + t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) + } } } } @@ -1472,15 +1107,20 @@ func TestUUID5Validation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "uuid5") + errs := validate.Field(test.param, "uuid5") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d UUID5 failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "uuid5") { - t.Fatalf("Index: %d UUID5 failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "uuid5" { + t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) + } } } } @@ -1501,15 +1141,20 @@ func TestUUID4Validation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "uuid4") + errs := validate.Field(test.param, "uuid4") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d UUID4 failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "uuid4") { - t.Fatalf("Index: %d UUID4 failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "uuid4" { + t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) + } } } } @@ -1529,15 +1174,20 @@ func TestUUID3Validation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "uuid3") + errs := validate.Field(test.param, "uuid3") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d UUID3 failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "uuid3") { - t.Fatalf("Index: %d UUID3 failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "uuid3" { + t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) + } } } } @@ -1560,15 +1210,20 @@ func TestUUIDValidation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "uuid") + errs := validate.Field(test.param, "uuid") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d UUID failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d UUID failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "uuid") { - t.Fatalf("Index: %d UUID failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d UUID failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "uuid" { + t.Fatalf("Index: %d UUID failed Error: %s", i, errs) + } } } } @@ -1593,15 +1248,20 @@ func TestISBNValidation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "isbn") + errs := validate.Field(test.param, "isbn") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d ISBN failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "isbn") { - t.Fatalf("Index: %d ISBN failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "isbn" { + t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) + } } } } @@ -1625,15 +1285,20 @@ func TestISBN13Validation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "isbn13") + errs := validate.Field(test.param, "isbn13") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d ISBN13 failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "isbn13") { - t.Fatalf("Index: %d ISBN13 failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "isbn13" { + t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) + } } } } @@ -1658,15 +1323,20 @@ func TestISBN10Validation(t *testing.T) { for i, test := range tests { - err := validate.Field(test.param, "isbn10") + errs := validate.Field(test.param, "isbn10") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d ISBN10 failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "isbn10") { - t.Fatalf("Index: %d ISBN10 failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "isbn10" { + t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) + } } } } @@ -1684,13 +1354,13 @@ func TestExcludesRuneValidation(t *testing.T) { } for i, s := range tests { - err := validate.Field(s.Value, s.Tag) + errs := validate.Field(s.Value, s.Tag) - if (s.ExpectedNil && err != nil) || (!s.ExpectedNil && err == nil) { - t.Fatalf("Index: %d failed Error: %s", i, err) + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) } - errs := validate.Struct(s) + errs = validate.Struct(s) if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { t.Fatalf("Index: %d failed Error: %s", i, errs) @@ -1710,13 +1380,13 @@ func TestExcludesAllValidation(t *testing.T) { } for i, s := range tests { - err := validate.Field(s.Value, s.Tag) + errs := validate.Field(s.Value, s.Tag) - if (s.ExpectedNil && err != nil) || (!s.ExpectedNil && err == nil) { - t.Fatalf("Index: %d failed Error: %s", i, err) + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) } - errs := validate.Struct(s) + errs = validate.Struct(s) if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { t.Fatalf("Index: %d failed Error: %s", i, errs) @@ -1725,18 +1395,21 @@ func TestExcludesAllValidation(t *testing.T) { username := "joeybloggs " - err := validate.Field(username, "excludesall=@ ") - NotEqual(t, err, nil) + errs := validate.Field(username, "excludesall=@ ") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "excludesall") excluded := "," - err = validate.Field(excluded, "excludesall=!@#$%^&*()_+.0x2C?") - NotEqual(t, err, nil) + errs = validate.Field(excluded, "excludesall=!@#$%^&*()_+.0x2C?") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "excludesall") excluded = "=" - err = validate.Field(excluded, "excludesall=!@#$%^&*()_+.0x2C=?") - NotEqual(t, err, nil) + errs = validate.Field(excluded, "excludesall=!@#$%^&*()_+.0x2C=?") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "excludesall") } func TestExcludesValidation(t *testing.T) { @@ -1751,13 +1424,13 @@ func TestExcludesValidation(t *testing.T) { } for i, s := range tests { - err := validate.Field(s.Value, s.Tag) + errs := validate.Field(s.Value, s.Tag) - if (s.ExpectedNil && err != nil) || (!s.ExpectedNil && err == nil) { - t.Fatalf("Index: %d failed Error: %s", i, err) + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) } - errs := validate.Struct(s) + errs = validate.Struct(s) if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { t.Fatalf("Index: %d failed Error: %s", i, errs) @@ -1777,13 +1450,13 @@ func TestContainsRuneValidation(t *testing.T) { } for i, s := range tests { - err := validate.Field(s.Value, s.Tag) + errs := validate.Field(s.Value, s.Tag) - if (s.ExpectedNil && err != nil) || (!s.ExpectedNil && err == nil) { - t.Fatalf("Index: %d failed Error: %s", i, err) + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) } - errs := validate.Struct(s) + errs = validate.Struct(s) if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { t.Fatalf("Index: %d failed Error: %s", i, errs) @@ -1803,13 +1476,13 @@ func TestContainsAnyValidation(t *testing.T) { } for i, s := range tests { - err := validate.Field(s.Value, s.Tag) + errs := validate.Field(s.Value, s.Tag) - if (s.ExpectedNil && err != nil) || (!s.ExpectedNil && err == nil) { - t.Fatalf("Index: %d failed Error: %s", i, err) + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) } - errs := validate.Struct(s) + errs = validate.Struct(s) if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { t.Fatalf("Index: %d failed Error: %s", i, errs) @@ -1829,13 +1502,13 @@ func TestContainsValidation(t *testing.T) { } for i, s := range tests { - err := validate.Field(s.Value, s.Tag) + errs := validate.Field(s.Value, s.Tag) - if (s.ExpectedNil && err != nil) || (!s.ExpectedNil && err == nil) { - t.Fatalf("Index: %d failed Error: %s", i, err) + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) } - errs := validate.Struct(s) + errs = validate.Struct(s) if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { t.Fatalf("Index: %d failed Error: %s", i, errs) @@ -1864,26 +1537,28 @@ func TestIsNeFieldValidation(t *testing.T) { arr3 := []string{"test"} now2 := now - err := validate.FieldWithValue(s, s2, "nefield") - Equal(t, err, nil) + errs := validate.FieldWithValue(s, s2, "nefield") + Equal(t, errs, nil) - err = validate.FieldWithValue(i2, i, "nefield") - Equal(t, err, nil) + errs = validate.FieldWithValue(i2, i, "nefield") + Equal(t, errs, nil) - err = validate.FieldWithValue(j2, j, "nefield") - Equal(t, err, nil) + errs = validate.FieldWithValue(j2, j, "nefield") + Equal(t, errs, nil) - err = validate.FieldWithValue(k2, k, "nefield") - Equal(t, err, nil) + errs = validate.FieldWithValue(k2, k, "nefield") + Equal(t, errs, nil) - err = validate.FieldWithValue(arr2, arr, "nefield") - Equal(t, err, nil) + errs = validate.FieldWithValue(arr2, arr, "nefield") + Equal(t, errs, nil) - err = validate.FieldWithValue(now2, now, "nefield") - NotEqual(t, err, nil) + errs = validate.FieldWithValue(now2, now, "nefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "nefield") - err = validate.FieldWithValue(arr3, arr, "nefield") - NotEqual(t, err, nil) + errs = validate.FieldWithValue(arr3, arr, "nefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "nefield") type Test struct { Start *time.Time `validate:"nefield=End"` @@ -1895,8 +1570,9 @@ func TestIsNeFieldValidation(t *testing.T) { End: &now, } - errs := validate.Struct(sv) + errs = validate.Struct(sv) NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Start", "Start", "nefield") now3 := time.Now().UTC() @@ -1910,7 +1586,7 @@ func TestIsNeFieldValidation(t *testing.T) { channel := make(chan string) - PanicMatches(t, func() { validate.FieldWithValue(nil, 1, "nefield") }, "struct not passed for cross validation") + PanicMatches(t, func() { validate.FieldWithValue(nil, 1, "nefield") }, "struct or field value not passed for cross validation") PanicMatches(t, func() { validate.FieldWithValue(5, channel, "nefield") }, "Bad field type chan string") PanicMatches(t, func() { validate.FieldWithValue(5, now, "nefield") }, "Bad Top Level field type") @@ -1938,23 +1614,24 @@ func TestIsNeValidation(t *testing.T) { arr := []string{"test"} now := time.Now().UTC() - err := validate.Field(s, "ne=abcd") - Equal(t, err, nil) + errs := validate.Field(s, "ne=abcd") + Equal(t, errs, nil) - err = validate.Field(i, "ne=1") - Equal(t, err, nil) + errs = validate.Field(i, "ne=1") + Equal(t, errs, nil) - err = validate.Field(j, "ne=1") - Equal(t, err, nil) + errs = validate.Field(j, "ne=1") + Equal(t, errs, nil) - err = validate.Field(k, "ne=1.543") - Equal(t, err, nil) + errs = validate.Field(k, "ne=1.543") + Equal(t, errs, nil) - err = validate.Field(arr, "ne=2") - Equal(t, err, nil) + errs = validate.Field(arr, "ne=2") + Equal(t, errs, nil) - err = validate.Field(arr, "ne=1") - NotEqual(t, err, nil) + errs = validate.Field(arr, "ne=1") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ne") PanicMatches(t, func() { validate.Field(now, "ne=now") }, "Bad field type time.Time") } @@ -1980,26 +1657,27 @@ func TestIsEqFieldValidation(t *testing.T) { arr3 := []string{"test", "test2"} now2 := now - err := validate.FieldWithValue(s, s2, "eqfield") - Equal(t, err, nil) + errs := validate.FieldWithValue(s, s2, "eqfield") + Equal(t, errs, nil) - err = validate.FieldWithValue(i2, i, "eqfield") - Equal(t, err, nil) + errs = validate.FieldWithValue(i2, i, "eqfield") + Equal(t, errs, nil) - err = validate.FieldWithValue(j2, j, "eqfield") - Equal(t, err, nil) + errs = validate.FieldWithValue(j2, j, "eqfield") + Equal(t, errs, nil) - err = validate.FieldWithValue(k2, k, "eqfield") - Equal(t, err, nil) + errs = validate.FieldWithValue(k2, k, "eqfield") + Equal(t, errs, nil) - err = validate.FieldWithValue(arr2, arr, "eqfield") - Equal(t, err, nil) + errs = validate.FieldWithValue(arr2, arr, "eqfield") + Equal(t, errs, nil) - err = validate.FieldWithValue(now2, now, "eqfield") - Equal(t, err, nil) + errs = validate.FieldWithValue(now2, now, "eqfield") + Equal(t, errs, nil) - err = validate.FieldWithValue(arr3, arr, "eqfield") - NotEqual(t, err, nil) + errs = validate.FieldWithValue(arr3, arr, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "eqfield") type Test struct { Start *time.Time `validate:"eqfield=End"` @@ -2011,7 +1689,7 @@ func TestIsEqFieldValidation(t *testing.T) { End: &now, } - errs := validate.Struct(sv) + errs = validate.Struct(sv) Equal(t, errs, nil) now3 := time.Now().UTC() @@ -2023,10 +1701,11 @@ func TestIsEqFieldValidation(t *testing.T) { errs = validate.Struct(sv) NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Start", "Start", "eqfield") channel := make(chan string) - PanicMatches(t, func() { validate.FieldWithValue(nil, 1, "eqfield") }, "struct not passed for cross validation") + PanicMatches(t, func() { validate.FieldWithValue(nil, 1, "eqfield") }, "struct or field value not passed for cross validation") PanicMatches(t, func() { validate.FieldWithValue(5, channel, "eqfield") }, "Bad field type chan string") PanicMatches(t, func() { validate.FieldWithValue(5, now, "eqfield") }, "Bad Top Level field type") @@ -2054,23 +1733,24 @@ func TestIsEqValidation(t *testing.T) { arr := []string{"test"} now := time.Now().UTC() - err := validate.Field(s, "eq=abcd") - Equal(t, err, nil) + errs := validate.Field(s, "eq=abcd") + Equal(t, errs, nil) - err = validate.Field(i, "eq=1") - Equal(t, err, nil) + errs = validate.Field(i, "eq=1") + Equal(t, errs, nil) - err = validate.Field(j, "eq=1") - Equal(t, err, nil) + errs = validate.Field(j, "eq=1") + Equal(t, errs, nil) - err = validate.Field(k, "eq=1.543") - Equal(t, err, nil) + errs = validate.Field(k, "eq=1.543") + Equal(t, errs, nil) - err = validate.Field(arr, "eq=1") - Equal(t, err, nil) + errs = validate.Field(arr, "eq=1") + Equal(t, errs, nil) - err = validate.Field(arr, "eq=2") - NotEqual(t, err, nil) + errs = validate.Field(arr, "eq=2") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "eq") PanicMatches(t, func() { validate.Field(now, "eq=now") }, "Bad field type time.Time") } @@ -2079,20 +1759,22 @@ func TestBase64Validation(t *testing.T) { s := "dW5pY29ybg==" - err := validate.Field(s, "base64") - Equal(t, err, nil) + errs := validate.Field(s, "base64") + Equal(t, errs, nil) s = "dGhpIGlzIGEgdGVzdCBiYXNlNjQ=" - err = validate.Field(s, "base64") - Equal(t, err, nil) + errs = validate.Field(s, "base64") + Equal(t, errs, nil) s = "" - err = validate.Field(s, "base64") - NotEqual(t, err, nil) + errs = validate.Field(s, "base64") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "base64") s = "dW5pY29ybg== foo bar" - err = validate.Field(s, "base64") - NotEqual(t, err, nil) + errs = validate.Field(s, "base64") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "base64") } func TestStructOnlyValidation(t *testing.T) { @@ -2148,16 +1830,16 @@ func TestGtField(t *testing.T) { End: &start, } - errs2 := validate.Struct(timeTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "End", "gtfield") + errs = validate.Struct(timeTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest.End", "End", "gtfield") - err3 := validate.FieldWithValue(&start, &end, "gtfield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(&start, &end, "gtfield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(&end, &start, "gtfield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "gtfield") + errs = validate.FieldWithValue(&end, &start, "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtfield") type IntTest struct { Val1 int `validate:"required"` @@ -2177,16 +1859,16 @@ func TestGtField(t *testing.T) { Val2: 1, } - errs2 = validate.Struct(intTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "gtfield") + errs = validate.Struct(intTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "IntTest.Val2", "Val2", "gtfield") - err3 = validate.FieldWithValue(int(1), int(5), "gtfield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(int(1), int(5), "gtfield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(int(5), int(1), "gtfield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "gtfield") + errs = validate.FieldWithValue(int(5), int(1), "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtfield") type UIntTest struct { Val1 uint `validate:"required"` @@ -2206,16 +1888,16 @@ func TestGtField(t *testing.T) { Val2: 1, } - errs2 = validate.Struct(uIntTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "gtfield") + errs = validate.Struct(uIntTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "UIntTest.Val2", "Val2", "gtfield") - err3 = validate.FieldWithValue(uint(1), uint(5), "gtfield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(uint(1), uint(5), "gtfield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(uint(5), uint(1), "gtfield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "gtfield") + errs = validate.FieldWithValue(uint(5), uint(1), "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtfield") type FloatTest struct { Val1 float64 `validate:"required"` @@ -2235,16 +1917,16 @@ func TestGtField(t *testing.T) { Val2: 1, } - errs2 = validate.Struct(floatTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "gtfield") + errs = validate.Struct(floatTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "FloatTest.Val2", "Val2", "gtfield") - err3 = validate.FieldWithValue(float32(1), float32(5), "gtfield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(float32(1), float32(5), "gtfield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(float32(5), float32(1), "gtfield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "gtfield") + errs = validate.FieldWithValue(float32(5), float32(1), "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtfield") PanicMatches(t, func() { validate.FieldWithValue(nil, 1, "gtfield") }, "struct not passed for cross validation") PanicMatches(t, func() { validate.FieldWithValue(5, "T", "gtfield") }, "Bad field type string") @@ -2287,16 +1969,16 @@ func TestLtField(t *testing.T) { End: &start, } - errs2 := validate.Struct(timeTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Start", "ltfield") + errs = validate.Struct(timeTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest.Start", "Start", "ltfield") - err3 := validate.FieldWithValue(&end, &start, "ltfield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(&end, &start, "ltfield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(&start, &end, "ltfield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "ltfield") + errs = validate.FieldWithValue(&start, &end, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltfield") type IntTest struct { Val1 int `validate:"required"` @@ -2316,16 +1998,16 @@ func TestLtField(t *testing.T) { Val2: 5, } - errs2 = validate.Struct(intTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "ltfield") + errs = validate.Struct(intTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "IntTest.Val2", "Val2", "ltfield") - err3 = validate.FieldWithValue(int(5), int(1), "ltfield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(int(5), int(1), "ltfield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(int(1), int(5), "ltfield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "ltfield") + errs = validate.FieldWithValue(int(1), int(5), "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltfield") type UIntTest struct { Val1 uint `validate:"required"` @@ -2345,16 +2027,16 @@ func TestLtField(t *testing.T) { Val2: 5, } - errs2 = validate.Struct(uIntTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "ltfield") + errs = validate.Struct(uIntTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "UIntTest.Val2", "Val2", "ltfield") - err3 = validate.FieldWithValue(uint(5), uint(1), "ltfield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(uint(5), uint(1), "ltfield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(uint(1), uint(5), "ltfield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "ltfield") + errs = validate.FieldWithValue(uint(1), uint(5), "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltfield") type FloatTest struct { Val1 float64 `validate:"required"` @@ -2374,16 +2056,16 @@ func TestLtField(t *testing.T) { Val2: 5, } - errs2 = validate.Struct(floatTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "ltfield") + errs = validate.Struct(floatTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "FloatTest.Val2", "Val2", "ltfield") - err3 = validate.FieldWithValue(float32(5), float32(1), "ltfield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(float32(5), float32(1), "ltfield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(float32(1), float32(5), "ltfield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "ltfield") + errs = validate.FieldWithValue(float32(1), float32(5), "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltfield") PanicMatches(t, func() { validate.FieldWithValue(nil, 5, "ltfield") }, "struct not passed for cross validation") PanicMatches(t, func() { validate.FieldWithValue(1, "T", "ltfield") }, "Bad field type string") @@ -2426,16 +2108,16 @@ func TestLteField(t *testing.T) { End: &start, } - errs2 := validate.Struct(timeTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Start", "ltefield") + errs = validate.Struct(timeTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest.Start", "Start", "ltefield") - err3 := validate.FieldWithValue(&end, &start, "ltefield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(&end, &start, "ltefield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(&start, &end, "ltefield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "ltefield") + errs = validate.FieldWithValue(&start, &end, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltefield") type IntTest struct { Val1 int `validate:"required"` @@ -2455,16 +2137,16 @@ func TestLteField(t *testing.T) { Val2: 5, } - errs2 = validate.Struct(intTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "ltefield") + errs = validate.Struct(intTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "IntTest.Val2", "Val2", "ltefield") - err3 = validate.FieldWithValue(int(5), int(1), "ltefield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(int(5), int(1), "ltefield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(int(1), int(5), "ltefield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "ltefield") + errs = validate.FieldWithValue(int(1), int(5), "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltefield") type UIntTest struct { Val1 uint `validate:"required"` @@ -2484,16 +2166,16 @@ func TestLteField(t *testing.T) { Val2: 5, } - errs2 = validate.Struct(uIntTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "ltefield") + errs = validate.Struct(uIntTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "UIntTest.Val2", "Val2", "ltefield") - err3 = validate.FieldWithValue(uint(5), uint(1), "ltefield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(uint(5), uint(1), "ltefield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(uint(1), uint(5), "ltefield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "ltefield") + errs = validate.FieldWithValue(uint(1), uint(5), "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltefield") type FloatTest struct { Val1 float64 `validate:"required"` @@ -2513,16 +2195,16 @@ func TestLteField(t *testing.T) { Val2: 5, } - errs2 = validate.Struct(floatTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "ltefield") + errs = validate.Struct(floatTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "FloatTest.Val2", "Val2", "ltefield") - err3 = validate.FieldWithValue(float32(5), float32(1), "ltefield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(float32(5), float32(1), "ltefield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(float32(1), float32(5), "ltefield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "ltefield") + errs = validate.FieldWithValue(float32(1), float32(5), "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltefield") PanicMatches(t, func() { validate.FieldWithValue(nil, 5, "ltefield") }, "struct not passed for cross validation") PanicMatches(t, func() { validate.FieldWithValue(1, "T", "ltefield") }, "Bad field type string") @@ -2565,16 +2247,16 @@ func TestGteField(t *testing.T) { End: &start, } - errs2 := validate.Struct(timeTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "End", "gtefield") + errs = validate.Struct(timeTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest.End", "End", "gtefield") - err3 := validate.FieldWithValue(&start, &end, "gtefield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(&start, &end, "gtefield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(&end, &start, "gtefield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "gtefield") + errs = validate.FieldWithValue(&end, &start, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtefield") type IntTest struct { Val1 int `validate:"required"` @@ -2594,16 +2276,16 @@ func TestGteField(t *testing.T) { Val2: 1, } - errs2 = validate.Struct(intTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "gtefield") + errs = validate.Struct(intTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "IntTest.Val2", "Val2", "gtefield") - err3 = validate.FieldWithValue(int(1), int(5), "gtefield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(int(1), int(5), "gtefield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(int(5), int(1), "gtefield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "gtefield") + errs = validate.FieldWithValue(int(5), int(1), "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtefield") type UIntTest struct { Val1 uint `validate:"required"` @@ -2623,16 +2305,16 @@ func TestGteField(t *testing.T) { Val2: 1, } - errs2 = validate.Struct(uIntTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "gtefield") + errs = validate.Struct(uIntTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "UIntTest.Val2", "Val2", "gtefield") - err3 = validate.FieldWithValue(uint(1), uint(5), "gtefield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(uint(1), uint(5), "gtefield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(uint(5), uint(1), "gtefield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "gtefield") + errs = validate.FieldWithValue(uint(5), uint(1), "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtefield") type FloatTest struct { Val1 float64 `validate:"required"` @@ -2652,16 +2334,16 @@ func TestGteField(t *testing.T) { Val2: 1, } - errs2 = validate.Struct(floatTest).Flatten() - NotEqual(t, errs2, nil) - AssertMapFieldError(t, errs2, "Val2", "gtefield") + errs = validate.Struct(floatTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "FloatTest.Val2", "Val2", "gtefield") - err3 = validate.FieldWithValue(float32(1), float32(5), "gtefield") - Equal(t, err3, nil) + errs = validate.FieldWithValue(float32(1), float32(5), "gtefield") + Equal(t, errs, nil) - err3 = validate.FieldWithValue(float32(5), float32(1), "gtefield") - NotEqual(t, err3, nil) - Equal(t, err3.Tag, "gtefield") + errs = validate.FieldWithValue(float32(5), float32(1), "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtefield") PanicMatches(t, func() { validate.FieldWithValue(nil, 1, "gtefield") }, "struct not passed for cross validation") PanicMatches(t, func() { validate.FieldWithValue(5, "T", "gtefield") }, "Bad field type string") @@ -2684,52 +2366,60 @@ func TestValidateByTagAndValue(t *testing.T) { val := "test" field := "test" - err := validate.FieldWithValue(val, field, "required") - Equal(t, err, nil) + errs := validate.FieldWithValue(val, field, "required") + Equal(t, errs, nil) - fn := func(val interface{}, current interface{}, field interface{}, param string) bool { + fn := func(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return current.(string) == field.(string) + return current.String() == field.String() } - validate.AddFunction("isequaltestfunc", fn) + validate.RegisterValidation("isequaltestfunc", fn) - err = validate.FieldWithValue(val, field, "isequaltestfunc") - Equal(t, err, nil) + errs = validate.FieldWithValue(val, field, "isequaltestfunc") + Equal(t, errs, nil) val = "unequal" - err = validate.FieldWithValue(val, field, "isequaltestfunc") - NotEqual(t, err, nil) - Equal(t, err.Tag, "isequaltestfunc") + errs = validate.FieldWithValue(val, field, "isequaltestfunc") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "isequaltestfunc") } func TestAddFunctions(t *testing.T) { - fn := func(val interface{}, current interface{}, field interface{}, param string) bool { + fn := func(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return true } - validate := New("validateme", BakedInValidators) + config := Config{ + TagName: "validateme", + ValidationFuncs: BakedInValidators, + } + + validate := New(config) - err := validate.AddFunction("new", fn) - Equal(t, err, nil) + errs := validate.RegisterValidation("new", fn) + Equal(t, errs, nil) - err = validate.AddFunction("", fn) - NotEqual(t, err, nil) + errs = validate.RegisterValidation("", fn) + NotEqual(t, errs, nil) - validate.AddFunction("new", nil) - NotEqual(t, err, nil) + validate.RegisterValidation("new", nil) + NotEqual(t, errs, nil) - err = validate.AddFunction("new", fn) - Equal(t, err, nil) + errs = validate.RegisterValidation("new", fn) + Equal(t, errs, nil) } func TestChangeTag(t *testing.T) { - validate := New("validateme", BakedInValidators) - validate.SetTag("val") + config := Config{ + TagName: "val", + ValidationFuncs: BakedInValidators, + } + validate := New(config) type Test struct { Name string `val:"len=4"` @@ -2738,8 +2428,8 @@ func TestChangeTag(t *testing.T) { Name: "TEST", } - err := validate.Struct(s) - Equal(t, err, nil) + errs := validate.Struct(s) + Equal(t, errs, nil) } func TestUnexposedStruct(t *testing.T) { @@ -2755,15 +2445,15 @@ func TestUnexposedStruct(t *testing.T) { Name: "TEST", } - err := validate.Struct(s) - Equal(t, err, nil) + errs := validate.Struct(s) + Equal(t, errs, nil) } func TestBadParams(t *testing.T) { i := 1 - err := validate.Field(i, "-") - Equal(t, err, nil) + errs := validate.Field(i, "-") + Equal(t, errs, nil) PanicMatches(t, func() { validate.Field(i, "len=a") }, "strconv.ParseInt: parsing \"a\": invalid syntax") PanicMatches(t, func() { validate.Field(i, "len=a") }, "strconv.ParseInt: parsing \"a\": invalid syntax") @@ -2784,16 +2474,18 @@ func TestLength(t *testing.T) { func TestIsGt(t *testing.T) { myMap := map[string]string{} - err := validate.Field(myMap, "gt=0") - NotEqual(t, err, nil) + errs := validate.Field(myMap, "gt=0") + NotEqual(t, errs, nil) f := 1.23 - err = validate.Field(f, "gt=5") - NotEqual(t, err, nil) + errs = validate.Field(f, "gt=5") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gt") var ui uint = 5 - err = validate.Field(ui, "gt=10") - NotEqual(t, err, nil) + errs = validate.Field(ui, "gt=10") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gt") i := true PanicMatches(t, func() { validate.Field(i, "gt") }, "Bad field type bool") @@ -2801,14 +2493,14 @@ func TestIsGt(t *testing.T) { tm := time.Now().UTC() tm = tm.Add(time.Hour * 24) - err = validate.Field(tm, "gt") - Equal(t, err, nil) + errs = validate.Field(tm, "gt") + Equal(t, errs, nil) t2 := time.Now().UTC() - err = validate.Field(t2, "gt") - NotEqual(t, err, nil) - Equal(t, err.Tag, "gt") + errs = validate.Field(t2, "gt") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gt") type Test struct { Now *time.Time `validate:"gt"` @@ -2817,7 +2509,7 @@ func TestIsGt(t *testing.T) { Now: &tm, } - errs := validate.Struct(s) + errs = validate.Struct(s) Equal(t, errs, nil) s = &Test{ @@ -2826,6 +2518,7 @@ func TestIsGt(t *testing.T) { errs = validate.Struct(s) NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Now", "Now", "gt") } func TestIsGte(t *testing.T) { @@ -2836,15 +2529,14 @@ func TestIsGte(t *testing.T) { t1 := time.Now().UTC() t1 = t1.Add(time.Hour * 24) - err := validate.Field(t1, "gte") - Equal(t, err, nil) + errs := validate.Field(t1, "gte") + Equal(t, errs, nil) t2 := time.Now().UTC() - err = validate.Field(t2, "gte") - NotEqual(t, err, nil) - Equal(t, err.Tag, "gte") - Equal(t, err.Type, reflect.TypeOf(time.Time{})) + errs = validate.Field(t2, "gte") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gte") type Test struct { Now *time.Time `validate:"gte"` @@ -2853,7 +2545,7 @@ func TestIsGte(t *testing.T) { Now: &t1, } - errs := validate.Struct(s) + errs = validate.Struct(s) Equal(t, errs, nil) s = &Test{ @@ -2862,36 +2554,40 @@ func TestIsGte(t *testing.T) { errs = validate.Struct(s) NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Now", "Now", "gte") } func TestIsLt(t *testing.T) { myMap := map[string]string{} - err := validate.Field(myMap, "lt=0") - NotEqual(t, err, nil) + errs := validate.Field(myMap, "lt=0") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "lt") f := 1.23 - err = validate.Field(f, "lt=0") - NotEqual(t, err, nil) + errs = validate.Field(f, "lt=0") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "lt") var ui uint = 5 - err = validate.Field(ui, "lt=0") - NotEqual(t, err, nil) + errs = validate.Field(ui, "lt=0") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "lt") i := true PanicMatches(t, func() { validate.Field(i, "lt") }, "Bad field type bool") t1 := time.Now().UTC() - err = validate.Field(t1, "lt") - Equal(t, err, nil) + errs = validate.Field(t1, "lt") + Equal(t, errs, nil) t2 := time.Now().UTC() t2 = t2.Add(time.Hour * 24) - err = validate.Field(t2, "lt") - NotEqual(t, err, nil) - Equal(t, err.Tag, "lt") + errs = validate.Field(t2, "lt") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "lt") type Test struct { Now *time.Time `validate:"lt"` @@ -2901,7 +2597,7 @@ func TestIsLt(t *testing.T) { Now: &t1, } - errs := validate.Struct(s) + errs = validate.Struct(s) Equal(t, errs, nil) s = &Test{ @@ -2910,6 +2606,7 @@ func TestIsLt(t *testing.T) { errs = validate.Struct(s) NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Now", "Now", "lt") } func TestIsLte(t *testing.T) { @@ -2919,15 +2616,15 @@ func TestIsLte(t *testing.T) { t1 := time.Now().UTC() - err := validate.Field(t1, "lte") - Equal(t, err, nil) + errs := validate.Field(t1, "lte") + Equal(t, errs, nil) t2 := time.Now().UTC() t2 = t2.Add(time.Hour * 24) - err = validate.Field(t2, "lte") - NotEqual(t, err, nil) - Equal(t, err.Tag, "lte") + errs = validate.Field(t2, "lte") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "lte") type Test struct { Now *time.Time `validate:"lte"` @@ -2937,7 +2634,7 @@ func TestIsLte(t *testing.T) { Now: &t1, } - errs := validate.Struct(s) + errs = validate.Struct(s) Equal(t, errs, nil) s = &Test{ @@ -2990,15 +2687,20 @@ func TestUrl(t *testing.T) { } for i, test := range tests { - err := validate.Field(test.param, "url") + errs := validate.Field(test.param, "url") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d URL failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d URL failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "url") { - t.Fatalf("Index: %d URL failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d URL failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "url" { + t.Fatalf("Index: %d URL failed Error: %s", i, errs) + } } } } @@ -3049,15 +2751,20 @@ func TestUri(t *testing.T) { } for i, test := range tests { - err := validate.Field(test.param, "uri") + errs := validate.Field(test.param, "uri") if test.expected == true { - if !IsEqual(t, err, nil) { - t.Fatalf("Index: %d URI failed Error: %s", i, err) + if !IsEqual(t, errs, nil) { + t.Fatalf("Index: %d URI failed Error: %s", i, errs) } } else { - if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "uri") { - t.Fatalf("Index: %d URI failed Error: %s", i, err) + if IsEqual(t, errs, nil) { + t.Fatalf("Index: %d URI failed Error: %s", i, errs) + } else { + val := errs[""] + if val.Tag != "uri" { + t.Fatalf("Index: %d URI failed Error: %s", i, errs) + } } } } @@ -3068,70 +2775,75 @@ func TestUri(t *testing.T) { func TestOrTag(t *testing.T) { s := "rgba(0,31,255,0.5)" - err := validate.Field(s, "rgb|rgba") - Equal(t, err, nil) + errs := validate.Field(s, "rgb|rgba") + Equal(t, errs, nil) s = "rgba(0,31,255,0.5)" - err = validate.Field(s, "rgb|rgba|len=18") - Equal(t, err, nil) + errs = validate.Field(s, "rgb|rgba|len=18") + Equal(t, errs, nil) s = "this ain't right" - err = validate.Field(s, "rgb|rgba") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgb|rgba") + errs = validate.Field(s, "rgb|rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgb|rgba") s = "this ain't right" - err = validate.Field(s, "rgb|rgba|len=10") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgb|rgba|len") + errs = validate.Field(s, "rgb|rgba|len=10") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgb|rgba|len") s = "this is right" - err = validate.Field(s, "rgb|rgba|len=13") - Equal(t, err, nil) + errs = validate.Field(s, "rgb|rgba|len=13") + Equal(t, errs, nil) s = "" - err = validate.Field(s, "omitempty,rgb|rgba") - Equal(t, err, nil) + errs = validate.Field(s, "omitempty,rgb|rgba") + Equal(t, errs, nil) + + s = "this is right, but a blank or isn't" + + PanicMatches(t, func() { validate.Field(s, "rgb||len=13") }, "Invalid validation tag on field") + PanicMatches(t, func() { validate.Field(s, "rgb|rgbaa|len=13") }, "Undefined validation function on field") } func TestHsla(t *testing.T) { s := "hsla(360,100%,100%,1)" - err := validate.Field(s, "hsla") - Equal(t, err, nil) + errs := validate.Field(s, "hsla") + Equal(t, errs, nil) s = "hsla(360,100%,100%,0.5)" - err = validate.Field(s, "hsla") - Equal(t, err, nil) + errs = validate.Field(s, "hsla") + Equal(t, errs, nil) s = "hsla(0,0%,0%, 0)" - err = validate.Field(s, "hsla") - Equal(t, err, nil) + errs = validate.Field(s, "hsla") + Equal(t, errs, nil) s = "hsl(361,100%,50%,1)" - err = validate.Field(s, "hsla") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hsla") + errs = validate.Field(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsla") s = "hsl(361,100%,50%)" - err = validate.Field(s, "hsla") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hsla") + errs = validate.Field(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsla") s = "hsla(361,100%,50%)" - err = validate.Field(s, "hsla") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hsla") + errs = validate.Field(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsla") s = "hsla(360,101%,50%)" - err = validate.Field(s, "hsla") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hsla") + errs = validate.Field(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsla") s = "hsla(360,100%,101%)" - err = validate.Field(s, "hsla") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hsla") + errs = validate.Field(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsla") i := 1 PanicMatches(t, func() { validate.Field(i, "hsla") }, "interface conversion: interface is int, not string") @@ -3140,32 +2852,32 @@ func TestHsla(t *testing.T) { func TestHsl(t *testing.T) { s := "hsl(360,100%,50%)" - err := validate.Field(s, "hsl") - Equal(t, err, nil) + errs := validate.Field(s, "hsl") + Equal(t, errs, nil) s = "hsl(0,0%,0%)" - err = validate.Field(s, "hsl") - Equal(t, err, nil) + errs = validate.Field(s, "hsl") + Equal(t, errs, nil) s = "hsl(361,100%,50%)" - err = validate.Field(s, "hsl") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hsl") + errs = validate.Field(s, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsl") s = "hsl(361,101%,50%)" - err = validate.Field(s, "hsl") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hsl") + errs = validate.Field(s, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsl") s = "hsl(361,100%,101%)" - err = validate.Field(s, "hsl") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hsl") + errs = validate.Field(s, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsl") s = "hsl(-10,100%,100%)" - err = validate.Field(s, "hsl") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hsl") + errs = validate.Field(s, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hsl") i := 1 PanicMatches(t, func() { validate.Field(i, "hsl") }, "interface conversion: interface is int, not string") @@ -3174,40 +2886,40 @@ func TestHsl(t *testing.T) { func TestRgba(t *testing.T) { s := "rgba(0,31,255,0.5)" - err := validate.Field(s, "rgba") - Equal(t, err, nil) + errs := validate.Field(s, "rgba") + Equal(t, errs, nil) s = "rgba(0,31,255,0.12)" - err = validate.Field(s, "rgba") - Equal(t, err, nil) + errs = validate.Field(s, "rgba") + Equal(t, errs, nil) s = "rgba(12%,55%,100%,0.12)" - err = validate.Field(s, "rgba") - Equal(t, err, nil) + errs = validate.Field(s, "rgba") + Equal(t, errs, nil) s = "rgba( 0, 31, 255, 0.5)" - err = validate.Field(s, "rgba") - Equal(t, err, nil) + errs = validate.Field(s, "rgba") + Equal(t, errs, nil) s = "rgba(12%,55,100%,0.12)" - err = validate.Field(s, "rgba") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgba") + errs = validate.Field(s, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgba") s = "rgb(0, 31, 255)" - err = validate.Field(s, "rgba") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgba") + errs = validate.Field(s, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgba") s = "rgb(1,349,275,0.5)" - err = validate.Field(s, "rgba") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgba") + errs = validate.Field(s, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgba") s = "rgb(01,31,255,0.5)" - err = validate.Field(s, "rgba") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgba") + errs = validate.Field(s, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgba") i := 1 PanicMatches(t, func() { validate.Field(i, "rgba") }, "interface conversion: interface is int, not string") @@ -3216,36 +2928,36 @@ func TestRgba(t *testing.T) { func TestRgb(t *testing.T) { s := "rgb(0,31,255)" - err := validate.Field(s, "rgb") - Equal(t, err, nil) + errs := validate.Field(s, "rgb") + Equal(t, errs, nil) s = "rgb(0, 31, 255)" - err = validate.Field(s, "rgb") - Equal(t, err, nil) + errs = validate.Field(s, "rgb") + Equal(t, errs, nil) s = "rgb(10%, 50%, 100%)" - err = validate.Field(s, "rgb") - Equal(t, err, nil) + errs = validate.Field(s, "rgb") + Equal(t, errs, nil) s = "rgb(10%, 50%, 55)" - err = validate.Field(s, "rgb") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgb") + errs = validate.Field(s, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgb") s = "rgb(1,349,275)" - err = validate.Field(s, "rgb") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgb") + errs = validate.Field(s, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgb") s = "rgb(01,31,255)" - err = validate.Field(s, "rgb") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgb") + errs = validate.Field(s, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgb") s = "rgba(0,31,255)" - err = validate.Field(s, "rgb") - NotEqual(t, err, nil) - Equal(t, err.Tag, "rgb") + errs = validate.Field(s, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "rgb") i := 1 PanicMatches(t, func() { validate.Field(i, "rgb") }, "interface conversion: interface is int, not string") @@ -3254,28 +2966,28 @@ func TestRgb(t *testing.T) { func TestEmail(t *testing.T) { s := "test@mail.com" - err := validate.Field(s, "email") - Equal(t, err, nil) + errs := validate.Field(s, "email") + Equal(t, errs, nil) s = "" - err = validate.Field(s, "email") - NotEqual(t, err, nil) - Equal(t, err.Tag, "email") + errs = validate.Field(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "email") s = "test@email" - err = validate.Field(s, "email") - NotEqual(t, err, nil) - Equal(t, err.Tag, "email") + errs = validate.Field(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "email") s = "test@email." - err = validate.Field(s, "email") - NotEqual(t, err, nil) - Equal(t, err.Tag, "email") + errs = validate.Field(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "email") s = "@email.com" - err = validate.Field(s, "email") - NotEqual(t, err, nil) - Equal(t, err.Tag, "email") + errs = validate.Field(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "email") i := true PanicMatches(t, func() { validate.Field(i, "email") }, "interface conversion: interface is bool, not string") @@ -3284,22 +2996,22 @@ func TestEmail(t *testing.T) { func TestHexColor(t *testing.T) { s := "#fff" - err := validate.Field(s, "hexcolor") - Equal(t, err, nil) + errs := validate.Field(s, "hexcolor") + Equal(t, errs, nil) s = "#c2c2c2" - err = validate.Field(s, "hexcolor") - Equal(t, err, nil) + errs = validate.Field(s, "hexcolor") + Equal(t, errs, nil) s = "fff" - err = validate.Field(s, "hexcolor") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hexcolor") + errs = validate.Field(s, "hexcolor") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hexcolor") s = "fffFF" - err = validate.Field(s, "hexcolor") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hexcolor") + errs = validate.Field(s, "hexcolor") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hexcolor") i := true PanicMatches(t, func() { validate.Field(i, "hexcolor") }, "interface conversion: interface is bool, not string") @@ -3308,13 +3020,13 @@ func TestHexColor(t *testing.T) { func TestHexadecimal(t *testing.T) { s := "ff0044" - err := validate.Field(s, "hexadecimal") - Equal(t, err, nil) + errs := validate.Field(s, "hexadecimal") + Equal(t, errs, nil) s = "abcdefg" - err = validate.Field(s, "hexadecimal") - NotEqual(t, err, nil) - Equal(t, err.Tag, "hexadecimal") + errs = validate.Field(s, "hexadecimal") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "hexadecimal") i := true PanicMatches(t, func() { validate.Field(i, "hexadecimal") }, "interface conversion: interface is bool, not string") @@ -3323,43 +3035,43 @@ func TestHexadecimal(t *testing.T) { func TestNumber(t *testing.T) { s := "1" - err := validate.Field(s, "number") - Equal(t, err, nil) + errs := validate.Field(s, "number") + Equal(t, errs, nil) s = "+1" - err = validate.Field(s, "number") - NotEqual(t, err, nil) - Equal(t, err.Tag, "number") + errs = validate.Field(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "number") s = "-1" - err = validate.Field(s, "number") - NotEqual(t, err, nil) - Equal(t, err.Tag, "number") + errs = validate.Field(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "number") s = "1.12" - err = validate.Field(s, "number") - NotEqual(t, err, nil) - Equal(t, err.Tag, "number") + errs = validate.Field(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "number") s = "+1.12" - err = validate.Field(s, "number") - NotEqual(t, err, nil) - Equal(t, err.Tag, "number") + errs = validate.Field(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "number") s = "-1.12" - err = validate.Field(s, "number") - NotEqual(t, err, nil) - Equal(t, err.Tag, "number") + errs = validate.Field(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "number") s = "1." - err = validate.Field(s, "number") - NotEqual(t, err, nil) - Equal(t, err.Tag, "number") + errs = validate.Field(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "number") s = "1.o" - err = validate.Field(s, "number") - NotEqual(t, err, nil) - Equal(t, err.Tag, "number") + errs = validate.Field(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "number") i := 1 PanicMatches(t, func() { validate.Field(i, "number") }, "interface conversion: interface is int, not string") @@ -3368,38 +3080,38 @@ func TestNumber(t *testing.T) { func TestNumeric(t *testing.T) { s := "1" - err := validate.Field(s, "numeric") - Equal(t, err, nil) + errs := validate.Field(s, "numeric") + Equal(t, errs, nil) s = "+1" - err = validate.Field(s, "numeric") - Equal(t, err, nil) + errs = validate.Field(s, "numeric") + Equal(t, errs, nil) s = "-1" - err = validate.Field(s, "numeric") - Equal(t, err, nil) + errs = validate.Field(s, "numeric") + Equal(t, errs, nil) s = "1.12" - err = validate.Field(s, "numeric") - Equal(t, err, nil) + errs = validate.Field(s, "numeric") + Equal(t, errs, nil) s = "+1.12" - err = validate.Field(s, "numeric") - Equal(t, err, nil) + errs = validate.Field(s, "numeric") + Equal(t, errs, nil) s = "-1.12" - err = validate.Field(s, "numeric") - Equal(t, err, nil) + errs = validate.Field(s, "numeric") + Equal(t, errs, nil) s = "1." - err = validate.Field(s, "numeric") - NotEqual(t, err, nil) - Equal(t, err.Tag, "numeric") + errs = validate.Field(s, "numeric") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "numeric") s = "1.o" - err = validate.Field(s, "numeric") - NotEqual(t, err, nil) - Equal(t, err.Tag, "numeric") + errs = validate.Field(s, "numeric") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "numeric") i := 1 PanicMatches(t, func() { validate.Field(i, "numeric") }, "interface conversion: interface is int, not string") @@ -3408,13 +3120,13 @@ func TestNumeric(t *testing.T) { func TestAlphaNumeric(t *testing.T) { s := "abcd123" - err := validate.Field(s, "alphanum") - Equal(t, err, nil) + errs := validate.Field(s, "alphanum") + Equal(t, errs, nil) s = "abc!23" - err = validate.Field(s, "alphanum") - NotEqual(t, err, nil) - Equal(t, err.Tag, "alphanum") + errs = validate.Field(s, "alphanum") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "alphanum") PanicMatches(t, func() { validate.Field(1, "alphanum") }, "interface conversion: interface is int, not string") } @@ -3422,93 +3134,20 @@ func TestAlphaNumeric(t *testing.T) { func TestAlpha(t *testing.T) { s := "abcd" - err := validate.Field(s, "alpha") - Equal(t, err, nil) + errs := validate.Field(s, "alpha") + Equal(t, errs, nil) s = "abc1" - err = validate.Field(s, "alpha") - NotEqual(t, err, nil) - Equal(t, err.Tag, "alpha") - - PanicMatches(t, func() { validate.Field(1, "alpha") }, "interface conversion: interface is int, not string") -} - -func TestFlattening(t *testing.T) { - - tSuccess := &TestString{ - Required: "Required", - Len: "length==10", - Min: "min=1", - Max: "1234567890", - MinMax: "12345", - Lt: "012345678", - Lte: "0123456789", - Gt: "01234567890", - Gte: "0123456789", - OmitEmpty: "", - Sub: &SubTest{ - Test: "1", - }, - SubIgnore: &SubTest{ - Test: "", - }, - Anonymous: struct { - A string `validate:"required"` - }{ - A: "1", - }, - Iface: &Impl{ - F: "123", - }, - } - - err1 := validate.Struct(tSuccess).Flatten() - Equal(t, len(err1), 0) - - tFail := &TestString{ - Required: "", - Len: "", - Min: "", - Max: "12345678901", - MinMax: "", - OmitEmpty: "12345678901", - Sub: &SubTest{ - Test: "", - }, - Anonymous: struct { - A string `validate:"required"` - }{ - A: "", - }, - Iface: &Impl{ - F: "12", - }, - } - - err2 := validate.Struct(tFail).Flatten() - - // Assert Top Level - NotEqual(t, err2, nil) - - // Assert Fields - AssertMapFieldError(t, err2, "Len", "len") - AssertMapFieldError(t, err2, "Gt", "gt") - AssertMapFieldError(t, err2, "Gte", "gte") - - // Assert Struct Field - AssertMapFieldError(t, err2, "Sub.Test", "required") + errs = validate.Field(s, "alpha") + NotEqual(t, errs, nil) - // Assert Anonymous Struct Field - AssertMapFieldError(t, err2, "Anonymous.A", "required") + AssertError(t, errs, "", "", "alpha") - // Assert Interface Field - AssertMapFieldError(t, err2, "Iface.F", "len") + PanicMatches(t, func() { validate.Field(1, "alpha") }, "interface conversion: interface is int, not string") } func TestStructStringValidation(t *testing.T) { - validate.SetMaxStructPoolSize(11) - tSuccess := &TestString{ Required: "Required", Len: "length==10", @@ -3536,8 +3175,8 @@ func TestStructStringValidation(t *testing.T) { }, } - err := validate.Struct(tSuccess) - Equal(t, err, nil) + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) tFail := &TestString{ Required: "", @@ -3563,36 +3202,28 @@ func TestStructStringValidation(t *testing.T) { }, } - err = validate.Struct(tFail) + errs = validate.Struct(tFail) // Assert Top Level - NotEqual(t, err, nil) - Equal(t, err.Struct, "TestString") - Equal(t, len(err.Errors), 10) - Equal(t, len(err.StructErrors), 3) + NotEqual(t, errs, nil) + Equal(t, len(errs), 13) // Assert Fields - AssertFieldError(t, err, "Required", "required") - AssertFieldError(t, err, "Len", "len") - AssertFieldError(t, err, "Min", "min") - AssertFieldError(t, err, "Max", "max") - AssertFieldError(t, err, "MinMax", "min") - AssertFieldError(t, err, "Gt", "gt") - AssertFieldError(t, err, "Gte", "gte") - AssertFieldError(t, err, "OmitEmpty", "max") - - // Assert Anonymous embedded struct - AssertStruct(t, err, "Anonymous", "") - - // Assert SubTest embedded struct - val := AssertStruct(t, err, "Sub", "SubTest") - Equal(t, len(val.Errors), 1) - Equal(t, len(val.StructErrors), 0) - - AssertFieldError(t, val, "Test", "required") - - errors := err.Error() - NotEqual(t, errors, nil) + AssertError(t, errs, "TestString.Required", "Required", "required") + AssertError(t, errs, "TestString.Len", "Len", "len") + AssertError(t, errs, "TestString.Min", "Min", "min") + AssertError(t, errs, "TestString.Max", "Max", "max") + AssertError(t, errs, "TestString.MinMax", "MinMax", "min") + AssertError(t, errs, "TestString.Lt", "Lt", "lt") + AssertError(t, errs, "TestString.Lte", "Lte", "lte") + AssertError(t, errs, "TestString.Gt", "Gt", "gt") + AssertError(t, errs, "TestString.Gte", "Gte", "gte") + AssertError(t, errs, "TestString.OmitEmpty", "OmitEmpty", "max") + + // Nested Struct Field Errs + AssertError(t, errs, "TestString.Anonymous.A", "A", "required") + AssertError(t, errs, "TestString.Sub.Test", "Test", "required") + AssertError(t, errs, "TestString.Iface.F", "F", "len") } func TestStructInt32Validation(t *testing.T) { @@ -3610,8 +3241,8 @@ func TestStructInt32Validation(t *testing.T) { OmitEmpty: 0, } - err := validate.Struct(tSuccess) - Equal(t, err, nil) + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) tFail := &TestInt32{ Required: 0, @@ -3626,25 +3257,23 @@ func TestStructInt32Validation(t *testing.T) { OmitEmpty: 11, } - err = validate.Struct(tFail) + errs = validate.Struct(tFail) // Assert Top Level - NotEqual(t, err, nil) - Equal(t, err.Struct, "TestInt32") - Equal(t, len(err.Errors), 10) - Equal(t, len(err.StructErrors), 0) + NotEqual(t, errs, nil) + Equal(t, len(errs), 10) // Assert Fields - AssertFieldError(t, err, "Required", "required") - AssertFieldError(t, err, "Len", "len") - AssertFieldError(t, err, "Min", "min") - AssertFieldError(t, err, "Max", "max") - AssertFieldError(t, err, "MinMax", "min") - AssertFieldError(t, err, "Lt", "lt") - AssertFieldError(t, err, "Lte", "lte") - AssertFieldError(t, err, "Gt", "gt") - AssertFieldError(t, err, "Gte", "gte") - AssertFieldError(t, err, "OmitEmpty", "max") + AssertError(t, errs, "TestInt32.Required", "Required", "required") + AssertError(t, errs, "TestInt32.Len", "Len", "len") + AssertError(t, errs, "TestInt32.Min", "Min", "min") + AssertError(t, errs, "TestInt32.Max", "Max", "max") + AssertError(t, errs, "TestInt32.MinMax", "MinMax", "min") + AssertError(t, errs, "TestInt32.Lt", "Lt", "lt") + AssertError(t, errs, "TestInt32.Lte", "Lte", "lte") + AssertError(t, errs, "TestInt32.Gt", "Gt", "gt") + AssertError(t, errs, "TestInt32.Gte", "Gte", "gte") + AssertError(t, errs, "TestInt32.OmitEmpty", "OmitEmpty", "max") } func TestStructUint64Validation(t *testing.T) { @@ -3658,8 +3287,8 @@ func TestStructUint64Validation(t *testing.T) { OmitEmpty: 0, } - err := validate.Struct(tSuccess) - Equal(t, err, nil) + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) tFail := &TestUint64{ Required: 0, @@ -3670,21 +3299,19 @@ func TestStructUint64Validation(t *testing.T) { OmitEmpty: 11, } - err = validate.Struct(tFail) + errs = validate.Struct(tFail) // Assert Top Level - NotEqual(t, err, nil) - Equal(t, err.Struct, "TestUint64") - Equal(t, len(err.Errors), 6) - Equal(t, len(err.StructErrors), 0) + NotEqual(t, errs, nil) + Equal(t, len(errs), 6) // Assert Fields - AssertFieldError(t, err, "Required", "required") - AssertFieldError(t, err, "Len", "len") - AssertFieldError(t, err, "Min", "min") - AssertFieldError(t, err, "Max", "max") - AssertFieldError(t, err, "MinMax", "min") - AssertFieldError(t, err, "OmitEmpty", "max") + AssertError(t, errs, "TestUint64.Required", "Required", "required") + AssertError(t, errs, "TestUint64.Len", "Len", "len") + AssertError(t, errs, "TestUint64.Min", "Min", "min") + AssertError(t, errs, "TestUint64.Max", "Max", "max") + AssertError(t, errs, "TestUint64.MinMax", "MinMax", "min") + AssertError(t, errs, "TestUint64.OmitEmpty", "OmitEmpty", "max") } func TestStructFloat64Validation(t *testing.T) { @@ -3698,8 +3325,8 @@ func TestStructFloat64Validation(t *testing.T) { OmitEmpty: 0, } - err := validate.Struct(tSuccess) - Equal(t, err, nil) + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) tFail := &TestFloat64{ Required: 0, @@ -3710,21 +3337,19 @@ func TestStructFloat64Validation(t *testing.T) { OmitEmpty: 11, } - err = validate.Struct(tFail) + errs = validate.Struct(tFail) // Assert Top Level - NotEqual(t, err, nil) - Equal(t, err.Struct, "TestFloat64") - Equal(t, len(err.Errors), 6) - Equal(t, len(err.StructErrors), 0) + NotEqual(t, errs, nil) + Equal(t, len(errs), 6) // Assert Fields - AssertFieldError(t, err, "Required", "required") - AssertFieldError(t, err, "Len", "len") - AssertFieldError(t, err, "Min", "min") - AssertFieldError(t, err, "Max", "max") - AssertFieldError(t, err, "MinMax", "min") - AssertFieldError(t, err, "OmitEmpty", "max") + AssertError(t, errs, "TestFloat64.Required", "Required", "required") + AssertError(t, errs, "TestFloat64.Len", "Len", "len") + AssertError(t, errs, "TestFloat64.Min", "Min", "min") + AssertError(t, errs, "TestFloat64.Max", "Max", "max") + AssertError(t, errs, "TestFloat64.MinMax", "MinMax", "min") + AssertError(t, errs, "TestFloat64.OmitEmpty", "OmitEmpty", "max") } func TestStructSliceValidation(t *testing.T) { @@ -3738,8 +3363,8 @@ func TestStructSliceValidation(t *testing.T) { OmitEmpty: []int{}, } - err := validate.Struct(tSuccess) - Equal(t, err, nil) + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) tFail := &TestSlice{ Required: []int{}, @@ -3750,21 +3375,17 @@ func TestStructSliceValidation(t *testing.T) { OmitEmpty: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, } - err = validate.Struct(tFail) - - // Assert Top Level - NotEqual(t, err, nil) - Equal(t, err.Struct, "TestSlice") - Equal(t, len(err.Errors), 6) - Equal(t, len(err.StructErrors), 0) + errs = validate.Struct(tFail) + NotEqual(t, errs, nil) + Equal(t, len(errs), 6) - // Assert Fields - AssertFieldError(t, err, "Required", "required") - AssertFieldError(t, err, "Len", "len") - AssertFieldError(t, err, "Min", "min") - AssertFieldError(t, err, "Max", "max") - AssertFieldError(t, err, "MinMax", "min") - AssertFieldError(t, err, "OmitEmpty", "max") + // Assert Field Errors + AssertError(t, errs, "TestSlice.Required", "Required", "required") + AssertError(t, errs, "TestSlice.Len", "Len", "len") + AssertError(t, errs, "TestSlice.Min", "Min", "min") + AssertError(t, errs, "TestSlice.Max", "Max", "max") + AssertError(t, errs, "TestSlice.MinMax", "MinMax", "min") + AssertError(t, errs, "TestSlice.OmitEmpty", "OmitEmpty", "max") } func TestInvalidStruct(t *testing.T) { @@ -3772,7 +3393,7 @@ func TestInvalidStruct(t *testing.T) { Test: "1", } - PanicMatches(t, func() { validate.Struct(s.Test) }, "interface passed for validation is not a struct") + PanicMatches(t, func() { validate.Struct(s.Test) }, "value passed for validation is not a struct") } func TestInvalidField(t *testing.T) { @@ -3780,7 +3401,7 @@ func TestInvalidField(t *testing.T) { Test: "1", } - PanicMatches(t, func() { validate.Field(s, "required") }, "Invalid field passed to fieldWithNameAndValue") + PanicMatches(t, func() { validate.Field(s, "required") }, "Invalid field passed to traverseField") } func TestInvalidTagField(t *testing.T) { @@ -3796,25 +3417,5 @@ func TestInvalidValidatorFunction(t *testing.T) { Test: "1", } - PanicMatches(t, func() { validate.Field(s.Test, "zzxxBadFunction") }, fmt.Sprintf("Undefined validation function on field %s", "")) -} - -func TestPoolObjectMaxSizeValidation(t *testing.T) { - // this will ensure that the pool objects are let go - // when the pool is saturated - validate.SetMaxStructPoolSize(0) - - tSuccess := &TestSlice{ - Required: []int{1}, - Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, - Min: []int{1, 2}, - Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, - MinMax: []int{1, 2, 3, 4, 5}, - OmitEmpty: []int{}, - } - - for i := 0; i < 2; i++ { - err := validate.Struct(tSuccess) - Equal(t, err, nil) - } + PanicMatches(t, func() { validate.Field(s.Test, "zzxxBadFunction") }, "Undefined validation function on field") }