From 3ab458c80cc5ee2c73a756b1d2b6bd51cda59c6a Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Thu, 13 Aug 2015 23:47:19 -0400 Subject: [PATCH 01/26] Updates Split out Type checking. Add Cross Field determination function for future automatic cross struct namespace field validation. --- baked_in.go | 126 ++++++++++++++-------------- util.go | 124 ++++++++++++++++++++++++++++ validator.go | 206 ++++++++++++++++++++++++++++++++-------------- validator_test.go | 57 ++++++++++++- 4 files changed, 384 insertions(+), 129 deletions(-) create mode 100644 util.go diff --git a/baked_in.go b/baked_in.go index b323863..8e5f648 100644 --- a/baked_in.go +++ b/baked_in.go @@ -71,32 +71,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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { if field.Len() != 11 { return false @@ -105,15 +105,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, currentStruct 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, currentStruct 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { uri := strings.SplitN(field.String(), ",", 2) @@ -127,10 +127,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, currentStruct, 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { if field.Len() == 0 { return true @@ -139,35 +139,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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return isISBN10(v, topStruct, currentStruct, field, fieldType, fieldKind, param) || isISBN13(v, topStruct, currentStruct, 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, currentStruct 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 +191,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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { s := strings.Replace(strings.Replace(field.String(), "-", "", 3), " ", "", 3) @@ -219,41 +219,41 @@ 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !containsRune(v, topStruct, currentStruct, 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !containsAny(v, topStruct, currentStruct, 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !contains(v, topStruct, currentStruct, 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { r, _ := utf8.DecodeRuneInString(param) return strings.ContainsRune(field.String(), r) } -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, currentStruct 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, currentStruct 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !isEqField(v, topStruct, currentStruct, field, fieldType, fieldKind, param) } -func isNe(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return !isEq(topStruct, currentStruct, field, fieldType, fieldKind, param) +func isNe(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + return !isEq(v, topStruct, currentStruct, 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 isEqField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { // if current == nil { if !current.IsValid() { @@ -317,7 +317,7 @@ func isEqField(topStruct reflect.Value, current reflect.Value, field reflect.Val panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -348,11 +348,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, currentStruct 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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -365,7 +365,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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -386,51 +386,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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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, currentStruct 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,7 +440,7 @@ 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 { +func isGteField(v *Validate, 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") @@ -501,7 +501,7 @@ func isGteField(topStruct reflect.Value, current reflect.Value, field reflect.Va panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isGtField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isGtField(v *Validate, 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") @@ -562,7 +562,7 @@ func isGtField(topStruct reflect.Value, current reflect.Value, field reflect.Val panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -605,7 +605,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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -647,7 +647,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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -684,12 +684,12 @@ 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, currentStruct 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, currentStruct, field, fieldType, fieldKind, param) } -func isLteField(topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isLteField(v *Validate, 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") @@ -750,7 +750,7 @@ func isLteField(topStruct reflect.Value, current reflect.Value, field reflect.Va panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -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, 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") @@ -811,7 +811,7 @@ func isLtField(topStruct reflect.Value, current reflect.Value, field reflect.Val panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -854,7 +854,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, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { switch fieldKind { @@ -898,9 +898,9 @@ 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 { +func hasMaxOf(v *Validate, 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) + return isLte(v, topStruct, currentStruct, field, fieldType, fieldKind, param) } // asInt retuns the parameter as a int64 diff --git a/util.go b/util.go new file mode 100644 index 0000000..7d55550 --- /dev/null +++ b/util.go @@ -0,0 +1,124 @@ +package validator + +import ( + "reflect" + "strconv" + "strings" +) + +func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect.Kind) { + + switch current.Kind() { + case reflect.Ptr: + + if current.IsNil() { + return current, reflect.Ptr + } + + return v.determineType(current.Elem()) + + case reflect.Interface: + + if current.IsNil() { + return current, reflect.Interface + } + + return v.determineType(current.Elem()) + + case reflect.Invalid: + + return current, reflect.Invalid + + default: + + // fmt.Println(current.Kind()) + if v.config.hasCustomFuncs { + if fn, ok := v.config.CustomTypeFuncs[current.Type()]; ok { + return v.determineType(reflect.ValueOf(fn(current))) + } + } + + return current, current.Kind() + } +} + +func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) { + + // fmt.Println("NS:", namespace) + + current, kind := v.determineType(current) + + // fmt.Println("getStructFieldOK - ", current, kind) + + switch kind { + case reflect.Ptr, reflect.Interface, reflect.Invalid: + return current, kind, false + + case reflect.Struct: + + typ := current.Type() + fld := namespace + + if typ != timeType && typ != timePtrType { + + idx := strings.Index(namespace, namespaceSeparator) + + // fmt.Println("IDX:", namespace, idx) + if idx != -1 { + fld = namespace[:idx] + } + + ns := namespace[idx+1:] + + bracketIdx := strings.Index(fld, "[") + if bracketIdx != -1 { + fld = fld[:bracketIdx] + // fmt.Println("NSS:", ns) + + if idx == -1 { + ns = namespace[bracketIdx:] + } else { + ns = namespace[idx+bracketIdx:] + } + } + + // fmt.Println("Looking for field:", fld) + current = current.FieldByName(fld) + + // fmt.Println("Current:", current) + + return v.getStructFieldOK(current, ns) + } + + case reflect.Array, reflect.Slice: + idx := strings.Index(namespace, "[") + idx2 := strings.Index(namespace, "]") + + arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2]) + + // fmt.Println("ArrayIndex:", arrIdx) + // fmt.Println("LEN:", current.Len()) + if arrIdx >= current.Len() { + return current, kind, false + } + + return v.getStructFieldOK(current.Index(arrIdx), namespace[idx2+1:]) + + case reflect.Map: + idx := strings.Index(namespace, "[") + idx2 := strings.Index(namespace, "]") + + // key, _ := strconv.Atoi(namespace[idx+1 : idx2]) + + // fmt.Println("ArrayIndex:", arrIdx) + // fmt.Println("LEN:", current.Len()) + // if arrIdx >= current.Len() { + // return current, kind, false + // } + + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(namespace[idx+1:idx2])), namespace[idx2+1:]) + } + + // fmt.Println("Returning field") + return current, kind, true +} diff --git a/validator.go b/validator.go index 709a1fc..cd5e783 100644 --- a/validator.go +++ b/validator.go @@ -22,6 +22,7 @@ import ( const ( utf8HexComma = "0x2C" utf8Pipe = "0x7C" + namespaceSeparator = "." tagSeparator = "," orSeparator = "|" tagKeySeparator = "=" @@ -96,7 +97,7 @@ type CustomTypeFunc func(field reflect.Value) interface{} // 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 @@ -263,18 +264,11 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. 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.determineType(current) + var typ reflect.Type + switch kind { + case reflect.Ptr, reflect.Interface, reflect.Invalid: if strings.Contains(tag, omitempty) { return } @@ -299,6 +293,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } + // fmt.Println(kind) errs[errPrefix+name] = &FieldError{ Field: name, Tag: vals[0], @@ -314,67 +309,150 @@ 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 - } - } + case reflect.Struct: + typ = current.Type() if typ != timeType && typ != timePtrType { + // goto FALLTHROUGH - if kind == reflect.Struct { - - 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) return } + // FALLTHROUGH: + // fallthrough + // default: + // fmt.Println(tag) + // if len(tag) == 0 { + // 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() + // fmt.Println("Kind:", k) + + // 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 { + + // if strings.Contains(tag, omitempty) { + // return + // } + + // if len(tag) > 0 { + + // tags := strings.Split(tag, tagSeparator) + // var param string + // vals := strings.SplitN(tags[0], tagKeySeparator, 2) + + // if len(vals) > 1 { + // param = vals[1] + // } + + // if kind == reflect.Invalid { + // errs[errPrefix+name] = &FieldError{ + // Field: name, + // Tag: vals[0], + // Param: param, + // Kind: kind, + // } + // return + // } + + // errs[errPrefix+name] = &FieldError{ + // Field: name, + // Tag: vals[0], + // Param: param, + // Value: current.Interface(), + // Kind: kind, + // Type: current.Type(), + // } + + // return + // } + // // if we get here tag length is zero and we can leave + // if kind == reflect.Invalid { + // return + // } + // } + + // 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 { + + // if kind == reflect.Struct { + + // 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) + // return + // } + // } + // FALLTHROUGH: + // fallthrough + // default: + // if len(tag) == 0 { + // 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 + // } + // } + tags, isCached := tagsCache.Get(tag) if !isCached { @@ -431,7 +509,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 @@ -491,7 +569,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 +592,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 5ca43f9..ccd9a9b 100644 --- a/validator_test.go +++ b/validator_test.go @@ -192,6 +192,59 @@ func ValidateValuerType(field reflect.Value) interface{} { return nil } +func TestCrossNamespaceFieldValidation(t *testing.T) { + + type Inner struct { + CreatedAt *time.Time + Slice []string + Map map[string]string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time + } + + now := time.Now() + + inner := &Inner{ + CreatedAt: &now, + Slice: []string{"val1", "val2", "val3"}, + Map: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, + } + + 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.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") + + // fmt.Println(ok) + // fmt.Println(current) + // fmt.Println(kind) +} + func TestExistsValidation(t *testing.T) { jsonText := "{ \"truthiness2\": true }" @@ -2710,7 +2763,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 +2782,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 } From 2ea9043764be53679393bf021e233d5f414172ff Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Fri, 14 Aug 2015 00:28:37 -0400 Subject: [PATCH 02/26] update to handle nested map + Array + Slice structs --- util.go | 44 ++++++++++++++++++++++++++++++++++---------- validator_test.go | 34 +++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/util.go b/util.go index 7d55550..4dd39ba 100644 --- a/util.go +++ b/util.go @@ -78,8 +78,9 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re if idx == -1 { ns = namespace[bracketIdx:] } else { - ns = namespace[idx+bracketIdx:] + ns = namespace[bracketIdx:] } + // fmt.Println("NSS2:", ns) } // fmt.Println("Looking for field:", fld) @@ -93,6 +94,14 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re case reflect.Array, reflect.Slice: idx := strings.Index(namespace, "[") idx2 := strings.Index(namespace, "]") + // idx3 := strings.Index(namespace, namespaceSeparator) + + // if idx3 == -1 { + // idx3 = 0 + // } else { + // idx3 = 1 + // } + // arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2]) @@ -102,21 +111,36 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re return current, kind, false } - return v.getStructFieldOK(current.Index(arrIdx), namespace[idx2+1:]) + startIdx := idx2 + 1 + + if startIdx < len(namespace) { + if namespace[startIdx:startIdx+1] == "." { + startIdx++ + } + } + + return v.getStructFieldOK(current.Index(arrIdx), namespace[startIdx:]) case reflect.Map: - idx := strings.Index(namespace, "[") + idx := strings.Index(namespace, "[") + 1 idx2 := strings.Index(namespace, "]") - // key, _ := strconv.Atoi(namespace[idx+1 : idx2]) + endIdx := idx2 - // fmt.Println("ArrayIndex:", arrIdx) - // fmt.Println("LEN:", current.Len()) - // if arrIdx >= current.Len() { - // return current, kind, false - // } + // fmt.Println("END IDX:", endIdx) + // fmt.Println("L NS:", len(namespace)) + // fmt.Println("NS:", namespace) + + if endIdx+1 < len(namespace) { + if namespace[endIdx+1:endIdx+2] == "." { + endIdx++ + } + } + + // fmt.Println("KEY:", namespace[idx:idx2]) + // fmt.Println("KEY NS:", namespace[endIdx+1:]) - return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(namespace[idx+1:idx2])), namespace[idx2+1:]) + return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(namespace[idx:idx2])), namespace[endIdx+1:]) } // fmt.Println("Returning field") diff --git a/validator_test.go b/validator_test.go index ccd9a9b..761e3bf 100644 --- a/validator_test.go +++ b/validator_test.go @@ -194,10 +194,20 @@ func ValidateValuerType(field reflect.Value) interface{} { func TestCrossNamespaceFieldValidation(t *testing.T) { + type SliceStruct struct { + Name string + } + + type MapStruct struct { + Name string + } + type Inner struct { - CreatedAt *time.Time - Slice []string - Map map[string]string + CreatedAt *time.Time + Slice []string + SliceStructs []*SliceStruct + Map map[string]string + MapStructs map[string]*SliceStruct } type Test struct { @@ -208,9 +218,11 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { now := time.Now() inner := &Inner{ - CreatedAt: &now, - Slice: []string{"val1", "val2", "val3"}, - Map: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, + CreatedAt: &now, + Slice: []string{"val1", "val2", "val3"}, + SliceStructs: []*SliceStruct{{Name: "name1"}, {Name: "name2"}, {Name: "name3"}}, + Map: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, + MapStructs: map[string]*SliceStruct{"key1": {Name: "name1"}, "key2": {Name: "name2"}, "key3": {Name: "name3"}}, } test := &Test{ @@ -232,6 +244,9 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { 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) @@ -240,9 +255,10 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { Equal(t, kind, reflect.String) Equal(t, current.String(), "val3") - // fmt.Println(ok) - // fmt.Println(current) - // fmt.Println(kind) + current, kind, ok = validate.getStructFieldOK(val, "Inner.MapStructs[key2].Name") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "name2") } func TestExistsValidation(t *testing.T) { From 81e29d37246ca6f2b6a8f1006274ff4185c57bba Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Fri, 14 Aug 2015 08:43:54 -0400 Subject: [PATCH 03/26] Complete Test Cases for getStructFieldOK --- validator_test.go | 62 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/validator_test.go b/validator_test.go index 761e3bf..d938b1b 100644 --- a/validator_test.go +++ b/validator_test.go @@ -203,11 +203,17 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { } type Inner struct { - CreatedAt *time.Time - Slice []string - SliceStructs []*SliceStruct - Map map[string]string - MapStructs map[string]*SliceStruct + 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 } type Test struct { @@ -218,11 +224,17 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { now := time.Now() inner := &Inner{ - CreatedAt: &now, - Slice: []string{"val1", "val2", "val3"}, - SliceStructs: []*SliceStruct{{Name: "name1"}, {Name: "name2"}, {Name: "name3"}}, - Map: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, - MapStructs: map[string]*SliceStruct{"key1": {Name: "name1"}, "key2": {Name: "name2"}, "key3": {Name: "name3"}}, + 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"}}, } test := &Test{ @@ -255,10 +267,40 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { 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") } func TestExistsValidation(t *testing.T) { From d19088f86581b803416350b02e39af9405f5c6e1 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Fri, 14 Aug 2015 09:06:52 -0400 Subject: [PATCH 04/26] Code Cleanup + some renaming --- util.go | 64 +++++++++-------------- validator.go | 129 ++-------------------------------------------- validator_test.go | 27 ++++++++++ 3 files changed, 53 insertions(+), 167 deletions(-) diff --git a/util.go b/util.go index 4dd39ba..1494690 100644 --- a/util.go +++ b/util.go @@ -6,7 +6,13 @@ import ( "strings" ) -func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect.Kind) { +const ( + namespaceSeparator = "." + leftBracket = "[" + rightBracket = "]" +) + +func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Kind) { switch current.Kind() { case reflect.Ptr: @@ -15,7 +21,7 @@ func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect. return current, reflect.Ptr } - return v.determineType(current.Elem()) + return v.extractType(current.Elem()) case reflect.Interface: @@ -23,7 +29,7 @@ func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect. return current, reflect.Interface } - return v.determineType(current.Elem()) + return v.extractType(current.Elem()) case reflect.Invalid: @@ -31,10 +37,9 @@ func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect. default: - // fmt.Println(current.Kind()) if v.config.hasCustomFuncs { if fn, ok := v.config.CustomTypeFuncs[current.Type()]; ok { - return v.determineType(reflect.ValueOf(fn(current))) + return v.extractType(reflect.ValueOf(fn(current))) } } @@ -44,14 +49,15 @@ func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect. func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) { - // fmt.Println("NS:", namespace) - - current, kind := v.determineType(current) - - // fmt.Println("getStructFieldOK - ", current, kind) + current, kind := v.extractType(current) switch kind { case reflect.Ptr, reflect.Interface, reflect.Invalid: + + if len(namespace) == 0 { + return current, kind, true + } + return current, kind, false case reflect.Struct: @@ -63,50 +69,34 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re idx := strings.Index(namespace, namespaceSeparator) - // fmt.Println("IDX:", namespace, idx) if idx != -1 { fld = namespace[:idx] } ns := namespace[idx+1:] - bracketIdx := strings.Index(fld, "[") + bracketIdx := strings.Index(fld, leftBracket) if bracketIdx != -1 { fld = fld[:bracketIdx] - // fmt.Println("NSS:", ns) if idx == -1 { ns = namespace[bracketIdx:] } else { ns = namespace[bracketIdx:] } - // fmt.Println("NSS2:", ns) } - // fmt.Println("Looking for field:", fld) current = current.FieldByName(fld) - // fmt.Println("Current:", current) - return v.getStructFieldOK(current, ns) } case reflect.Array, reflect.Slice: - idx := strings.Index(namespace, "[") - idx2 := strings.Index(namespace, "]") - // idx3 := strings.Index(namespace, namespaceSeparator) - - // if idx3 == -1 { - // idx3 = 0 - // } else { - // idx3 = 1 - // } - // + idx := strings.Index(namespace, leftBracket) + idx2 := strings.Index(namespace, rightBracket) arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2]) - // fmt.Println("ArrayIndex:", arrIdx) - // fmt.Println("LEN:", current.Len()) if arrIdx >= current.Len() { return current, kind, false } @@ -114,7 +104,7 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re startIdx := idx2 + 1 if startIdx < len(namespace) { - if namespace[startIdx:startIdx+1] == "." { + if namespace[startIdx:startIdx+1] == namespaceSeparator { startIdx++ } } @@ -122,27 +112,19 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re return v.getStructFieldOK(current.Index(arrIdx), namespace[startIdx:]) case reflect.Map: - idx := strings.Index(namespace, "[") + 1 - idx2 := strings.Index(namespace, "]") + idx := strings.Index(namespace, leftBracket) + 1 + idx2 := strings.Index(namespace, rightBracket) endIdx := idx2 - // fmt.Println("END IDX:", endIdx) - // fmt.Println("L NS:", len(namespace)) - // fmt.Println("NS:", namespace) - if endIdx+1 < len(namespace) { - if namespace[endIdx+1:endIdx+2] == "." { + if namespace[endIdx+1:endIdx+2] == namespaceSeparator { endIdx++ } } - // fmt.Println("KEY:", namespace[idx:idx2]) - // fmt.Println("KEY NS:", namespace[endIdx+1:]) - return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(namespace[idx:idx2])), namespace[endIdx+1:]) } - // fmt.Println("Returning field") return current, kind, true } diff --git a/validator.go b/validator.go index cd5e783..13ea451 100644 --- a/validator.go +++ b/validator.go @@ -22,7 +22,6 @@ import ( const ( utf8HexComma = "0x2C" utf8Pipe = "0x7C" - namespaceSeparator = "." tagSeparator = "," orSeparator = "|" tagKeySeparator = "=" @@ -32,8 +31,8 @@ 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" ) @@ -264,7 +263,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - current, kind := v.determineType(current) + current, kind := v.extractType(current) var typ reflect.Type switch kind { @@ -293,7 +292,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - // fmt.Println(kind) errs[errPrefix+name] = &FieldError{ Field: name, Tag: vals[0], @@ -314,7 +312,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. typ = current.Type() if typ != timeType && typ != timePtrType { - // goto FALLTHROUGH // required passed validation above so stop here // if only validating the structs existance. @@ -325,13 +322,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false) return } - // FALLTHROUGH: - // fallthrough - // default: - // fmt.Println(tag) - // if len(tag) == 0 { - // return - // } } if len(tag) == 0 { @@ -339,119 +329,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. } typ = current.Type() - // fmt.Println("Kind:", k) - - // 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 { - - // if strings.Contains(tag, omitempty) { - // return - // } - - // if len(tag) > 0 { - - // tags := strings.Split(tag, tagSeparator) - // var param string - // vals := strings.SplitN(tags[0], tagKeySeparator, 2) - - // if len(vals) > 1 { - // param = vals[1] - // } - - // if kind == reflect.Invalid { - // errs[errPrefix+name] = &FieldError{ - // Field: name, - // Tag: vals[0], - // Param: param, - // Kind: kind, - // } - // return - // } - - // errs[errPrefix+name] = &FieldError{ - // Field: name, - // Tag: vals[0], - // Param: param, - // Value: current.Interface(), - // Kind: kind, - // Type: current.Type(), - // } - - // return - // } - // // if we get here tag length is zero and we can leave - // if kind == reflect.Invalid { - // return - // } - // } - - // 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 { - - // if kind == reflect.Struct { - - // 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) - // return - // } - // } - // FALLTHROUGH: - // fallthrough - // default: - // if len(tag) == 0 { - // 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 - // } - // } tags, isCached := tagsCache.Get(tag) diff --git a/validator_test.go b/validator_test.go index d938b1b..2c9302b 100644 --- a/validator_test.go +++ b/validator_test.go @@ -301,6 +301,33 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { Equal(t, ok, true) Equal(t, kind, reflect.String) Equal(t, current.String(), "9") + + 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) } func TestExistsValidation(t *testing.T) { From 2fe52ca08f2f1f0345550f3a668212f258a3ee77 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Fri, 14 Aug 2015 21:02:01 -0400 Subject: [PATCH 05/26] finish eqcsfield + test coverage --- baked_in.go | 87 +++++++++++++++++++--------- util.go | 52 +++++++++++++---- validator.go | 2 +- validator_test.go | 143 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 242 insertions(+), 42 deletions(-) diff --git a/baked_in.go b/baked_in.go index 8e5f648..daebec8 100644 --- a/baked_in.go +++ b/baked_in.go @@ -26,6 +26,7 @@ var BakedInValidators = map[string]Func{ "gt": isGt, "gte": isGte, "eqfield": isEqField, + "eqcsfield": isEqCrossStructField, "nefield": isNeField, "gtefield": isGteField, "gtfield": isGtField, @@ -253,68 +254,98 @@ func isNe(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fie return !isEq(v, topStruct, currentStruct, field, fieldType, fieldKind, param) } -func isEqField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isEqCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - // if current == nil { - if !current.IsValid() { - panic("struct or field value not passed for cross validation") - } + // if !topStruct.IsValid() { + // panic("struct or field value not passed for cross validation") + // } - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() + topField, topKind, ok := v.getStructFieldOK(topStruct, param) + if !ok || topKind != fieldKind { + // fmt.Println("NOT OK:", ok) + return false } - switch current.Kind() { + // fmt.Println("HERE", fieldKind) + 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: + // fmt.Println(topField.Len(), field.Len()) + return int64(topField.Len()) == int64(field.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 { - if current.Kind() == reflect.Invalid { - panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) + t := field.Interface().(time.Time) + fieldTime := topField.Interface().(time.Time) + + return fieldTime.Equal(t) } } - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() + // default reflect.String: + return topField.String() == current.String() +} + +func isEqField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + // if !currentStruct.IsValid() { + // panic("struct or field value not passed for cross validation") + // } + + currentField, currentKind, ok := v.getStructFieldOK(currentStruct, param) + if !ok || currentKind != 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() == 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.Slice, reflect.Map, reflect.Array: - return int64(field.Len()) == int64(current.Len()) + return int64(field.Len()) == int64(currentField.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 != currentField.Type() { + return false + } - t := current.Interface().(time.Time) + 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { diff --git a/util.go b/util.go index 1494690..dc612d3 100644 --- a/util.go +++ b/util.go @@ -51,12 +51,28 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re current, kind := v.extractType(current) + // fmt.Println("SOK:", current, kind, namespace) + + // if len(namespace) == 0 { + // // if kind == reflect.Invalid { + // // return current, kind, false + // // } + // return current, kind, true + // } + + if kind == reflect.Invalid { + return current, kind, false + } + + if len(namespace) == 0 { + return current, kind, true + } + switch kind { - case reflect.Ptr, reflect.Interface, reflect.Invalid: + // case reflect.Invalid: + // return current, kind, false - if len(namespace) == 0 { - return current, kind, true - } + case reflect.Ptr, reflect.Interface: return current, kind, false @@ -64,6 +80,7 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re typ := current.Type() fld := namespace + ns := namespace if typ != timeType && typ != timePtrType { @@ -71,23 +88,34 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re if idx != -1 { fld = namespace[:idx] + ns = namespace[idx+1:] + } else { + ns = "" + idx = len(namespace) } - ns := namespace[idx+1:] + // ns := namespace[idx+1:] bracketIdx := strings.Index(fld, leftBracket) if bracketIdx != -1 { fld = fld[:bracketIdx] - if idx == -1 { - ns = namespace[bracketIdx:] - } else { - ns = namespace[bracketIdx:] - } + ns = namespace[bracketIdx:] + // if idx == -1 { + // ns = namespace[bracketIdx:] + // } else { + // ns = namespace[bracketIdx:] + // } } current = current.FieldByName(fld) + // if current.Kind() == reflect.Invalid { + // return current, reflect.Invalid, false + // } + + // fmt.Println("NS:", ns, idx) + return v.getStructFieldOK(current, ns) } @@ -126,5 +154,7 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(namespace[idx:idx2])), namespace[endIdx+1:]) } - return current, kind, true + // if got here there was more namespace, cannot go any deeper + panic("Invalid field namespace") + // return current, kind, false } diff --git a/validator.go b/validator.go index 13ea451..e3866f4 100644 --- a/validator.go +++ b/validator.go @@ -311,7 +311,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. case reflect.Struct: typ = current.Type() - if typ != timeType && typ != timePtrType { + if typ != timeType { // required passed validation above so stop here // if only validating the structs existance. diff --git a/validator_test.go b/validator_test.go index 2c9302b..defdb22 100644 --- a/validator_test.go +++ b/validator_test.go @@ -192,6 +192,110 @@ func ValidateValuerType(field reflect.Value) interface{} { return 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 { @@ -328,6 +432,14 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { 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) { @@ -2063,9 +2175,11 @@ func TestIsNeFieldValidation(t *testing.T) { errs = validate.Struct(sv) Equal(t, errs, nil) + errs = validate.FieldWithValue(nil, 1, "nefield") + Equal(t, errs, nil) + channel := make(chan string) - 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") @@ -2182,9 +2296,12 @@ 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) - 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") @@ -2199,6 +2316,28 @@ func TestIsEqFieldValidation(t *testing.T) { } PanicMatches(t, func() { validate.Struct(sv2) }, "Field \"NonExistantField\" not found in struct") + + 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) { From 8aea478060e16e3cce850c709e2b5d8f4d54c24c Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Fri, 14 Aug 2015 21:19:41 -0400 Subject: [PATCH 06/26] Update isGtField to user new functions --- baked_in.go | 89 ++++++++++++++++++++++++++--------------------- validator_test.go | 6 ++++ 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/baked_in.go b/baked_in.go index daebec8..8519e94 100644 --- a/baked_in.go +++ b/baked_in.go @@ -256,17 +256,11 @@ func isNe(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fie func isEqCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - // if !topStruct.IsValid() { - // panic("struct or field value not passed for cross validation") - // } - topField, topKind, ok := v.getStructFieldOK(topStruct, param) if !ok || topKind != fieldKind { - // fmt.Println("NOT OK:", ok) return false } - // fmt.Println("HERE", fieldKind) switch fieldKind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -279,7 +273,6 @@ func isEqCrossStructField(v *Validate, topStruct reflect.Value, current reflect. return topField.Float() == field.Float() case reflect.Slice, reflect.Map, reflect.Array: - // fmt.Println(topField.Len(), field.Len()) return int64(topField.Len()) == int64(field.Len()) case reflect.Struct: @@ -304,10 +297,6 @@ func isEqCrossStructField(v *Validate, topStruct reflect.Value, current reflect. func isEqField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - // if !currentStruct.IsValid() { - // panic("struct or field value not passed for cross validation") - // } - currentField, currentKind, ok := v.getStructFieldOK(currentStruct, param) if !ok || currentKind != fieldKind { return false @@ -532,65 +521,87 @@ func isGteField(v *Validate, topStruct reflect.Value, current reflect.Value, fie panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isGtField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +// func isEqField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - if !current.IsValid() { - panic("struct not passed for cross validation") - } +// currentField, currentKind, ok := v.getStructFieldOK(currentStruct, param) +// if !ok || currentKind != fieldKind { +// return false +// } - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() - } +// switch fieldKind { - switch current.Kind() { +// case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: +// return field.Int() == currentField.Int() - case reflect.Struct: +// case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: +// return field.Uint() == currentField.Uint() - if current.Type() == timeType || current.Type() == timePtrType { - break - } +// case reflect.Float32, reflect.Float64: +// return field.Float() == currentField.Float() - current = current.FieldByName(param) +// case reflect.Slice, reflect.Map, reflect.Array: +// return int64(field.Len()) == int64(currentField.Len()) - if current.Kind() == reflect.Invalid { - panic(fmt.Sprintf("Field \"%s\" not found in struct", param)) - } - } +// case reflect.Struct: - if current.Kind() == reflect.Ptr && !current.IsNil() { - current = current.Elem() +// // 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) +// } + +// } + +// // default reflect.String: +// return field.String() == currentField.String() +// } + +func isGtField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + currentField, currentKind, ok := v.getStructFieldOK(currentStruct, 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { diff --git a/validator_test.go b/validator_test.go index defdb22..07a7678 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2459,6 +2459,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"` From 5989727cf7560f406f7097a0aecc7a82c3c943cd Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Fri, 14 Aug 2015 21:31:16 -0400 Subject: [PATCH 07/26] complete test coverage for IsGteField after conversion --- baked_in.go | 92 ++++++++--------------------------------------- validator_test.go | 10 ++++++ 2 files changed, 25 insertions(+), 77 deletions(-) diff --git a/baked_in.go b/baked_in.go index 8519e94..0c6ca4f 100644 --- a/baked_in.go +++ b/baked_in.go @@ -460,109 +460,47 @@ func hasValue(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, } } -func isGteField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isGteField(v *Validate, topStruct reflect.Value, currentStruct 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(currentStruct, 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 isEqField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - -// currentField, currentKind, ok := v.getStructFieldOK(currentStruct, 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()) - -// 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) -// } - -// } - -// // default reflect.String: -// return field.String() == currentField.String() -// } - func isGtField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { currentField, currentKind, ok := v.getStructFieldOK(currentStruct, param) diff --git a/validator_test.go b/validator_test.go index 07a7678..e9a9fb6 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2882,6 +2882,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"` From 4cbf0659ce05389132189612ce0d1bb7080ca2a5 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Fri, 14 Aug 2015 21:41:25 -0400 Subject: [PATCH 08/26] convert isLtField & isLteField to use new functions + test coverage --- baked_in.go | 100 ++++++++++++++-------------------------------- validator_test.go | 32 +++++++++++++-- 2 files changed, 59 insertions(+), 73 deletions(-) diff --git a/baked_in.go b/baked_in.go index 0c6ca4f..2ab23e4 100644 --- a/baked_in.go +++ b/baked_in.go @@ -669,126 +669,86 @@ func hasMinOf(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, return isGte(v, topStruct, currentStruct, field, fieldType, fieldKind, param) } -func isLteField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isLteField(v *Validate, topStruct reflect.Value, currentStruct 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(currentStruct, 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(v *Validate, 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, currentStruct 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(currentStruct, 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { diff --git a/validator_test.go b/validator_test.go index e9a9fb6..93cdb55 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2604,6 +2604,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"` @@ -2691,7 +2698,10 @@ 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") + errs = validate.FieldWithValue(nil, 5, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltfield") + 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") @@ -2743,6 +2753,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"` @@ -2830,7 +2850,10 @@ 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") + errs = validate.FieldWithValue(nil, 5, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "ltefield") + 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") @@ -2979,7 +3002,10 @@ 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") + errs = validate.FieldWithValue(nil, 1, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "gtefield") + 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") From 3a0791591a10f70e77e48ea874385e34e659103f Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 16 Aug 2015 10:26:30 -0400 Subject: [PATCH 09/26] Add cross struct map support for all types not just string --- util.go | 51 +++++++++++++++++++++++++- validator_test.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/util.go b/util.go index dc612d3..0d3f254 100644 --- a/util.go +++ b/util.go @@ -151,7 +151,56 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re } } - return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(namespace[idx:idx2])), namespace[endIdx+1:]) + 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:]) + } + // v.Type().Key().Kind() + + // return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:]) } // if got here there was more namespace, cannot go any deeper diff --git a/validator_test.go b/validator_test.go index 93cdb55..a0dc26a 100644 --- a/validator_test.go +++ b/validator_test.go @@ -318,6 +318,19 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { 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 { @@ -339,6 +352,19 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { 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{ @@ -406,6 +432,71 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { 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"}, From cd50c5e085619c58b68f630d0e694790da625eb7 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 16 Aug 2015 15:37:47 -0400 Subject: [PATCH 10/26] Added necsfield method + tests --- baked_in.go | 43 +++---------------- util.go | 36 ++++++++++++++++ validator_test.go | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 37 deletions(-) diff --git a/baked_in.go b/baked_in.go index 2ab23e4..6c42ee5 100644 --- a/baked_in.go +++ b/baked_in.go @@ -5,7 +5,6 @@ import ( "net" "net/url" "reflect" - "strconv" "strings" "time" "unicode/utf8" @@ -27,6 +26,7 @@ var BakedInValidators = map[string]Func{ "gte": isGte, "eqfield": isEqField, "eqcsfield": isEqCrossStructField, + "necsfield": isNeCrossStructField, "nefield": isNeField, "gtefield": isGteField, "gtfield": isGtField, @@ -254,6 +254,11 @@ func isNe(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fie return !isEq(v, topStruct, currentStruct, field, fieldType, fieldKind, param) } +func isNeCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + return !isEqCrossStructField(v, topStruct, current, field, fieldType, fieldKind, param) +} + 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) @@ -842,39 +847,3 @@ func hasMaxOf(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, return isLte(v, 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()) - } -} diff --git a/util.go b/util.go index 0d3f254..72c9f22 100644 --- a/util.go +++ b/util.go @@ -207,3 +207,39 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re panic("Invalid field namespace") // return current, kind, false } + +// 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_test.go b/validator_test.go index a0dc26a..1132e45 100644 --- a/validator_test.go +++ b/validator_test.go @@ -192,6 +192,112 @@ func ValidateValuerType(field reflect.Value) interface{} { return nil } +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 + 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, "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 { From 327aa2e82619598c6cbaeda6ec119d7a19171108 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 16 Aug 2015 15:59:05 -0400 Subject: [PATCH 11/26] Add gtcsfield, gtecsfield, ltcsfield and ltecsfield --- baked_in.go | 166 +++++++++++++++++++++++++++++++++++++++++++++- validator_test.go | 4 -- 2 files changed, 165 insertions(+), 5 deletions(-) diff --git a/baked_in.go b/baked_in.go index 6c42ee5..41e2527 100644 --- a/baked_in.go +++ b/baked_in.go @@ -254,6 +254,170 @@ func isNe(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fie return !isEq(v, topStruct, currentStruct, field, fieldType, fieldKind, param) } +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()) + + 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) + } + } + + // default reflect.String: + return topField.String() <= field.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 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: + + // 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) + } + } + + // default reflect.String: + return topField.String() < field.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.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 topField.String() >= field.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() > 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) + } + } + + // default reflect.String: + return topField.String() > field.String() +} + func isNeCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return !isEqCrossStructField(v, topStruct, current, field, fieldType, fieldKind, param) @@ -297,7 +461,7 @@ func isEqCrossStructField(v *Validate, topStruct reflect.Value, current reflect. } // default reflect.String: - return topField.String() == current.String() + return topField.String() == field.String() } func isEqField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { diff --git a/validator_test.go b/validator_test.go index 1132e45..244eb75 100644 --- a/validator_test.go +++ b/validator_test.go @@ -224,16 +224,12 @@ func TestCrossStructNeFieldValidation(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "Test.CreatedAt", "CreatedAt", "necsfield") - // 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 From 1fbc38427277cf87998dda8e5213736fda9c17a2 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 16 Aug 2015 21:12:26 -0400 Subject: [PATCH 12/26] Complete test coverage for gecsfield, gtecsfield, ltcsfield and ltecsfield --- baked_in.go | 12 +- validator_test.go | 320 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 324 insertions(+), 8 deletions(-) diff --git a/baked_in.go b/baked_in.go index 41e2527..b99fc32 100644 --- a/baked_in.go +++ b/baked_in.go @@ -27,6 +27,10 @@ var BakedInValidators = map[string]Func{ "eqfield": isEqField, "eqcsfield": isEqCrossStructField, "necsfield": isNeCrossStructField, + "gtcsfield": isGtCrossStructField, + "gtecsfield": isGteCrossStructField, + "ltcsfield": isLtCrossStructField, + "ltecsfield": isLteCrossStructField, "nefield": isNeField, "gtefield": isGteField, "gtfield": isGtField, @@ -292,7 +296,7 @@ func isLteCrossStructField(v *Validate, topStruct reflect.Value, current reflect } // default reflect.String: - return topField.String() <= field.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 { @@ -333,7 +337,7 @@ func isLtCrossStructField(v *Validate, topStruct reflect.Value, current reflect. } // default reflect.String: - return topField.String() < field.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 { @@ -374,7 +378,7 @@ func isGteCrossStructField(v *Validate, topStruct reflect.Value, current reflect } // default reflect.String: - return topField.String() >= field.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 { @@ -415,7 +419,7 @@ func isGtCrossStructField(v *Validate, topStruct reflect.Value, current reflect. } // default reflect.String: - return topField.String() > field.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 { diff --git a/validator_test.go b/validator_test.go index 244eb75..dce61c5 100644 --- a/validator_test.go +++ b/validator_test.go @@ -192,6 +192,314 @@ func ValidateValuerType(field reflect.Value) interface{} { return nil } +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 { @@ -224,16 +532,20 @@ func TestCrossStructNeFieldValidation(t *testing.T) { 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 + j = 1 + k = 1.543 arr := []string{"test"} s2 := "abcd" i2 := 1 - j2 := 1 - k2 := 1.543 + j2 = 1 + k2 = 1.543 arr2 := []string{"test"} arr3 := []string{"test", "test2"} now2 := now From 8ff687aae4f1e7bb81f511a10230c2dc97ca531e Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 16 Aug 2015 21:17:00 -0400 Subject: [PATCH 13/26] code cleanup --- util.go | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/util.go b/util.go index 72c9f22..0efb789 100644 --- a/util.go +++ b/util.go @@ -51,15 +51,6 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re current, kind := v.extractType(current) - // fmt.Println("SOK:", current, kind, namespace) - - // if len(namespace) == 0 { - // // if kind == reflect.Invalid { - // // return current, kind, false - // // } - // return current, kind, true - // } - if kind == reflect.Invalid { return current, kind, false } @@ -69,8 +60,6 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re } switch kind { - // case reflect.Invalid: - // return current, kind, false case reflect.Ptr, reflect.Interface: @@ -94,28 +83,15 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re idx = len(namespace) } - // ns := namespace[idx+1:] - bracketIdx := strings.Index(fld, leftBracket) if bracketIdx != -1 { fld = fld[:bracketIdx] ns = namespace[bracketIdx:] - // if idx == -1 { - // ns = namespace[bracketIdx:] - // } else { - // ns = namespace[bracketIdx:] - // } } current = current.FieldByName(fld) - // if current.Kind() == reflect.Invalid { - // return current, reflect.Invalid, false - // } - - // fmt.Println("NS:", ns, idx) - return v.getStructFieldOK(current, ns) } @@ -198,14 +174,10 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re default: return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:]) } - // v.Type().Key().Kind() - - // 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") - // return current, kind, false } // asInt retuns the parameter as a int64 From 3697be93bed164aee4d55e0f9abe01739c5f555a Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 16 Aug 2015 21:23:51 -0400 Subject: [PATCH 14/26] update benchmarks, pretty much the same --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8c40597..a691a5c 100644 --- a/README.md +++ b/README.md @@ -194,22 +194,22 @@ hurt parallel performance too much. ```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 326 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 327 ns/op 16 B/op 1 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 490 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 829 ns/op 416 B/op 6 allocs/op +BenchmarkFieldOrTagSuccess-4 500000 2448 ns/op 20 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1290 ns/op 384 B/op 6 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1233 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1847 ns/op 529 B/op 11 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1292 ns/op 56 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1840 ns/op 577 B/op 13 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 353 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 746 ns/op 529 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 7265 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailure-4 100000 12068 ns/op 2860 B/op 72 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2179 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 4436 ns/op 2863 B/op 72 allocs/op ``` How to Contribute From 8ae139a445163064a10aa349ee15ccb8cdd4de92 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 17 Aug 2015 07:37:05 -0400 Subject: [PATCH 15/26] Partially Merged in Partial struct methods + Tests --- validator.go | 129 +++++++++++++++++++++---- validator_test.go | 237 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+), 17 deletions(-) diff --git a/validator.go b/validator.go index e3866f4..71268f0 100644 --- a/validator.go +++ b/validator.go @@ -38,10 +38,11 @@ const ( ) 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 @@ -180,7 +181,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 +199,91 @@ 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 that are listed by name in the map including nested structs, unless otherwise specified. Items in the map that are NOT found in the struct will cause a panic. +func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { + + sv, _ := v.extractType(reflect.ValueOf(current)) + name := sv.Type().Name() + m := map[string]*struct{}{} + + var i int + + if fields != nil { + for _, k := range fields { + + flds := strings.Split(k, ".") + if len(flds) > 0 { + + key := name + for _, s := range flds { + + idx := strings.Index(s, "[") + + if idx != -1 { + for idx != -1 { + i++ + key += s[:idx] + m[key] = emptyStructPtr + + idx2 := strings.Index(s, "]") + idx2++ + key += s[idx:idx2] + m[key] = emptyStructPtr + s = s[idx2:] + idx = strings.Index(s, "[") + + if i == 10 { + idx = -1 + } + } + } else { + + key += s + m[key] = emptyStructPtr + } + + key += "." + } + } + } + } + + 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 the fields in the struct that are NOT listed by name in the map including nested structs, unless otherwise specified. Items in the map that are NOT found in the struct will cause a panic. +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 +299,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 +310,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 +320,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,12 +338,21 @@ 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 @@ -319,7 +414,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false) + v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false, partial, exclude, includeExclude) return } } @@ -402,9 +497,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 @@ -414,18 +509,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) } } diff --git a/validator_test.go b/validator_test.go index dce61c5..4cb96cf 100644 --- a/validator_test.go +++ b/validator_test.go @@ -192,6 +192,243 @@ 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) + // AssertError(t, errs, ".A", "A", "required") + + // // will fail as unset feild is tested + // errs = validate.StructPartial(tPartial, p2) + // AssertError(t, errs, "TestPartial.Anonymous.A", "A", "required") + + // errs = validate.StructExcept(tPartial, p1) + // 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) + // //Equal(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) + + // // Case note: + // // were bypassing dive here? by setting a single item? + // // im not sure anyone would or should do this, I cant think of a reason + // // why they would but you never know. As for describing this behavior in + // // documentation I would be at a loss as to do it + // // especialy concidering the next test + // errs = validate.StructExcept(tPartial, p2) + // Equal(t, errs, nil) + // //AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + + // // test sub validation: + // // this is diving + // errs = validate.StructExcept(tPartial, p1) + // AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + // AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + // Equal(t, len(errs), 2) + + // errs = validate.StructPartial(tPartial, p2) + // //Equal(t, errs, nil) + // AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + // Equal(t, len(errs), 1) + + // // 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) + // AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + // Equal(t, len(errs), 1) + + // // See above case note... this is a variation on the above + // // when all taken into account it seems super strange! + // errs = validate.StructExcept(tPartial, p2) + // Equal(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) + // AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required") + + // // See above case note... this is a variation on the above + // // when all taken into account it seems super strange! + // errs = validate.StructExcept(tPartial, p2) + // Equal(t, errs, nil) + // //AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required") + +} + func TestCrossStructLteFieldValidation(t *testing.T) { type Inner struct { From 656ae32e8b18f895e32452663fc70566ea41a852 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 17 Aug 2015 09:05:20 -0400 Subject: [PATCH 16/26] Updated PanicMatches after assertion library updates now panic less, instead of panicing when data types do not match in the field and cross field validations, the validation just fails, because it's true i.e. does nil != 5 pass or does nil == "string" nope fail --- baked_in.go | 12 +++++++ util.go | 1 - validator_test.go | 83 +++++++++++++++++++++++++++++++++-------------- 3 files changed, 70 insertions(+), 26 deletions(-) diff --git a/baked_in.go b/baked_in.go index b99fc32..8d305bf 100644 --- a/baked_in.go +++ b/baked_in.go @@ -251,6 +251,13 @@ func contains(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, } func isNeField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + _, currentKind, ok := v.getStructFieldOK(currentStruct, param) + + if !ok || currentKind != fieldKind { + return true + } + return !isEqField(v, topStruct, currentStruct, field, fieldType, fieldKind, param) } @@ -424,6 +431,11 @@ func isGtCrossStructField(v *Validate, topStruct reflect.Value, current reflect. func isNeCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + _, currentKind, ok := v.getStructFieldOK(topStruct, param) + if !ok || currentKind != fieldKind { + return true + } + return !isEqCrossStructField(v, topStruct, current, field, fieldType, fieldKind, param) } diff --git a/util.go b/util.go index 0efb789..641c2d4 100644 --- a/util.go +++ b/util.go @@ -32,7 +32,6 @@ func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Ki return v.extractType(current.Elem()) case reflect.Invalid: - return current, reflect.Invalid default: diff --git a/validator_test.go b/validator_test.go index 1ce10cf..48dc3b0 100644 --- a/validator_test.go +++ b/validator_test.go @@ -309,9 +309,9 @@ func TestStructPartial(t *testing.T) { 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.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") @@ -2920,11 +2920,6 @@ func TestIsNeFieldValidation(t *testing.T) { errs = validate.FieldWithValue(nil, 1, "nefield") Equal(t, errs, nil) - channel := make(chan string) - - 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") - type Test2 struct { Start *time.Time `validate:"nefield=NonExistantField"` End *time.Time @@ -2935,7 +2930,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) { @@ -3043,9 +3039,13 @@ func TestIsEqFieldValidation(t *testing.T) { 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(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"` @@ -3057,7 +3057,9 @@ 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 @@ -3294,9 +3296,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"` @@ -3308,7 +3318,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) { @@ -3444,8 +3456,13 @@ func TestLtField(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "ltfield") - 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(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"` @@ -3457,7 +3474,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) { @@ -3596,8 +3615,13 @@ func TestLteField(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "ltefield") - 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(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"` @@ -3609,7 +3633,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) { @@ -3748,8 +3774,13 @@ func TestGteField(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "gtefield") - 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(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"` @@ -3761,7 +3792,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) { From 387cfe5aa92b1b698e9387d255c064e7d781a847 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 17 Aug 2015 13:18:28 -0400 Subject: [PATCH 17/26] Complete adding StructPartial and StructExcept for issue-#149 --- README.md | 30 +++--- validator.go | 19 ++-- validator_test.go | 234 ++++++++++++++++++++++------------------------ 3 files changed, 135 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index 8f9933e..0c00871 100644 --- a/README.md +++ b/README.md @@ -194,22 +194,22 @@ hurt parallel performance too much. ```go $ go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkFieldSuccess-4 5000000 326 ns/op 16 B/op 1 allocs/op -BenchmarkFieldFailure-4 5000000 327 ns/op 16 B/op 1 allocs/op -BenchmarkFieldCustomTypeSuccess-4 3000000 490 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 2000000 829 ns/op 416 B/op 6 allocs/op -BenchmarkFieldOrTagSuccess-4 500000 2448 ns/op 20 B/op 2 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1290 ns/op 384 B/op 6 allocs/op -BenchmarkStructSimpleSuccess-4 1000000 1233 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailure-4 1000000 1847 ns/op 529 B/op 11 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1292 ns/op 56 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1840 ns/op 577 B/op 13 allocs/op +BenchmarkFieldSuccess-4 5000000 332 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 334 ns/op 16 B/op 1 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 502 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 833 ns/op 416 B/op 6 allocs/op +BenchmarkFieldOrTagSuccess-4 500000 2520 ns/op 20 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1310 ns/op 384 B/op 6 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1274 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1887 ns/op 529 B/op 11 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1374 ns/op 56 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1871 ns/op 577 B/op 13 allocs/op BenchmarkStructSimpleSuccessParallel-4 5000000 353 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 746 ns/op 529 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 200000 7265 ns/op 368 B/op 30 allocs/op -BenchmarkStructComplexFailure-4 100000 12068 ns/op 2860 B/op 72 allocs/op -BenchmarkStructComplexSuccessParallel-4 1000000 2179 ns/op 368 B/op 30 allocs/op -BenchmarkStructComplexFailureParallel-4 300000 4436 ns/op 2863 B/op 72 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 799 ns/op 529 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 7521 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailure-4 100000 12341 ns/op 2861 B/op 72 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2463 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 5141 ns/op 2862 B/op 72 allocs/op ``` How to Contribute diff --git a/validator.go b/validator.go index 71268f0..3ca8419 100644 --- a/validator.go +++ b/validator.go @@ -216,35 +216,28 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati name := sv.Type().Name() m := map[string]*struct{}{} - var i int - if fields != nil { for _, k := range fields { - flds := strings.Split(k, ".") + flds := strings.Split(k, namespaceSeparator) if len(flds) > 0 { - key := name + key := name + namespaceSeparator for _, s := range flds { - idx := strings.Index(s, "[") + idx := strings.Index(s, leftBracket) if idx != -1 { for idx != -1 { - i++ key += s[:idx] m[key] = emptyStructPtr - idx2 := strings.Index(s, "]") + idx2 := strings.Index(s, rightBracket) idx2++ key += s[idx:idx2] m[key] = emptyStructPtr s = s[idx2:] - idx = strings.Index(s, "[") - - if i == 10 { - idx = -1 - } + idx = strings.Index(s, leftBracket) } } else { @@ -252,7 +245,7 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati m[key] = emptyStructPtr } - key += "." + key += namespaceSeparator } } } diff --git a/validator_test.go b/validator_test.go index 48dc3b0..eca7baf 100644 --- a/validator_test.go +++ b/validator_test.go @@ -228,9 +228,9 @@ func TestStructPartial(t *testing.T) { "SubTest.Test", } - // p4 := []string{ - // "A", - // } + p4 := []string{ + "A", + } tPartial := &TestPartial{ NoTag: "NoTag", @@ -309,123 +309,117 @@ func TestStructPartial(t *testing.T) { 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) - // AssertError(t, errs, ".A", "A", "required") - - // // will fail as unset feild is tested - // errs = validate.StructPartial(tPartial, p2) - // AssertError(t, errs, "TestPartial.Anonymous.A", "A", "required") - - // errs = validate.StructExcept(tPartial, p1) - // 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) - // //Equal(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) - - // // Case note: - // // were bypassing dive here? by setting a single item? - // // im not sure anyone would or should do this, I cant think of a reason - // // why they would but you never know. As for describing this behavior in - // // documentation I would be at a loss as to do it - // // especialy concidering the next test - // errs = validate.StructExcept(tPartial, p2) - // Equal(t, errs, nil) - // //AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") - - // // test sub validation: - // // this is diving - // errs = validate.StructExcept(tPartial, p1) - // AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") - // AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") - // Equal(t, len(errs), 2) - - // errs = validate.StructPartial(tPartial, p2) - // //Equal(t, errs, nil) - // AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") - // Equal(t, len(errs), 1) - - // // 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) - // AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") - // Equal(t, len(errs), 1) - - // // See above case note... this is a variation on the above - // // when all taken into account it seems super strange! - // errs = validate.StructExcept(tPartial, p2) - // Equal(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) - // AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required") - - // // See above case note... this is a variation on the above - // // when all taken into account it seems super strange! - // errs = validate.StructExcept(tPartial, p2) - // Equal(t, errs, nil) - // //AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required") + 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") } From 4f46e021330768a74bdb017d5604ac35b9e17853 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Tue, 18 Aug 2015 21:10:40 -0400 Subject: [PATCH 18/26] Add documentation for cross struct validation tags + Struct Partials --- doc.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++------ validator.go | 9 ++++++-- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/doc.go b/doc.go index db05710..13bb66e 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/validator.go b/validator.go index 3ca8419..39f0f6a 100644 --- a/validator.go +++ b/validator.go @@ -93,6 +93,7 @@ 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 @@ -209,7 +210,9 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string return errs } -// StructPartial validates the fields that are listed by name in the map including nested structs, unless otherwise specified. Items in the map that are NOT found in the struct will cause a panic. +// 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 func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { sv, _ := v.extractType(reflect.ValueOf(current)) @@ -263,7 +266,9 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati return errs } -// StructExcept validates the fields in the struct that are NOT listed by name in the map including nested structs, unless otherwise specified. Items in the map that are NOT found in the struct will cause a panic. +// 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 func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors { sv, _ := v.extractType(reflect.ValueOf(current)) From 883731a774dcdcbe4d60aee19d29a822dbae3a87 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Tue, 18 Aug 2015 22:10:26 -0400 Subject: [PATCH 19/26] Updates + Benchmarks update documentation. add benchmarks for StructPartial, dive tag, cross field and cross struct cross field. --- benchmarks_test.go | 196 ++++++++++++++++++++++++++++++++++++++------- validator.go | 4 + 2 files changed, 172 insertions(+), 28 deletions(-) diff --git a/benchmarks_test.go b/benchmarks_test.go index af201e6..1cd19b2 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,161 @@ 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 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/validator.go b/validator.go index 39f0f6a..3542569 100644 --- a/validator.go +++ b/validator.go @@ -213,6 +213,8 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string // StructPartial validates the fields passed in only, ignoring all others. // Fields may be provided in a namespaced fashion relative to the struct provided // i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name +// NOTE: This is normally not needed, however in some specific cases such as: tied to a +// legacy data structure, it will be useful func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { sv, _ := v.extractType(reflect.ValueOf(current)) @@ -269,6 +271,8 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati // StructExcept validates all fields except the ones passed in. // Fields may be provided in a namespaced fashion relative to the struct provided // i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name +// NOTE: This is normally not needed, however in some specific cases such as: tied to a +// legacy data structure, it will be useful func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors { sv, _ := v.extractType(reflect.ValueOf(current)) From 7757a227ed8593829941157f598e227becb50c46 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Tue, 18 Aug 2015 22:14:28 -0400 Subject: [PATCH 20/26] Add benchmark for StructExcept --- benchmarks_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/benchmarks_test.go b/benchmarks_test.go index 1cd19b2..3649bf9 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -153,6 +153,38 @@ func BenchmarkStructPartialFailure(b *testing.B) { } } +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 { From b5317c5c5c7b6de9093e3b8d5879bce4592e4122 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Wed, 19 Aug 2015 08:47:31 -0400 Subject: [PATCH 21/26] Update examples_test.go Backport change to update import path from pull request #155 --- examples_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_test.go b/examples_test.go index 9d01450..f2dad92 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() { From 14b90946b3a72c7933160bddc20f0c6a9f3b169f Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 19 Aug 2015 13:05:04 -0400 Subject: [PATCH 22/26] Backport v7 updates backporting v7 updates for the near released v7. --- README.md | 38 ++++---- baked_in.go | 37 -------- benchmarks_test.go | 120 +++++++++++++++++------ util.go | 82 ++++++++++++++++ validator.go | 205 +++++++++++++++++++++++++--------------- validator_test.go | 231 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 554 insertions(+), 159 deletions(-) create mode 100644 util.go diff --git a/README.md b/README.md index 8c40597..d977d20 100644 --- a/README.md +++ b/README.md @@ -192,24 +192,28 @@ NOTE: allocations for structs are up from v5, however ns/op for parallel operati It was a decicion not to cache struct info because although it reduced allocation to v5 levels, it hurt parallel performance too much. ```go -$ go test -cpu=4 -bench=. -benchmem=true + 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 337 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 331 ns/op 16 B/op 1 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 497 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 842 ns/op 416 B/op 6 allocs/op +BenchmarkFieldOrTagSuccess-4 500000 2432 ns/op 20 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1323 ns/op 384 B/op 6 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1409 ns/op 56 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1876 ns/op 577 B/op 13 allocs/op +BenchmarkStructPartialSuccess-4 1000000 1438 ns/op 384 B/op 13 allocs/op +BenchmarkStructPartialFailure-4 1000000 2040 ns/op 785 B/op 18 allocs/op +BenchmarkStructExceptSuccess-4 1000000 1000 ns/op 368 B/op 11 allocs/op +BenchmarkStructExceptFailure-4 1000000 1431 ns/op 384 B/op 13 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1375 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1893 ns/op 529 B/op 11 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 362 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 883 ns/op 529 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 8237 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailure-4 100000 12617 ns/op 2861 B/op 72 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2398 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 5733 ns/op 2862 B/op 72 allocs/op ``` How to Contribute diff --git a/baked_in.go b/baked_in.go index b323863..8bf4ff1 100644 --- a/baked_in.go +++ b/baked_in.go @@ -5,7 +5,6 @@ import ( "net" "net/url" "reflect" - "strconv" "strings" "time" "unicode/utf8" @@ -902,39 +901,3 @@ func hasMaxOf(topStruct reflect.Value, currentStruct reflect.Value, field reflec 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()) - } -} diff --git a/benchmarks_test.go b/benchmarks_test.go index af201e6..35e702a 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -62,34 +62,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 +108,98 @@ 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 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/util.go b/util.go new file mode 100644 index 0000000..1406fef --- /dev/null +++ b/util.go @@ -0,0 +1,82 @@ +package validator + +import ( + "reflect" + "strconv" +) + +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() + } +} + +// 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 709a1fc..b03b318 100644 --- a/validator.go +++ b/validator.go @@ -38,10 +38,11 @@ const ( ) 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 @@ -180,7 +181,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 +199,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 +300,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 +311,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 +321,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 +339,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 +403,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 { @@ -447,9 +498,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 +510,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) } } diff --git a/validator_test.go b/validator_test.go index c17167a..42085f0 100644 --- a/validator_test.go +++ b/validator_test.go @@ -192,6 +192,237 @@ 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 TestExistsValidation(t *testing.T) { jsonText := "{ \"truthiness2\": true }" From 15fdf82c301d0be6edd16337ba5faf2bb2263117 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 19 Aug 2015 13:32:32 -0400 Subject: [PATCH 23/26] Update verbiage for unique features for v7 cross struct cross field validation tags. --- README.md | 2 +- baked_in.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 3eaa61b..ca9e9f8 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Package validator implements value validations for structs and individual fields 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) diff --git a/baked_in.go b/baked_in.go index 0378d8b..46cf022 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1023,7 +1023,6 @@ func isLt(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fie // 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { return isLte(v, topStruct, currentStruct, field, fieldType, fieldKind, param) } From 55f9e44ce51d283728a04dfb31535390c1cb20f9 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 19 Aug 2015 17:06:19 -0400 Subject: [PATCH 24/26] Update benchmarks for new go 1.5! --- README.md | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ca9e9f8..4bf4d23 100644 --- a/README.md +++ b/README.md @@ -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 332 ns/op 16 B/op 1 allocs/op -BenchmarkFieldFailure-4 5000000 334 ns/op 16 B/op 1 allocs/op -BenchmarkFieldCustomTypeSuccess-4 3000000 502 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 2000000 833 ns/op 416 B/op 6 allocs/op -BenchmarkFieldOrTagSuccess-4 500000 2520 ns/op 20 B/op 2 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1310 ns/op 384 B/op 6 allocs/op -BenchmarkStructSimpleSuccess-4 1000000 1274 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailure-4 1000000 1887 ns/op 529 B/op 11 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1374 ns/op 56 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1871 ns/op 577 B/op 13 allocs/op -BenchmarkStructSimpleSuccessParallel-4 5000000 353 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 799 ns/op 529 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 200000 7521 ns/op 368 B/op 30 allocs/op -BenchmarkStructComplexFailure-4 100000 12341 ns/op 2861 B/op 72 allocs/op -BenchmarkStructComplexSuccessParallel-4 1000000 2463 ns/op 368 B/op 30 allocs/op -BenchmarkStructComplexFailureParallel-4 300000 5141 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 From 43d7f25cfb70904835766ba096c63ed290f304cc Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 19 Aug 2015 20:17:28 -0400 Subject: [PATCH 25/26] update nefield and necsfield to hav own logic instead of calling !eqfield... --- .gitignore | 1 + baked_in.go | 71 ++++++++++++++++++++++++++++++++++++++++++++--- validator_test.go | 3 ++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 7e9b500..792ca00 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/baked_in.go b/baked_in.go index 46cf022..5a7435a 100644 --- a/baked_in.go +++ b/baked_in.go @@ -252,13 +252,45 @@ func contains(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, func isNeField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - _, currentKind, ok := v.getStructFieldOK(currentStruct, param) + currentField, currentKind, ok := v.getStructFieldOK(currentStruct, param) if !ok || currentKind != fieldKind { return true } - return !isEqField(v, topStruct, currentStruct, field, fieldType, fieldKind, param) + 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { @@ -431,12 +463,43 @@ func isGtCrossStructField(v *Validate, topStruct reflect.Value, current reflect. func isNeCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - _, currentKind, ok := v.getStructFieldOK(topStruct, param) + topField, currentKind, ok := v.getStructFieldOK(topStruct, param) if !ok || currentKind != fieldKind { return true } - return !isEqCrossStructField(v, topStruct, current, field, fieldType, fieldKind, param) + 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 { diff --git a/validator_test.go b/validator_test.go index eca7baf..22084b4 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2914,6 +2914,9 @@ func TestIsNeFieldValidation(t *testing.T) { errs = validate.FieldWithValue(nil, 1, "nefield") Equal(t, errs, nil) + errs = validate.FieldWithValue(sv, now, "nefield") + Equal(t, errs, nil) + type Test2 struct { Start *time.Time `validate:"nefield=NonExistantField"` End *time.Time From ce06c472673316e5335d0fd8e6f738496a26f932 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 19 Aug 2015 20:22:26 -0400 Subject: [PATCH 26/26] rename some variable for clarity --- baked_in.go | 136 ++++++++++++++++++++++++++-------------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/baked_in.go b/baked_in.go index 5a7435a..f9a0245 100644 --- a/baked_in.go +++ b/baked_in.go @@ -76,32 +76,32 @@ var BakedInValidators = map[string]Func{ "mac": isMac, } -func isMac(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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 @@ -110,15 +110,15 @@ func isSSN(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fi return matchesRegex(sSNRegex, field.String()) } -func isLongitude(v *Validate, 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(v *Validate, 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(v *Validate, 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) @@ -132,10 +132,10 @@ func isDataURI(v *Validate, topStruct reflect.Value, currentStruct reflect.Value fld := reflect.ValueOf(uri[1]) - return isBase64(v, topStruct, currentStruct, fld, fld.Type(), fld.Kind(), param) + return isBase64(v, topStruct, currentStructOrField, fld, fld.Type(), fld.Kind(), param) } -func hasMultiByteCharacter(v *Validate, 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 @@ -144,35 +144,35 @@ func hasMultiByteCharacter(v *Validate, topStruct reflect.Value, currentStruct r return matchesRegex(multibyteRegex, field.String()) } -func isPrintableASCII(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return isISBN10(v, topStruct, currentStruct, field, fieldType, fieldKind, param) || isISBN13(v, 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(v *Validate, 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) @@ -196,7 +196,7 @@ func isISBN13(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, return false } -func isISBN10(v *Validate, 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) @@ -224,35 +224,35 @@ func isISBN10(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, return false } -func excludesRune(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return !containsRune(v, 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return !containsAny(v, 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return !contains(v, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +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(currentStruct, param) + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) if !ok || currentKind != fieldKind { return true @@ -293,8 +293,8 @@ func isNeField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value return field.String() != currentField.String() } -func isNe(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return !isEq(v, 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 isLteCrossStructField(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { @@ -543,9 +543,9 @@ func isEqCrossStructField(v *Validate, topStruct reflect.Value, current reflect. return topField.String() == field.String() } -func isEqField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +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(currentStruct, param) + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) if !ok || currentKind != fieldKind { return false } @@ -585,7 +585,7 @@ func isEqField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value return field.String() == currentField.String() } -func isEq(v *Validate, 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 { @@ -616,11 +616,11 @@ func isEq(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fie panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isBase64(v *Validate, 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(v *Validate, 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 { @@ -633,7 +633,7 @@ func isURI(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fi panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isURL(v *Validate, 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 { @@ -654,51 +654,51 @@ func isURL(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fi panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isEmail(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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(v *Validate, 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: @@ -708,9 +708,9 @@ func hasValue(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, } } -func isGteField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isGteField(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(currentStruct, param) + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) if !ok || currentKind != fieldKind { return false } @@ -749,9 +749,9 @@ func isGteField(v *Validate, topStruct reflect.Value, currentStruct reflect.Valu return len(field.String()) >= len(currentField.String()) } -func isGtField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isGtField(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(currentStruct, param) + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) if !ok || currentKind != fieldKind { return false } @@ -790,7 +790,7 @@ func isGtField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value return len(field.String()) > len(currentField.String()) } -func isGte(v *Validate, 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 { @@ -833,7 +833,7 @@ func isGte(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fi panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isGt(v *Validate, 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 { @@ -875,7 +875,7 @@ func isGt(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fie // 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(v *Validate, 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 { @@ -912,14 +912,14 @@ func hasLengthOf(v *Validate, topStruct reflect.Value, currentStruct reflect.Val // 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(v *Validate, 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(v, topStruct, currentStruct, field, fieldType, fieldKind, param) + return isGte(v, topStruct, currentStructOrField, field, fieldType, fieldKind, param) } -func isLteField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { +func isLteField(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(currentStruct, param) + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) if !ok || currentKind != fieldKind { return false } @@ -958,9 +958,9 @@ func isLteField(v *Validate, topStruct reflect.Value, currentStruct reflect.Valu return len(field.String()) <= len(currentField.String()) } -func isLtField(v *Validate, topStruct reflect.Value, currentStruct 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 { - currentField, currentKind, ok := v.getStructFieldOK(currentStruct, param) + currentField, currentKind, ok := v.getStructFieldOK(currentStructOrField, param) if !ok || currentKind != fieldKind { return false } @@ -999,7 +999,7 @@ func isLtField(v *Validate, topStruct reflect.Value, currentStruct reflect.Value return len(field.String()) < len(currentField.String()) } -func isLte(v *Validate, 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 { @@ -1042,7 +1042,7 @@ func isLte(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fi panic(fmt.Sprintf("Bad field type %T", field.Interface())) } -func isLt(v *Validate, 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 { @@ -1086,6 +1086,6 @@ func isLt(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, fie // 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(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return isLte(v, topStruct, currentStruct, field, fieldType, fieldKind, param) +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) }