diff --git a/.gitignore b/.gitignore index 7e9b50032..792ca00d2 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ _testmain.go *.prof *.test *.out +*.txt cover.html README.html \ No newline at end of file diff --git a/README.md b/README.md index 25233aa76..4bf4d2328 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@ 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://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) +[![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v7)](https://coveralls.io/r/bluesuncorp/validator?branch=v7) +[![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v7?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v7) Package validator implements value validations for structs and individual fields based on tags. It has the following **unique** features: -- Cross Field and Cross Struct validations. +- Cross Field and Cross Struct validations by using validation tags or custom validators. - Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated. - Handles type interface by determining it's underlying type prior to validation. - Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29) @@ -20,20 +20,20 @@ Installation Use go get. - go get gopkg.in/bluesuncorp/validator.v6 + go get gopkg.in/bluesuncorp/validator.v7 or to update - go get -u gopkg.in/bluesuncorp/validator.v6 + go get -u gopkg.in/bluesuncorp/validator.v7 Then import the validator package into your own code. - import "gopkg.in/bluesuncorp/validator.v6" + import "gopkg.in/bluesuncorp/validator.v7" Usage and documentation ------ -Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs. +Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v7 for detailed usage docs. ##### Examples: @@ -143,7 +143,7 @@ import ( "fmt" "reflect" - "gopkg.in/bluesuncorp/validator.v6" + "gopkg.in/bluesuncorp/validator.v7" ) // DbBackedUser User struct @@ -187,29 +187,35 @@ func ValidateValuer(field reflect.Value) interface{} { 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. +###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go 1.5 ```go -$ go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkFieldSuccess-4 5000000 318 ns/op 16 B/op 1 allocs/op -BenchmarkFieldFailure-4 5000000 316 ns/op 16 B/op 1 allocs/op -BenchmarkFieldCustomTypeSuccess-4 3000000 492 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 2000000 843 ns/op 416 B/op 6 allocs/op -BenchmarkFieldOrTagSuccess-4 500000 2384 ns/op 20 B/op 2 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1295 ns/op 384 B/op 6 allocs/op -BenchmarkStructSimpleSuccess-4 1000000 1175 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailure-4 1000000 1822 ns/op 529 B/op 11 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1302 ns/op 56 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1847 ns/op 577 B/op 13 allocs/op -BenchmarkStructSimpleSuccessParallel-4 5000000 339 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 733 ns/op 529 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 200000 7104 ns/op 368 B/op 30 allocs/op -BenchmarkStructComplexFailure-4 100000 11996 ns/op 2861 B/op 72 allocs/op -BenchmarkStructComplexSuccessParallel-4 1000000 2252 ns/op 368 B/op 30 allocs/op -BenchmarkStructComplexFailureParallel-4 300000 4691 ns/op 2862 B/op 72 allocs/op +BenchmarkFieldSuccess-4 5000000 290 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 286 ns/op 16 B/op 1 allocs/op +BenchmarkFieldDiveSuccess-4 500000 2497 ns/op 384 B/op 19 allocs/op +BenchmarkFieldDiveFailure-4 500000 3022 ns/op 752 B/op 23 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 446 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 778 ns/op 416 B/op 6 allocs/op +BenchmarkFieldOrTagSuccess-4 1000000 1287 ns/op 32 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1125 ns/op 400 B/op 6 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1225 ns/op 80 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1742 ns/op 608 B/op 13 allocs/op +BenchmarkStructPartialSuccess-4 1000000 1304 ns/op 400 B/op 11 allocs/op +BenchmarkStructPartialFailure-4 1000000 1818 ns/op 784 B/op 16 allocs/op +BenchmarkStructExceptSuccess-4 2000000 869 ns/op 368 B/op 9 allocs/op +BenchmarkStructExceptFailure-4 1000000 1308 ns/op 400 B/op 11 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-4 2000000 973 ns/op 128 B/op 6 allocs/op +BenchmarkStructSimpleCrossFieldFailure-4 1000000 1519 ns/op 528 B/op 11 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1382 ns/op 160 B/op 8 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 1931 ns/op 560 B/op 13 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1132 ns/op 48 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1735 ns/op 560 B/op 11 allocs/op +BenchmarkStructSimpleSuccessParallel-4 3000000 363 ns/op 48 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 705 ns/op 560 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 6935 ns/op 432 B/op 27 allocs/op +BenchmarkStructComplexFailure-4 200000 11059 ns/op 2920 B/op 69 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2220 ns/op 432 B/op 27 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 4739 ns/op 2920 B/op 69 allocs/op ``` How to Contribute diff --git a/baked_in.go b/baked_in.go index b32386328..f9a0245ec 100644 --- a/baked_in.go +++ b/baked_in.go @@ -5,7 +5,6 @@ import ( "net" "net/url" "reflect" - "strconv" "strings" "time" "unicode/utf8" @@ -26,6 +25,12 @@ var BakedInValidators = map[string]Func{ "gt": isGt, "gte": isGte, "eqfield": isEqField, + "eqcsfield": isEqCrossStructField, + "necsfield": isNeCrossStructField, + "gtcsfield": isGtCrossStructField, + "gtecsfield": isGteCrossStructField, + "ltcsfield": isLtCrossStructField, + "ltecsfield": isLteCrossStructField, "nefield": isNeField, "gtefield": isGteField, "gtfield": isGtField, @@ -71,32 +76,32 @@ var BakedInValidators = map[string]Func{ "mac": isMac, } -func isMac(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isMac(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { _, err := net.ParseMAC(field.String()) return err == nil } -func isIPv4(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isIPv4(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { ip := net.ParseIP(field.String()) return ip != nil && ip.To4() != nil } -func isIPv6(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isIPv6(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { ip := net.ParseIP(field.String()) return ip != nil && ip.To4() == nil } -func isIP(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isIP(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { ip := net.ParseIP(field.String()) return ip != nil } -func isSSN(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isSSN(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { if field.Len() != 11 { return false @@ -105,15 +110,15 @@ func isSSN(topStruct reflect.Value, currentStruct reflect.Value, field reflect.V return matchesRegex(sSNRegex, field.String()) } -func isLongitude(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isLongitude(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(longitudeRegex, field.String()) } -func isLatitude(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isLatitude(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(latitudeRegex, field.String()) } -func isDataURI(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isDataURI(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { uri := strings.SplitN(field.String(), ",", 2) @@ -127,10 +132,10 @@ func isDataURI(topStruct reflect.Value, currentStruct reflect.Value, field refle fld := reflect.ValueOf(uri[1]) - return isBase64(topStruct, currentStruct, fld, fld.Type(), fld.Kind(), param) + return isBase64(v, topStruct, currentStructOrField, fld, fld.Type(), fld.Kind(), param) } -func hasMultiByteCharacter(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func hasMultiByteCharacter(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { if field.Len() == 0 { return true @@ -139,35 +144,35 @@ func hasMultiByteCharacter(topStruct reflect.Value, currentStruct reflect.Value, return matchesRegex(multibyteRegex, field.String()) } -func isPrintableASCII(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isPrintableASCII(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(printableASCIIRegex, field.String()) } -func isASCII(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isASCII(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(aSCIIRegex, field.String()) } -func isUUID5(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isUUID5(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(uUID5Regex, field.String()) } -func isUUID4(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isUUID4(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(uUID4Regex, field.String()) } -func isUUID3(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isUUID3(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(uUID3Regex, field.String()) } -func isUUID(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isUUID(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(uUIDRegex, field.String()) } -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 isISBN(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return isISBN10(v, topStruct, currentStructOrField, field, fieldType, fieldKind, param) || isISBN13(v, topStruct, currentStructOrField, field, fieldType, fieldKind, param) } -func isISBN13(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isISBN13(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { s := strings.Replace(strings.Replace(field.String(), "-", "", 4), " ", "", 4) @@ -191,7 +196,7 @@ func isISBN13(topStruct reflect.Value, currentStruct reflect.Value, field reflec return false } -func isISBN10(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isISBN10(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { s := strings.Replace(strings.Replace(field.String(), "-", "", 3), " ", "", 3) @@ -219,105 +224,368 @@ func isISBN10(topStruct reflect.Value, currentStruct reflect.Value, field reflec return false } -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 excludesRune(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !containsRune(v, topStruct, currentStructOrField, field, fieldType, fieldKind, 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 excludesAll(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !containsAny(v, topStruct, currentStructOrField, field, fieldType, fieldKind, 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 excludes(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !contains(v, topStruct, currentStructOrField, field, fieldType, fieldKind, param) } -func containsRune(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func containsRune(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { r, _ := utf8.DecodeRuneInString(param) return strings.ContainsRune(field.String(), r) } -func containsAny(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func containsAny(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return strings.ContainsAny(field.String(), param) } -func contains(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func contains(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return strings.Contains(field.String(), 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 isNeField(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) + + if !ok || currentKind != fieldKind { + return true + } + + switch fieldKind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() != currentField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() != currentField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() != currentField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) != int64(currentField.Len()) + + case reflect.Struct: + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return true + } + + if fieldType == timeType { + + t := currentField.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) + + return !fieldTime.Equal(t) + } + + } + + // default reflect.String: + return field.String() != currentField.String() } -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 isNe(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !isEq(v, topStruct, currentStructOrField, field, fieldType, fieldKind, param) } -func isEqField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isLteCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + topField, topKind, ok := v.getStructFieldOK(topStruct, param) + if !ok || topKind != fieldKind { + return false + } + + switch fieldKind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() <= topField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() <= topField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() <= topField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) <= int64(topField.Len()) - // if current == nil { - if !current.IsValid() { - panic("struct or field value not passed for cross validation") + case reflect.Struct: + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } + + if fieldType == timeType { + + fieldTime := field.Interface().(time.Time) + topTime := topField.Interface().(time.Time) + + return fieldTime.Before(topTime) || fieldTime.Equal(topTime) + } } - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() + // default reflect.String: + return field.String() <= topField.String() +} + +func isLtCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + topField, topKind, ok := v.getStructFieldOK(topStruct, param) + if !ok || topKind != fieldKind { + return false } - switch current.Kind() { + switch fieldKind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() < topField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() < topField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() < topField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) < int64(topField.Len()) case reflect.Struct: - if current.Type() == timeType || current.Type() == timePtrType { - break + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false } - current = current.FieldByName(param) + if fieldType == timeType { + + fieldTime := field.Interface().(time.Time) + topTime := topField.Interface().(time.Time) - if current.Kind() == reflect.Invalid { - panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) + return fieldTime.Before(topTime) } } - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() + // default reflect.String: + return field.String() < topField.String() +} + +func isGteCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + topField, topKind, ok := v.getStructFieldOK(topStruct, param) + if !ok || topKind != fieldKind { + return false } switch fieldKind { - case reflect.String: - return field.String() == current.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() >= topField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() >= topField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() >= topField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) >= int64(topField.Len()) + + case reflect.Struct: + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } + + if fieldType == timeType { + + fieldTime := field.Interface().(time.Time) + topTime := topField.Interface().(time.Time) + + return fieldTime.After(topTime) || fieldTime.Equal(topTime) + } + } + + // default reflect.String: + return field.String() >= topField.String() +} + +func isGtCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + topField, topKind, ok := v.getStructFieldOK(topStruct, param) + if !ok || topKind != fieldKind { + return false + } + + switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return field.Int() == current.Int() + return field.Int() > topField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return field.Uint() == current.Uint() + return field.Uint() > topField.Uint() case reflect.Float32, reflect.Float64: - return field.Float() == current.Float() + return field.Float() > topField.Float() case reflect.Slice, reflect.Map, reflect.Array: - return int64(field.Len()) == int64(current.Len()) + return int64(field.Len()) > int64(topField.Len()) case reflect.Struct: - if fieldType == timeType || fieldType == timePtrType { - if current.Type() != timeType && current.Type() != timePtrType { - panic("Bad Top Level field type") - } + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } + + if fieldType == timeType { + + fieldTime := field.Interface().(time.Time) + topTime := topField.Interface().(time.Time) + + return fieldTime.After(topTime) + } + } + + // default reflect.String: + return field.String() > topField.String() +} + +func isNeCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + topField, currentKind, ok := v.getStructFieldOK(topStruct, param) + if !ok || currentKind != fieldKind { + return true + } + + switch fieldKind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return topField.Int() != field.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return topField.Uint() != field.Uint() + + case reflect.Float32, reflect.Float64: + return topField.Float() != field.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(topField.Len()) != int64(field.Len()) + + case reflect.Struct: + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return true + } + + if fieldType == timeType { + + t := field.Interface().(time.Time) + fieldTime := topField.Interface().(time.Time) + + return !fieldTime.Equal(t) + } + } + + // default reflect.String: + return topField.String() != field.String() +} + +func isEqCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + topField, topKind, ok := v.getStructFieldOK(topStruct, param) + if !ok || topKind != fieldKind { + return false + } + + switch fieldKind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return topField.Int() == field.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return topField.Uint() == field.Uint() + + case reflect.Float32, reflect.Float64: + return topField.Float() == field.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(topField.Len()) == int64(field.Len()) + + case reflect.Struct: + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } + + if fieldType == timeType { + + t := field.Interface().(time.Time) + fieldTime := topField.Interface().(time.Time) + + return fieldTime.Equal(t) + } + } + + // default reflect.String: + return topField.String() == field.String() +} + +func isEqField(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) + if !ok || currentKind != fieldKind { + return false + } + + switch fieldKind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() == currentField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() == currentField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() == currentField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) == int64(currentField.Len()) - t := current.Interface().(time.Time) + case reflect.Struct: + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } + + if fieldType == timeType { + + t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.Equal(t) } + } - panic(fmt.Sprintf("Bad field type %T", field.Interface())) + // default reflect.String: + return field.String() == currentField.String() } -func isEq(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isEq(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -348,11 +616,11 @@ func isEq(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Va panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isBase64(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isBase64(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(base64Regex, field.String()) } -func isURI(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isURI(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -365,7 +633,7 @@ func isURI(topStruct reflect.Value, currentStruct reflect.Value, field reflect.V panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isURL(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isURL(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -386,51 +654,51 @@ func isURL(topStruct reflect.Value, currentStruct reflect.Value, field reflect.V panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isEmail(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isEmail(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(emailRegex, field.String()) } -func isHsla(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isHsla(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(hslaRegex, field.String()) } -func isHsl(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isHsl(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(hslRegex, field.String()) } -func isRgba(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isRgba(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(rgbaRegex, field.String()) } -func isRgb(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isRgb(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(rgbRegex, field.String()) } -func isHexcolor(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isHexcolor(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(hexcolorRegex, field.String()) } -func isHexadecimal(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isHexadecimal(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(hexadecimalRegex, field.String()) } -func isNumber(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isNumber(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(numberRegex, field.String()) } -func isNumeric(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isNumeric(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(numericRegex, field.String()) } -func isAlphanum(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isAlphanum(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(alphaNumericRegex, field.String()) } -func isAlpha(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isAlpha(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return matchesRegex(alphaRegex, field.String()) } -func hasValue(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func hasValue(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: @@ -440,129 +708,89 @@ func hasValue(topStruct reflect.Value, currentStruct reflect.Value, field reflec } } -func isGteField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - - if !current.IsValid() { - panic("struct not passed for cross validation") - } - - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() - } - - switch current.Kind() { - - case reflect.Struct: - - if current.Type() == timeType || current.Type() == timePtrType { - break - } - - current = current.FieldByName(param) - - if current.Kind() == reflect.Invalid { - panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) - } - } +func isGteField(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) + if !ok || currentKind != fieldKind { + return false } switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return field.Int() >= current.Int() + return field.Int() >= currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return field.Uint() >= current.Uint() + return field.Uint() >= currentField.Uint() case reflect.Float32, reflect.Float64: - return field.Float() >= current.Float() + return field.Float() >= currentField.Float() case reflect.Struct: - if field.Type() == timeType || field.Type() == timePtrType { + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } - if current.Type() != timeType && current.Type() != timePtrType { - panic("Bad Top Level field type") - } + if fieldType == timeType { - t := current.Interface().(time.Time) + t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.After(t) || fieldTime.Equal(t) } } - panic(fmt.Sprintf("Bad field type %T", field.Interface())) + // default reflect.String + return len(field.String()) >= len(currentField.String()) } -func isGtField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - - if !current.IsValid() { - panic("struct not passed for cross validation") - } +func isGtField(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() - } - - switch current.Kind() { - - case reflect.Struct: - - if current.Type() == timeType || current.Type() == timePtrType { - break - } - - current = current.FieldByName(param) - - if current.Kind() == reflect.Invalid { - panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) - } - } - - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) + if !ok || currentKind != fieldKind { + return false } switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return field.Int() > current.Int() + return field.Int() > currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return field.Uint() > current.Uint() + return field.Uint() > currentField.Uint() case reflect.Float32, reflect.Float64: - return field.Float() > current.Float() + return field.Float() > currentField.Float() case reflect.Struct: - if field.Type() == timeType || field.Type() == timePtrType { + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } - if current.Type() != timeType && current.Type() != timePtrType { - panic("Bad Top Level field type") - } + if fieldType == timeType { - t := current.Interface().(time.Time) + t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.After(t) } } - panic(fmt.Sprintf("Bad field type %T", field.Interface())) + // default reflect.String + return len(field.String()) > len(currentField.String()) } -func isGte(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isGte(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -605,7 +833,7 @@ func isGte(topStruct reflect.Value, currentStruct reflect.Value, field reflect.V panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isGt(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isGt(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -647,7 +875,7 @@ func isGt(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Va // 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(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func hasLengthOf(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -684,134 +912,94 @@ func hasLengthOf(topStruct reflect.Value, currentStruct reflect.Value, field ref // 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(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func hasMinOf(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return isGte(topStruct, currentStruct, field, fieldType, fieldKind, param) + return isGte(v, topStruct, currentStructOrField, field, fieldType, fieldKind, param) } -func isLteField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - - if !current.IsValid() { - panic("struct not passed for cross validation") - } - - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() - } - - switch current.Kind() { - - case reflect.Struct: +func isLteField(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if current.Type() == timeType || current.Type() == timePtrType { - break - } - - current = current.FieldByName(param) - - if current.Kind() == reflect.Invalid { - panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) - } - } - - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) + if !ok || currentKind != fieldKind { + return false } switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return field.Int() <= current.Int() + return field.Int() <= currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return field.Uint() <= current.Uint() + return field.Uint() <= currentField.Uint() case reflect.Float32, reflect.Float64: - return field.Float() <= current.Float() + return field.Float() <= currentField.Float() case reflect.Struct: - if field.Type() == timeType || field.Type() == timePtrType { + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } - if current.Type() != timeType && current.Type() != timePtrType { - panic("Bad Top Level field type") - } + if fieldType == timeType { - t := current.Interface().(time.Time) + t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.Before(t) || fieldTime.Equal(t) } } - panic(fmt.Sprintf("Bad field type %T", field.Interface())) + // default reflect.String + return len(field.String()) <= len(currentField.String()) } -func isLtField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isLtField(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if !current.IsValid() { - panic("struct not passed for cross validation") - } - - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() - } - - switch current.Kind() { - - case reflect.Struct: - - if current.Type() == timeType || current.Type() == timePtrType { - break - } - - current = current.FieldByName(param) - - if current.Kind() == reflect.Invalid { - panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) - } - } - - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) + if !ok || currentKind != fieldKind { + return false } switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return field.Int() < current.Int() + return field.Int() < currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return field.Uint() < current.Uint() + return field.Uint() < currentField.Uint() case reflect.Float32, reflect.Float64: - return field.Float() < current.Float() + return field.Float() < currentField.Float() case reflect.Struct: - if field.Type() == timeType || field.Type() == timePtrType { + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } - if current.Type() != timeType && current.Type() != timePtrType { - panic("Bad Top Level field type") - } + if fieldType == timeType { - t := current.Interface().(time.Time) + t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.Before(t) } } - panic(fmt.Sprintf("Bad field type %T", field.Interface())) + // default reflect.String + return len(field.String()) < len(currentField.String()) } -func isLte(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isLte(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -854,7 +1042,7 @@ func isLte(topStruct reflect.Value, currentStruct reflect.Value, field reflect.V panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isLt(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isLt(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -898,43 +1086,6 @@ func isLt(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Va // 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(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - - return isLte(topStruct, currentStruct, field, fieldType, fieldKind, param) -} - -// asInt retuns the parameter as a int64 -// or panics if it can't convert -func asInt(param string) int64 { - - i, err := strconv.ParseInt(param, 0, 64) - panicIf(err) - - return i -} - -// asUint returns the parameter as a uint64 -// or panics if it can't convert -func asUint(param string) uint64 { - - i, err := strconv.ParseUint(param, 0, 64) - panicIf(err) - - return i -} - -// asFloat returns the parameter as a float64 -// or panics if it can't convert -func asFloat(param string) float64 { - - i, err := strconv.ParseFloat(param, 64) - panicIf(err) - - return i -} - -func panicIf(err error) { - if err != nil { - panic(err.Error()) - } +func hasMaxOf(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return isLte(v, topStruct, currentStructOrField, field, fieldType, fieldKind, param) } diff --git a/benchmarks_test.go b/benchmarks_test.go index af201e6fe..3649bf98e 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -4,6 +4,7 @@ import ( sql "database/sql/driver" "reflect" "testing" + "time" ) func BenchmarkFieldSuccess(b *testing.B) { @@ -18,6 +19,18 @@ func BenchmarkFieldFailure(b *testing.B) { } } +func BenchmarkFieldDiveSuccess(b *testing.B) { + for n := 0; n < b.N; n++ { + validate.Field([]string{"val1", "val2", "val3"}, "required,dive,required") + } +} + +func BenchmarkFieldDiveFailure(b *testing.B) { + for n := 0; n < b.N; n++ { + validate.Field([]string{"val1", "", "val3"}, "required,dive,required") + } +} + func BenchmarkFieldCustomTypeSuccess(b *testing.B) { customTypes := map[reflect.Type]CustomTypeFunc{} @@ -62,34 +75,6 @@ func BenchmarkFieldOrTagFailure(b *testing.B) { } } -func BenchmarkStructSimpleSuccess(b *testing.B) { - - type Foo struct { - StringValue string `validate:"min=5,max=10"` - IntValue int `validate:"min=5,max=10"` - } - - validFoo := &Foo{StringValue: "Foobar", IntValue: 7} - - for n := 0; n < b.N; n++ { - validate.Struct(validFoo) - } -} - -func BenchmarkStructSimpleFailure(b *testing.B) { - - type Foo struct { - StringValue string `validate:"min=5,max=10"` - IntValue int `validate:"min=5,max=10"` - } - - invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} - - for n := 0; n < b.N; n++ { - validate.Struct(invalidFoo) - } -} - func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { customTypes := map[reflect.Type]CustomTypeFunc{} @@ -136,6 +121,193 @@ func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { } } +func BenchmarkStructPartialSuccess(b *testing.B) { + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + for n := 0; n < b.N; n++ { + validate.StructPartial(test, "Name") + } +} + +func BenchmarkStructPartialFailure(b *testing.B) { + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + for n := 0; n < b.N; n++ { + validate.StructPartial(test, "NickName") + } +} + +func BenchmarkStructExceptSuccess(b *testing.B) { + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + for n := 0; n < b.N; n++ { + validate.StructPartial(test, "Nickname") + } +} + +func BenchmarkStructExceptFailure(b *testing.B) { + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + for n := 0; n < b.N; n++ { + validate.StructPartial(test, "Name") + } +} + +func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) { + + type Test struct { + Start time.Time + End time.Time `validate:"gtfield=Start"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + test := &Test{ + Start: now, + End: then, + } + + for n := 0; n < b.N; n++ { + validate.Struct(test) + } +} + +func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) { + + type Test struct { + Start time.Time + End time.Time `validate:"gtfield=Start"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * -5) + + test := &Test{ + Start: now, + End: then, + } + + for n := 0; n < b.N; n++ { + validate.Struct(test) + } +} + +func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) { + + type Inner struct { + Start time.Time + } + + type Outer struct { + Inner *Inner + CreatedAt time.Time `validate:"eqcsfield=Inner.Start"` + } + + now := time.Now().UTC() + + inner := &Inner{ + Start: now, + } + + outer := &Outer{ + Inner: inner, + CreatedAt: now, + } + + for n := 0; n < b.N; n++ { + validate.Struct(outer) + } +} + +func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) { + + type Inner struct { + Start time.Time + } + + type Outer struct { + Inner *Inner + CreatedAt time.Time `validate:"eqcsfield=Inner.Start"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + inner := &Inner{ + Start: then, + } + + outer := &Outer{ + Inner: inner, + CreatedAt: now, + } + + for n := 0; n < b.N; n++ { + validate.Struct(outer) + } +} + +func BenchmarkStructSimpleSuccess(b *testing.B) { + + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + + validFoo := &Foo{StringValue: "Foobar", IntValue: 7} + + for n := 0; n < b.N; n++ { + validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleFailure(b *testing.B) { + + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + + invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} + + for n := 0; n < b.N; n++ { + validate.Struct(invalidFoo) + } +} + func BenchmarkStructSimpleSuccessParallel(b *testing.B) { type Foo struct { diff --git a/doc.go b/doc.go index db057108e..13bb66e78 100644 --- a/doc.go +++ b/doc.go @@ -21,7 +21,7 @@ Custom Functions Custom functions can be added // Structure - func customFunc(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + func customFunc(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { if whatever { return false @@ -36,18 +36,41 @@ Custom functions can be added Cross Field Validation -Cross Field Validation can be implemented, for example Start & End Date range validation +Cross Field Validation can be done via the following tags: eqfield, nefield, gtfield, gtefield, +ltfield, ltefield, eqcsfield, necsfield, gtcsfield, ftecsfield, ltcsfield and ltecsfield. If +however some custom cross field validation is required, it can be done using a custom validation. + +Why not just have cross fields validation tags i.e. only eqcsfield and not eqfield; the reason is +efficiency, if you want to check a field within the same struct eqfield only has to find the field +on the same struct, 1 level; but if we used eqcsfield it could be multiple levels down. + + type Inner struct { + StartDate time.Time + } + + type Outer struct { + InnerStructField *Inner + CreatedAt time.Time `validate:"ltecsfield=InnerStructField.StartDate"` + } + + now := time.Now() + + inner := &Inner{ + StartDate: now, + } + + outer := &Outer{ + InnerStructField: inner, + CreatedAt: now, + } + + errs := validate.Struct(outer) // 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... // when calling validate.Field(field, tag) val will be nil - // - // 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 eqfield, nefield, gtfield, gtefield, ltfield, ltefield and Tags below Multiple Validators @@ -201,6 +224,10 @@ Here is a list of the current built in validators: Validation on Password field using validate.Struct Usage(eqfield=ConfirmPassword) Validating by field validate.FieldWithValue(password, confirmpassword, "eqfield") + eqcsfield + This does the same as eqfield except that it validates the field provided relative + to the top level struct. (Usage: eqcsfield=InnerStructField.Field) + nefield This will validate the field value against another fields value either within a struct or passed in field. @@ -208,6 +235,10 @@ Here is a list of the current built in validators: Validation on Color field using validate.Struct Usage(nefield=Color2) Validating by field validate.FieldWithValue(color1, color2, "nefield") + necsfield + This does the same as nefield except that it validates the field provided relative + to the top level struct. (Usage: necsfield=InnerStructField.Field) + gtfield Only valid for Numbers and time.Time types, this will validate the field value against another fields value either within a struct or passed in field. @@ -215,6 +246,10 @@ Here is a list of the current built in validators: Validation on End field using validate.Struct Usage(gtfield=Start) Validating by field validate.FieldWithValue(start, end, "gtfield") + gtcsfield + This does the same as gtfield except that it validates the field provided relative + to the top level struct. (Usage: gtcsfield=InnerStructField.Field) + gtefield Only valid for Numbers and time.Time types, this will validate the field value against another fields value either within a struct or passed in field. @@ -222,6 +257,10 @@ Here is a list of the current built in validators: Validation on End field using validate.Struct Usage(gtefield=Start) Validating by field validate.FieldWithValue(start, end, "gtefield") + gtecsfield + This does the same as gtefield except that it validates the field provided relative + to the top level struct. (Usage: gtecsfield=InnerStructField.Field) + ltfield Only valid for Numbers and time.Time types, this will validate the field value against another fields value either within a struct or passed in field. @@ -229,6 +268,10 @@ Here is a list of the current built in validators: Validation on End field using validate.Struct Usage(ltfield=Start) Validating by field validate.FieldWithValue(start, end, "ltfield") + ltcsfield + This does the same as ltfield except that it validates the field provided relative + to the top level struct. (Usage: ltcsfield=InnerStructField.Field) + ltefield Only valid for Numbers and time.Time types, this will validate the field value against another fields value either within a struct or passed in field. @@ -236,6 +279,10 @@ Here is a list of the current built in validators: Validation on End field using validate.Struct Usage(ltefield=Start) Validating by field validate.FieldWithValue(start, end, "ltefield") + ltecsfield + This does the same as ltefield except that it validates the field provided relative + to the top level struct. (Usage: ltecsfield=InnerStructField.Field) + alpha This validates that a string value contains alpha characters only (Usage: alpha) diff --git a/examples_test.go b/examples_test.go index 9d01450a8..f2dad9270 100644 --- a/examples_test.go +++ b/examples_test.go @@ -3,7 +3,7 @@ package validator_test import ( "fmt" - "../validator" + "gopkg.in/bluesuncorp/validator.v6" ) func ExampleValidate_new() { diff --git a/util.go b/util.go new file mode 100644 index 000000000..641c2d4d5 --- /dev/null +++ b/util.go @@ -0,0 +1,216 @@ +package validator + +import ( + "reflect" + "strconv" + "strings" +) + +const ( + namespaceSeparator = "." + leftBracket = "[" + rightBracket = "]" +) + +func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Kind) { + + switch current.Kind() { + case reflect.Ptr: + + if current.IsNil() { + return current, reflect.Ptr + } + + return v.extractType(current.Elem()) + + case reflect.Interface: + + if current.IsNil() { + return current, reflect.Interface + } + + return v.extractType(current.Elem()) + + case reflect.Invalid: + return current, reflect.Invalid + + default: + + if v.config.hasCustomFuncs { + if fn, ok := v.config.CustomTypeFuncs[current.Type()]; ok { + return v.extractType(reflect.ValueOf(fn(current))) + } + } + + return current, current.Kind() + } +} + +func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) { + + current, kind := v.extractType(current) + + if kind == reflect.Invalid { + return current, kind, false + } + + if len(namespace) == 0 { + return current, kind, true + } + + switch kind { + + case reflect.Ptr, reflect.Interface: + + return current, kind, false + + case reflect.Struct: + + typ := current.Type() + fld := namespace + ns := namespace + + if typ != timeType && typ != timePtrType { + + idx := strings.Index(namespace, namespaceSeparator) + + if idx != -1 { + fld = namespace[:idx] + ns = namespace[idx+1:] + } else { + ns = "" + idx = len(namespace) + } + + bracketIdx := strings.Index(fld, leftBracket) + if bracketIdx != -1 { + fld = fld[:bracketIdx] + + ns = namespace[bracketIdx:] + } + + current = current.FieldByName(fld) + + return v.getStructFieldOK(current, ns) + } + + case reflect.Array, reflect.Slice: + idx := strings.Index(namespace, leftBracket) + idx2 := strings.Index(namespace, rightBracket) + + arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2]) + + if arrIdx >= current.Len() { + return current, kind, false + } + + startIdx := idx2 + 1 + + if startIdx < len(namespace) { + if namespace[startIdx:startIdx+1] == namespaceSeparator { + startIdx++ + } + } + + return v.getStructFieldOK(current.Index(arrIdx), namespace[startIdx:]) + + case reflect.Map: + idx := strings.Index(namespace, leftBracket) + 1 + idx2 := strings.Index(namespace, rightBracket) + + endIdx := idx2 + + if endIdx+1 < len(namespace) { + if namespace[endIdx+1:endIdx+2] == namespaceSeparator { + endIdx++ + } + } + + key := namespace[idx:idx2] + + switch current.Type().Key().Kind() { + case reflect.Int: + i, _ := strconv.Atoi(key) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:]) + case reflect.Int8: + i, _ := strconv.ParseInt(key, 10, 8) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(int8(i))), namespace[endIdx+1:]) + case reflect.Int16: + i, _ := strconv.ParseInt(key, 10, 16) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(int16(i))), namespace[endIdx+1:]) + case reflect.Int32: + i, _ := strconv.ParseInt(key, 10, 32) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(int32(i))), namespace[endIdx+1:]) + case reflect.Int64: + i, _ := strconv.ParseInt(key, 10, 64) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:]) + case reflect.Uint: + i, _ := strconv.ParseUint(key, 10, 0) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(uint(i))), namespace[endIdx+1:]) + case reflect.Uint8: + i, _ := strconv.ParseUint(key, 10, 8) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(uint8(i))), namespace[endIdx+1:]) + case reflect.Uint16: + i, _ := strconv.ParseUint(key, 10, 16) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(uint16(i))), namespace[endIdx+1:]) + case reflect.Uint32: + i, _ := strconv.ParseUint(key, 10, 32) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(uint32(i))), namespace[endIdx+1:]) + case reflect.Uint64: + i, _ := strconv.ParseUint(key, 10, 64) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:]) + case reflect.Float32: + f, _ := strconv.ParseFloat(key, 32) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(float32(f))), namespace[endIdx+1:]) + case reflect.Float64: + f, _ := strconv.ParseFloat(key, 64) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(f)), namespace[endIdx+1:]) + case reflect.Bool: + b, _ := strconv.ParseBool(key) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(b)), namespace[endIdx+1:]) + + // reflect.Type = string + default: + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:]) + } + } + + // if got here there was more namespace, cannot go any deeper + panic("Invalid field namespace") +} + +// asInt retuns the parameter as a int64 +// or panics if it can't convert +func asInt(param string) int64 { + + i, err := strconv.ParseInt(param, 0, 64) + panicIf(err) + + return i +} + +// asUint returns the parameter as a uint64 +// or panics if it can't convert +func asUint(param string) uint64 { + + i, err := strconv.ParseUint(param, 0, 64) + panicIf(err) + + return i +} + +// asFloat returns the parameter as a float64 +// or panics if it can't convert +func asFloat(param string) float64 { + + i, err := strconv.ParseFloat(param, 64) + panicIf(err) + + return i +} + +func panicIf(err error) { + if err != nil { + panic(err.Error()) + } +} diff --git a/validator.go b/validator.go index 709a1fcec..3542569e5 100644 --- a/validator.go +++ b/validator.go @@ -31,17 +31,18 @@ const ( diveTag = "dive" existsTag = "exists" fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" - arrayIndexFieldName = "%s[%d]" - mapIndexFieldName = "%s[%v]" + arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket + mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket invalidValidation = "Invalid validation tag on field %s" undefinedValidation = "Undefined validation function on field %s" ) var ( - timeType = reflect.TypeOf(time.Time{}) - timePtrType = reflect.TypeOf(&time.Time{}) - errsPool = &sync.Pool{New: newValidationErrors} - tagsCache = &tagCacheMap{m: map[string][]*tagCache{}} + timeType = reflect.TypeOf(time.Time{}) + timePtrType = reflect.TypeOf(&time.Time{}) + errsPool = &sync.Pool{New: newValidationErrors} + tagsCache = &tagCacheMap{m: map[string][]*tagCache{}} + emptyStructPtr = new(struct{}) ) // returns new ValidationErrors to the pool @@ -92,11 +93,12 @@ type Config struct { type CustomTypeFunc func(field reflect.Value) interface{} // Func accepts all values needed for file and cross field validation +// v = validator instance, needed but some built in functions for it's custom types // 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 +type Func func(v *Validate, 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 @@ -180,7 +182,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors { errs := errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) - v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "") + v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "", false, false, nil) if len(errs) == 0 { errsPool.Put(errs) @@ -198,7 +200,92 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string errs := errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) - v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "") + v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "", false, false, nil) + + if len(errs) == 0 { + errsPool.Put(errs) + return nil + } + + return errs +} + +// StructPartial validates the fields passed in only, ignoring all others. +// Fields may be provided in a namespaced fashion relative to the struct provided +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name +// NOTE: This is normally not needed, however in some specific cases such as: tied to a +// legacy data structure, it will be useful +func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { + + sv, _ := v.extractType(reflect.ValueOf(current)) + name := sv.Type().Name() + m := map[string]*struct{}{} + + if fields != nil { + for _, k := range fields { + + flds := strings.Split(k, namespaceSeparator) + if len(flds) > 0 { + + key := name + namespaceSeparator + for _, s := range flds { + + idx := strings.Index(s, leftBracket) + + if idx != -1 { + for idx != -1 { + key += s[:idx] + m[key] = emptyStructPtr + + idx2 := strings.Index(s, rightBracket) + idx2++ + key += s[idx:idx2] + m[key] = emptyStructPtr + s = s[idx2:] + idx = strings.Index(s, leftBracket) + } + } else { + + key += s + m[key] = emptyStructPtr + } + + key += namespaceSeparator + } + } + } + } + + errs := errsPool.Get().(ValidationErrors) + + v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, false, m) + + if len(errs) == 0 { + errsPool.Put(errs) + return nil + } + + return errs +} + +// StructExcept validates all fields except the ones passed in. +// Fields may be provided in a namespaced fashion relative to the struct provided +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name +// NOTE: This is normally not needed, however in some specific cases such as: tied to a +// legacy data structure, it will be useful +func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors { + + sv, _ := v.extractType(reflect.ValueOf(current)) + name := sv.Type().Name() + m := map[string]*struct{}{} + + for _, key := range fields { + m[name+"."+key] = emptyStructPtr + } + + errs := errsPool.Get().(ValidationErrors) + + v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, true, m) if len(errs) == 0 { errsPool.Put(errs) @@ -214,7 +301,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors { errs := errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) - v.tranverseStruct(sv, sv, sv, "", errs, true) + v.tranverseStruct(sv, sv, sv, "", errs, true, false, false, nil) if len(errs) == 0 { errsPool.Put(errs) @@ -225,7 +312,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors { } // 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) { +func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]*struct{}) { if current.Kind() == reflect.Ptr && !current.IsNil() { current = current.Elem() @@ -235,6 +322,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec panic("value passed for validation is not a struct") } + var ok bool typ := current.Type() if useStructName { @@ -252,29 +340,31 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec continue } - v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name) + if partial { + + _, ok = includeExclude[errPrefix+fld.Name] + + if (ok && exclude) || (!ok && !exclude) { + continue + } + } + + v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name, partial, exclude, includeExclude) } } // 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) { +func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) { if tag == skipValidationTag { return } - kind := current.Kind() - - if kind == reflect.Ptr && !current.IsNil() { - current = current.Elem() - kind = current.Kind() - } - - // 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()) || kind == reflect.Invalid { + current, kind := v.extractType(current) + var typ reflect.Type + switch kind { + case reflect.Ptr, reflect.Interface, reflect.Invalid: if strings.Contains(tag, omitempty) { return } @@ -314,67 +404,29 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. if kind == reflect.Invalid { return } - } - - typ := current.Type() - - switch kind { - case reflect.Struct, reflect.Interface: - - if kind == reflect.Interface { - - current = current.Elem() - kind = current.Kind() - - if kind == reflect.Ptr && !current.IsNil() { - current = current.Elem() - kind = current.Kind() - } - - // changed current, so have to get inner type again - typ = current.Type() - - if kind != reflect.Struct { - goto FALLTHROUGH - } - } - if typ != timeType && typ != timePtrType { + case reflect.Struct: + typ = current.Type() - if kind == reflect.Struct { + if typ != timeType { - if v.config.hasCustomFuncs { - if fn, ok := v.config.CustomTypeFuncs[typ]; ok { - v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) - return - } - } - - // required passed validation above so stop here - // if only validating the structs existance. - if strings.Contains(tag, structOnlyTag) { - return - } - - v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false) + // required passed validation above so stop here + // if only validating the structs existance. + if strings.Contains(tag, structOnlyTag) { return } - } - FALLTHROUGH: - fallthrough - default: - if len(tag) == 0 { + + v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false, partial, exclude, includeExclude) return } } - if v.config.hasCustomFuncs { - if fn, ok := v.config.CustomTypeFuncs[typ]; ok { - v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) - return - } + if len(tag) == 0 { + return } + typ = current.Type() + tags, isCached := tagsCache.Get(tag) if !isCached { @@ -431,7 +483,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. if cTag.tagVals[0][0] == omitempty { - if !hasValue(topStruct, currentStruct, current, typ, kind, "") { + if !hasValue(v, topStruct, currentStruct, current, typ, kind, "") { return } continue @@ -447,9 +499,9 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. // or panic ;) switch kind { case reflect.Slice, reflect.Array: - v.traverseSlice(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name) + v.traverseSlice(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name, partial, exclude, includeExclude) case reflect.Map: - v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name) + v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name, partial, exclude, includeExclude) default: // throw error, if not a slice or map then should not have gotten here // bad dive tag @@ -459,18 +511,18 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. } // 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) { +func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) { for i := 0; i < current.Len(); i++ { - v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i)) + v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i), partial, exclude, includeExclude) } } // 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) { +func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) { for _, key := range current.MapKeys() { - v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface())) + v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface()), partial, exclude, includeExclude) } } @@ -491,7 +543,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } - if valFunc(topStruct, currentStruct, current, currentType, currentKind, val[1]) { + if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) { return false } @@ -514,7 +566,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } - if valFunc(topStruct, currentStruct, current, currentType, currentKind, cTag.tagVals[0][1]) { + if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, cTag.tagVals[0][1]) { return false } diff --git a/validator_test.go b/validator_test.go index c17167a54..22084b467 100644 --- a/validator_test.go +++ b/validator_test.go @@ -192,6 +192,992 @@ func ValidateValuerType(field reflect.Value) interface{} { return nil } +type TestPartial struct { + NoTag string + BlankTag string `validate:""` + Required string `validate:"required"` + SubSlice []*SubTest `validate:"required,dive"` + Sub *SubTest + SubIgnore *SubTest `validate:"-"` + Anonymous struct { + A string `validate:"required"` + ASubSlice []*SubTest `validate:"required,dive"` + + SubAnonStruct []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + } `validate:"required,dive"` + } +} + +func TestStructPartial(t *testing.T) { + + p1 := []string{ + "NoTag", + "Required", + } + + p2 := []string{ + "SubSlice[0].Test", + "Sub", + "SubIgnore", + "Anonymous.A", + } + + p3 := []string{ + "SubTest.Test", + } + + p4 := []string{ + "A", + } + + tPartial := &TestPartial{ + NoTag: "NoTag", + Required: "Required", + + SubSlice: []*SubTest{ + { + + Test: "Required", + }, + { + + Test: "Required", + }, + }, + + Sub: &SubTest{ + Test: "1", + }, + SubIgnore: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + ASubSlice []*SubTest `validate:"required,dive"` + SubAnonStruct []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + } `validate:"required,dive"` + }{ + A: "1", + ASubSlice: []*SubTest{ + { + Test: "Required", + }, + { + Test: "Required", + }, + }, + + SubAnonStruct: []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + }{ + {"Required", "RequiredOther"}, + {"Required", "RequiredOther"}, + }, + }, + } + + // the following should all return no errors as everything is valid in + // the default state + errs := validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + // this isnt really a robust test, but is ment to illustrate the ANON CASE below + errs = validate.StructPartial(tPartial.SubSlice[0], p3...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p2...) + Equal(t, errs, nil) + + // mod tParial for required feild and re-test making sure invalid fields are NOT required: + tPartial.Required = "" + + errs = validate.StructExcept(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + // inversion and retesting Partial to generate failures: + errs = validate.StructPartial(tPartial, p1...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Required", "Required", "required") + + errs = validate.StructExcept(tPartial, p2...) + AssertError(t, errs, "TestPartial.Required", "Required", "required") + + // reset Required field, and set nested struct + tPartial.Required = "Required" + tPartial.Anonymous.A = "" + + // will pass as unset feilds is not going to be tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p2...) + Equal(t, errs, nil) + + // ANON CASE the response here is strange, it clearly does what it is being told to + errs = validate.StructExcept(tPartial.Anonymous, p4...) + Equal(t, errs, nil) + + // will fail as unset feild is tested + errs = validate.StructPartial(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.A", "A", "required") + + errs = validate.StructExcept(tPartial, p1...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.A", "A", "required") + + // reset nested struct and unset struct in slice + tPartial.Anonymous.A = "Required" + tPartial.SubSlice[0].Test = "" + + // these will pass as unset item is NOT tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p2...) + Equal(t, errs, nil) + + // these will fail as unset item IS tested + errs = validate.StructExcept(tPartial, p1...) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + Equal(t, len(errs), 1) + + errs = validate.StructPartial(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + Equal(t, len(errs), 1) + + // Unset second slice member concurrently to test dive behavior: + tPartial.SubSlice[1].Test = "" + + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + // NOTE: When specifying nested items, it is still the users responsibility + // to specify the dive tag, the library does not override this. + errs = validate.StructExcept(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + + errs = validate.StructExcept(tPartial, p1...) + Equal(t, len(errs), 2) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + + errs = validate.StructPartial(tPartial, p2...) + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + + // reset struct in slice, and unset struct in slice in unset posistion + tPartial.SubSlice[0].Test = "Required" + + // these will pass as the unset item is NOT tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + // testing for missing item by exception, yes it dives and fails + errs = validate.StructExcept(tPartial, p1...) + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + + errs = validate.StructExcept(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + + tPartial.SubSlice[1].Test = "Required" + + tPartial.Anonymous.SubAnonStruct[0].Test = "" + // these will pass as the unset item is NOT tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p1...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required") + + errs = validate.StructExcept(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required") + +} + +func TestCrossStructLteFieldValidation(t *testing.T) { + + type Inner struct { + CreatedAt *time.Time + String string + Int int + Uint uint + Float float64 + Array []string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"ltecsfield=Inner.CreatedAt"` + String string `validate:"ltecsfield=Inner.String"` + Int int `validate:"ltecsfield=Inner.Int"` + Uint uint `validate:"ltecsfield=Inner.Uint"` + Float float64 `validate:"ltecsfield=Inner.Float"` + Array []string `validate:"ltecsfield=Inner.Array"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + inner := &Inner{ + CreatedAt: &then, + String: "abcd", + Int: 13, + Uint: 13, + Float: 1.13, + Array: []string{"val1", "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + String: "abc", + Int: 12, + Uint: 12, + Float: 1.12, + Array: []string{"val1"}, + } + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + test.String = "abcd" + test.Int = 13 + test.Uint = 13 + test.Float = 1.13 + test.Array = []string{"val1", "val2"} + + errs = validate.Struct(test) + Equal(t, errs, nil) + + after := now.Add(time.Hour * 10) + + test.CreatedAt = &after + test.String = "abce" + test.Int = 14 + test.Uint = 14 + test.Float = 1.14 + test.Array = []string{"val1", "val2", "val3"} + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "CreatedAt", "ltecsfield") + AssertError(t, errs, "Test.String", "String", "ltecsfield") + AssertError(t, errs, "Test.Int", "Int", "ltecsfield") + AssertError(t, errs, "Test.Uint", "Uint", "ltecsfield") + AssertError(t, errs, "Test.Float", "Float", "ltecsfield") + AssertError(t, errs, "Test.Array", "Array", "ltecsfield") + + errs = validate.FieldWithValue(1, "", "ltecsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltecsfield") + + errs = validate.FieldWithValue(test, now, "ltecsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltecsfield") +} + +func TestCrossStructLtFieldValidation(t *testing.T) { + + type Inner struct { + CreatedAt *time.Time + String string + Int int + Uint uint + Float float64 + Array []string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"ltcsfield=Inner.CreatedAt"` + String string `validate:"ltcsfield=Inner.String"` + Int int `validate:"ltcsfield=Inner.Int"` + Uint uint `validate:"ltcsfield=Inner.Uint"` + Float float64 `validate:"ltcsfield=Inner.Float"` + Array []string `validate:"ltcsfield=Inner.Array"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + inner := &Inner{ + CreatedAt: &then, + String: "abcd", + Int: 13, + Uint: 13, + Float: 1.13, + Array: []string{"val1", "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + String: "abc", + Int: 12, + Uint: 12, + Float: 1.12, + Array: []string{"val1"}, + } + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + test.String = "abcd" + test.Int = 13 + test.Uint = 13 + test.Float = 1.13 + test.Array = []string{"val1", "val2"} + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "CreatedAt", "ltcsfield") + AssertError(t, errs, "Test.String", "String", "ltcsfield") + AssertError(t, errs, "Test.Int", "Int", "ltcsfield") + AssertError(t, errs, "Test.Uint", "Uint", "ltcsfield") + AssertError(t, errs, "Test.Float", "Float", "ltcsfield") + AssertError(t, errs, "Test.Array", "Array", "ltcsfield") + + errs = validate.FieldWithValue(1, "", "ltcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltcsfield") + + errs = validate.FieldWithValue(test, now, "ltcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltcsfield") +} + +func TestCrossStructGteFieldValidation(t *testing.T) { + + type Inner struct { + CreatedAt *time.Time + String string + Int int + Uint uint + Float float64 + Array []string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"gtecsfield=Inner.CreatedAt"` + String string `validate:"gtecsfield=Inner.String"` + Int int `validate:"gtecsfield=Inner.Int"` + Uint uint `validate:"gtecsfield=Inner.Uint"` + Float float64 `validate:"gtecsfield=Inner.Float"` + Array []string `validate:"gtecsfield=Inner.Array"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * -5) + + inner := &Inner{ + CreatedAt: &then, + String: "abcd", + Int: 13, + Uint: 13, + Float: 1.13, + Array: []string{"val1", "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + String: "abcde", + Int: 14, + Uint: 14, + Float: 1.14, + Array: []string{"val1", "val2", "val3"}, + } + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + test.String = "abcd" + test.Int = 13 + test.Uint = 13 + test.Float = 1.13 + test.Array = []string{"val1", "val2"} + + errs = validate.Struct(test) + Equal(t, errs, nil) + + before := now.Add(time.Hour * -10) + + test.CreatedAt = &before + test.String = "abc" + test.Int = 12 + test.Uint = 12 + test.Float = 1.12 + test.Array = []string{"val1"} + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "CreatedAt", "gtecsfield") + AssertError(t, errs, "Test.String", "String", "gtecsfield") + AssertError(t, errs, "Test.Int", "Int", "gtecsfield") + AssertError(t, errs, "Test.Uint", "Uint", "gtecsfield") + AssertError(t, errs, "Test.Float", "Float", "gtecsfield") + AssertError(t, errs, "Test.Array", "Array", "gtecsfield") + + errs = validate.FieldWithValue(1, "", "gtecsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtecsfield") + + errs = validate.FieldWithValue(test, now, "gtecsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtecsfield") +} + +func TestCrossStructGtFieldValidation(t *testing.T) { + + type Inner struct { + CreatedAt *time.Time + String string + Int int + Uint uint + Float float64 + Array []string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"gtcsfield=Inner.CreatedAt"` + String string `validate:"gtcsfield=Inner.String"` + Int int `validate:"gtcsfield=Inner.Int"` + Uint uint `validate:"gtcsfield=Inner.Uint"` + Float float64 `validate:"gtcsfield=Inner.Float"` + Array []string `validate:"gtcsfield=Inner.Array"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * -5) + + inner := &Inner{ + CreatedAt: &then, + String: "abcd", + Int: 13, + Uint: 13, + Float: 1.13, + Array: []string{"val1", "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + String: "abcde", + Int: 14, + Uint: 14, + Float: 1.14, + Array: []string{"val1", "val2", "val3"}, + } + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + test.String = "abcd" + test.Int = 13 + test.Uint = 13 + test.Float = 1.13 + test.Array = []string{"val1", "val2"} + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "CreatedAt", "gtcsfield") + AssertError(t, errs, "Test.String", "String", "gtcsfield") + AssertError(t, errs, "Test.Int", "Int", "gtcsfield") + AssertError(t, errs, "Test.Uint", "Uint", "gtcsfield") + AssertError(t, errs, "Test.Float", "Float", "gtcsfield") + AssertError(t, errs, "Test.Array", "Array", "gtcsfield") + + errs = validate.FieldWithValue(1, "", "gtcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtcsfield") + + errs = validate.FieldWithValue(test, now, "gtcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtcsfield") +} + +func TestCrossStructNeFieldValidation(t *testing.T) { + + type Inner struct { + CreatedAt *time.Time + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"necsfield=Inner.CreatedAt"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + inner := &Inner{ + CreatedAt: &then, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + } + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "CreatedAt", "necsfield") + + var j uint64 + var k float64 + var j2 uint64 + var k2 float64 + s := "abcd" + i := 1 + j = 1 + k = 1.543 + arr := []string{"test"} + + s2 := "abcd" + i2 := 1 + j2 = 1 + k2 = 1.543 + arr2 := []string{"test"} + arr3 := []string{"test", "test2"} + now2 := now + + errs = validate.FieldWithValue(s, s2, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "necsfield") + + errs = validate.FieldWithValue(i2, i, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "necsfield") + + errs = validate.FieldWithValue(j2, j, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "necsfield") + + errs = validate.FieldWithValue(k2, k, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "necsfield") + + errs = validate.FieldWithValue(arr2, arr, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "necsfield") + + errs = validate.FieldWithValue(now2, now, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "necsfield") + + errs = validate.FieldWithValue(arr3, arr, "necsfield") + Equal(t, errs, nil) + + type SInner struct { + Name string + } + + type TStruct struct { + Inner *SInner + CreatedAt *time.Time `validate:"necsfield=Inner"` + } + + sinner := &SInner{ + Name: "NAME", + } + + test2 := &TStruct{ + Inner: sinner, + CreatedAt: &now, + } + + errs = validate.Struct(test2) + Equal(t, errs, nil) + + test2.Inner = nil + errs = validate.Struct(test2) + Equal(t, errs, nil) + + errs = validate.FieldWithValue(nil, 1, "necsfield") + Equal(t, errs, nil) +} + +func TestCrossStructEqFieldValidation(t *testing.T) { + + type Inner struct { + CreatedAt *time.Time + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"eqcsfield=Inner.CreatedAt"` + } + + now := time.Now().UTC() + + inner := &Inner{ + CreatedAt: &now, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + } + + errs := validate.Struct(test) + Equal(t, errs, nil) + + newTime := time.Now().UTC() + test.CreatedAt = &newTime + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "CreatedAt", "eqcsfield") + + var j uint64 + var k float64 + s := "abcd" + i := 1 + j = 1 + k = 1.543 + arr := []string{"test"} + + var j2 uint64 + var k2 float64 + s2 := "abcd" + i2 := 1 + j2 = 1 + k2 = 1.543 + arr2 := []string{"test"} + arr3 := []string{"test", "test2"} + now2 := now + + errs = validate.FieldWithValue(s, s2, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.FieldWithValue(i2, i, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.FieldWithValue(j2, j, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.FieldWithValue(k2, k, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.FieldWithValue(arr2, arr, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.FieldWithValue(now2, now, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.FieldWithValue(arr3, arr, "eqcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "eqcsfield") + + type SInner struct { + Name string + } + + type TStruct struct { + Inner *SInner + CreatedAt *time.Time `validate:"eqcsfield=Inner"` + } + + sinner := &SInner{ + Name: "NAME", + } + + test2 := &TStruct{ + Inner: sinner, + CreatedAt: &now, + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TStruct.CreatedAt", "CreatedAt", "eqcsfield") + + test2.Inner = nil + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TStruct.CreatedAt", "CreatedAt", "eqcsfield") + + errs = validate.FieldWithValue(nil, 1, "eqcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "eqcsfield") +} + +func TestCrossNamespaceFieldValidation(t *testing.T) { + + type SliceStruct struct { + Name string + } + + type MapStruct struct { + Name string + } + + type Inner struct { + CreatedAt *time.Time + Slice []string + SliceStructs []*SliceStruct + SliceSlice [][]string + SliceSliceStruct [][]*SliceStruct + SliceMap []map[string]string + Map map[string]string + MapMap map[string]map[string]string + MapStructs map[string]*SliceStruct + MapMapStruct map[string]map[string]*SliceStruct + MapSlice map[string][]string + MapInt map[int]string + MapInt8 map[int8]string + MapInt16 map[int16]string + MapInt32 map[int32]string + MapInt64 map[int64]string + MapUint map[uint]string + MapUint8 map[uint8]string + MapUint16 map[uint16]string + MapUint32 map[uint32]string + MapUint64 map[uint64]string + MapFloat32 map[float32]string + MapFloat64 map[float64]string + MapBool map[bool]string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time + } + + now := time.Now() + + inner := &Inner{ + CreatedAt: &now, + Slice: []string{"val1", "val2", "val3"}, + SliceStructs: []*SliceStruct{{Name: "name1"}, {Name: "name2"}, {Name: "name3"}}, + SliceSlice: [][]string{{"1", "2", "3"}, {"4", "5", "6"}, {"7", "8", "9"}}, + SliceSliceStruct: [][]*SliceStruct{{{Name: "name1"}, {Name: "name2"}, {Name: "name3"}}, {{Name: "name4"}, {Name: "name5"}, {Name: "name6"}}, {{Name: "name7"}, {Name: "name8"}, {Name: "name9"}}}, + SliceMap: []map[string]string{{"key1": "val1", "key2": "val2", "key3": "val3"}, {"key4": "val4", "key5": "val5", "key6": "val6"}}, + Map: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, + MapStructs: map[string]*SliceStruct{"key1": {Name: "name1"}, "key2": {Name: "name2"}, "key3": {Name: "name3"}}, + MapMap: map[string]map[string]string{"key1": {"key1-1": "val1"}, "key2": {"key2-1": "val2"}, "key3": {"key3-1": "val3"}}, + MapMapStruct: map[string]map[string]*SliceStruct{"key1": {"key1-1": {Name: "name1"}}, "key2": {"key2-1": {Name: "name2"}}, "key3": {"key3-1": {Name: "name3"}}}, + MapSlice: map[string][]string{"key1": {"1", "2", "3"}, "key2": {"4", "5", "6"}, "key3": {"7", "8", "9"}}, + MapInt: map[int]string{1: "val1", 2: "val2", 3: "val3"}, + MapInt8: map[int8]string{1: "val1", 2: "val2", 3: "val3"}, + MapInt16: map[int16]string{1: "val1", 2: "val2", 3: "val3"}, + MapInt32: map[int32]string{1: "val1", 2: "val2", 3: "val3"}, + MapInt64: map[int64]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint: map[uint]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint8: map[uint8]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint16: map[uint16]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint32: map[uint32]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint64: map[uint64]string{1: "val1", 2: "val2", 3: "val3"}, + MapFloat32: map[float32]string{1.01: "val1", 2.02: "val2", 3.03: "val3"}, + MapFloat64: map[float64]string{1.01: "val1", 2.02: "val2", 3.03: "val3"}, + MapBool: map[bool]string{true: "val1", false: "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + } + + val := reflect.ValueOf(test) + + current, kind, ok := validate.getStructFieldOK(val, "Inner.CreatedAt") + Equal(t, ok, true) + Equal(t, kind, reflect.Struct) + tm, ok := current.Interface().(time.Time) + Equal(t, ok, true) + Equal(t, tm, now) + + current, kind, ok = validate.getStructFieldOK(val, "Inner.Slice[1]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.CrazyNonExistantField") + Equal(t, ok, false) + + current, kind, ok = validate.getStructFieldOK(val, "Inner.Slice[101]") + Equal(t, ok, false) + + current, kind, ok = validate.getStructFieldOK(val, "Inner.Map[key3]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val3") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapMap[key2][key2-1]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapStructs[key2].Name") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "name2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapMapStruct[key3][key3-1].Name") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "name3") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.SliceSlice[2][0]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "7") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.SliceSliceStruct[2][1].Name") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "name8") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.SliceMap[1][key5]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val5") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapSlice[key3][2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "9") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapInt[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapInt8[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapInt16[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapInt32[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapInt64[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapUint[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapUint8[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapUint16[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapUint32[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapUint64[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapFloat32[3.03]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val3") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapFloat64[2.02]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapBool[true]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val1") + + inner = &Inner{ + CreatedAt: &now, + Slice: []string{"val1", "val2", "val3"}, + SliceStructs: []*SliceStruct{{Name: "name1"}, {Name: "name2"}, nil}, + SliceSlice: [][]string{{"1", "2", "3"}, {"4", "5", "6"}, {"7", "8", "9"}}, + SliceSliceStruct: [][]*SliceStruct{{{Name: "name1"}, {Name: "name2"}, {Name: "name3"}}, {{Name: "name4"}, {Name: "name5"}, {Name: "name6"}}, {{Name: "name7"}, {Name: "name8"}, {Name: "name9"}}}, + SliceMap: []map[string]string{{"key1": "val1", "key2": "val2", "key3": "val3"}, {"key4": "val4", "key5": "val5", "key6": "val6"}}, + Map: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, + MapStructs: map[string]*SliceStruct{"key1": {Name: "name1"}, "key2": {Name: "name2"}, "key3": {Name: "name3"}}, + MapMap: map[string]map[string]string{"key1": {"key1-1": "val1"}, "key2": {"key2-1": "val2"}, "key3": {"key3-1": "val3"}}, + MapMapStruct: map[string]map[string]*SliceStruct{"key1": {"key1-1": {Name: "name1"}}, "key2": {"key2-1": {Name: "name2"}}, "key3": {"key3-1": {Name: "name3"}}}, + MapSlice: map[string][]string{"key1": {"1", "2", "3"}, "key2": {"4", "5", "6"}, "key3": {"7", "8", "9"}}, + } + + test = &Test{ + Inner: inner, + CreatedAt: nil, + } + + val = reflect.ValueOf(test) + + current, kind, ok = validate.getStructFieldOK(val, "Inner.SliceStructs[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.Ptr) + Equal(t, current.String(), "<*validator.SliceStruct Value>") + Equal(t, current.IsNil(), true) + + current, kind, ok = validate.getStructFieldOK(val, "Inner.SliceStructs[2].Name") + Equal(t, ok, false) + Equal(t, kind, reflect.Ptr) + Equal(t, current.String(), "<*validator.SliceStruct Value>") + Equal(t, current.IsNil(), true) + + PanicMatches(t, func() { validate.getStructFieldOK(reflect.ValueOf(1), "crazyinput") }, "Invalid field namespace") +} + func TestExistsValidation(t *testing.T) { jsonText := "{ \"truthiness2\": true }" @@ -1925,11 +2911,11 @@ func TestIsNeFieldValidation(t *testing.T) { errs = validate.Struct(sv) Equal(t, errs, nil) - channel := make(chan string) + errs = validate.FieldWithValue(nil, 1, "nefield") + Equal(t, errs, nil) - 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") + errs = validate.FieldWithValue(sv, now, "nefield") + Equal(t, errs, nil) type Test2 struct { Start *time.Time `validate:"nefield=NonExistantField"` @@ -1941,7 +2927,8 @@ func TestIsNeFieldValidation(t *testing.T) { End: &now, } - PanicMatches(t, func() { validate.Struct(sv2) }, "Field \"NonExistantField\" not found in struct") + errs = validate.Struct(sv2) + Equal(t, errs, nil) } func TestIsNeValidation(t *testing.T) { @@ -2044,11 +3031,18 @@ func TestIsEqFieldValidation(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "Test.Start", "Start", "eqfield") + errs = validate.FieldWithValue(nil, 1, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "eqfield") + channel := make(chan string) + errs = validate.FieldWithValue(5, channel, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "eqfield") - 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") + errs = validate.FieldWithValue(5, now, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "eqfield") type Test2 struct { Start *time.Time `validate:"eqfield=NonExistantField"` @@ -2060,7 +3054,31 @@ func TestIsEqFieldValidation(t *testing.T) { End: &now, } - PanicMatches(t, func() { validate.Struct(sv2) }, "Field \"NonExistantField\" not found in struct") + errs = validate.Struct(sv2) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test2.Start", "Start", "eqfield") + + type Inner struct { + Name string + } + + type TStruct struct { + Inner *Inner + CreatedAt *time.Time `validate:"eqfield=Inner"` + } + + inner := &Inner{ + Name: "NAME", + } + + test := &TStruct{ + Inner: inner, + CreatedAt: &now, + } + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "TStruct.CreatedAt", "CreatedAt", "eqfield") } func TestIsEqValidation(t *testing.T) { @@ -2182,6 +3200,12 @@ func TestGtField(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "gtfield") + errs = validate.FieldWithValue(&timeTest, &end, "gtfield") + NotEqual(t, errs, nil) + + errs = validate.FieldWithValue("test", "test bigger", "gtfield") + Equal(t, errs, nil) + type IntTest struct { Val1 int `validate:"required"` Val2 int `validate:"required,gtfield=Val1"` @@ -2269,9 +3293,17 @@ func TestGtField(t *testing.T) { 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") - PanicMatches(t, func() { validate.FieldWithValue(5, start, "gtfield") }, "Bad Top Level field type") + errs = validate.FieldWithValue(nil, 1, "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtfield") + + errs = validate.FieldWithValue(5, "T", "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtfield") + + errs = validate.FieldWithValue(5, start, "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtfield") type TimeTest2 struct { Start *time.Time `validate:"required"` @@ -2283,7 +3315,9 @@ func TestGtField(t *testing.T) { End: &end, } - PanicMatches(t, func() { validate.Struct(timeTest2) }, "Field \"NonExistantField\" not found in struct") + errs = validate.Struct(timeTest2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest2.End", "End", "gtfield") } func TestLtField(t *testing.T) { @@ -2321,6 +3355,13 @@ func TestLtField(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "ltfield") + errs = validate.FieldWithValue(timeTest, &end, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltfield") + + errs = validate.FieldWithValue("test", "tes", "ltfield") + Equal(t, errs, nil) + type IntTest struct { Val1 int `validate:"required"` Val2 int `validate:"required,ltfield=Val1"` @@ -2408,9 +3449,17 @@ func TestLtField(t *testing.T) { 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") - PanicMatches(t, func() { validate.FieldWithValue(1, end, "ltfield") }, "Bad Top Level field type") + errs = validate.FieldWithValue(nil, 5, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltfield") + + errs = validate.FieldWithValue(1, "T", "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltfield") + + errs = validate.FieldWithValue(1, end, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltfield") type TimeTest2 struct { Start *time.Time `validate:"required"` @@ -2422,7 +3471,9 @@ func TestLtField(t *testing.T) { End: &start, } - PanicMatches(t, func() { validate.Struct(timeTest2) }, "Field \"NonExistantField\" not found in struct") + errs = validate.Struct(timeTest2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest2.End", "End", "ltfield") } func TestLteField(t *testing.T) { @@ -2460,6 +3511,16 @@ func TestLteField(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "ltefield") + errs = validate.FieldWithValue(timeTest, &end, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltefield") + + errs = validate.FieldWithValue("test", "tes", "ltefield") + Equal(t, errs, nil) + + errs = validate.FieldWithValue("test", "test", "ltefield") + Equal(t, errs, nil) + type IntTest struct { Val1 int `validate:"required"` Val2 int `validate:"required,ltefield=Val1"` @@ -2547,9 +3608,17 @@ func TestLteField(t *testing.T) { 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") - PanicMatches(t, func() { validate.FieldWithValue(1, end, "ltefield") }, "Bad Top Level field type") + errs = validate.FieldWithValue(nil, 5, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltefield") + + errs = validate.FieldWithValue(1, "T", "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltefield") + + errs = validate.FieldWithValue(1, end, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltefield") type TimeTest2 struct { Start *time.Time `validate:"required"` @@ -2561,7 +3630,9 @@ func TestLteField(t *testing.T) { End: &start, } - PanicMatches(t, func() { validate.Struct(timeTest2) }, "Field \"NonExistantField\" not found in struct") + errs = validate.Struct(timeTest2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest2.End", "End", "ltefield") } func TestGteField(t *testing.T) { @@ -2599,6 +3670,16 @@ func TestGteField(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "gtefield") + errs = validate.FieldWithValue(timeTest, &start, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtefield") + + errs = validate.FieldWithValue("test", "test", "gtefield") + Equal(t, errs, nil) + + errs = validate.FieldWithValue("test", "test bigger", "gtefield") + Equal(t, errs, nil) + type IntTest struct { Val1 int `validate:"required"` Val2 int `validate:"required,gtefield=Val1"` @@ -2686,9 +3767,17 @@ func TestGteField(t *testing.T) { 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") - PanicMatches(t, func() { validate.FieldWithValue(5, start, "gtefield") }, "Bad Top Level field type") + errs = validate.FieldWithValue(nil, 1, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtefield") + + errs = validate.FieldWithValue(5, "T", "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtefield") + + errs = validate.FieldWithValue(5, start, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtefield") type TimeTest2 struct { Start *time.Time `validate:"required"` @@ -2700,7 +3789,9 @@ func TestGteField(t *testing.T) { End: &end, } - PanicMatches(t, func() { validate.Struct(timeTest2) }, "Field \"NonExistantField\" not found in struct") + errs = validate.Struct(timeTest2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest2.End", "End", "gtefield") } func TestValidateByTagAndValue(t *testing.T) { @@ -2710,7 +3801,7 @@ func TestValidateByTagAndValue(t *testing.T) { errs := validate.FieldWithValue(val, field, "required") Equal(t, errs, nil) - fn := func(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + fn := func(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return current.String() == field.String() } @@ -2729,7 +3820,7 @@ func TestValidateByTagAndValue(t *testing.T) { func TestAddFunctions(t *testing.T) { - fn := func(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + fn := func(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return true }