diff --git a/README.md b/README.md index 512696d..2e56559 100644 --- a/README.md +++ b/README.md @@ -308,38 +308,38 @@ func validateStruct() { Benchmarks ------ -###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go 1.5.1 +###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.5.2 darwin/amd64 ```go -$ go test -cpu=4 -bench=. -benchmem=true +go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkFieldSuccess-4 10000000 162 ns/op 0 B/op 0 allocs/op -BenchmarkFieldFailure-4 2000000 678 ns/op 400 B/op 4 allocs/op -BenchmarkFieldDiveSuccess-4 500000 3079 ns/op 480 B/op 27 allocs/op -BenchmarkFieldDiveFailure-4 300000 3584 ns/op 880 B/op 31 allocs/op -BenchmarkFieldCustomTypeSuccess-4 5000000 345 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 2000000 650 ns/op 400 B/op 4 allocs/op -BenchmarkFieldOrTagSuccess-4 1000000 1188 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1088 ns/op 432 B/op 6 allocs/op -BenchmarkStructLevelValidationSuccess-4 2000000 689 ns/op 160 B/op 6 allocs/op -BenchmarkStructLevelValidationFailure-4 1000000 1290 ns/op 592 B/op 11 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 2000000 911 ns/op 80 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1446 ns/op 624 B/op 11 allocs/op -BenchmarkStructPartialSuccess-4 1000000 1221 ns/op 384 B/op 10 allocs/op -BenchmarkStructPartialFailure-4 1000000 1764 ns/op 800 B/op 15 allocs/op -BenchmarkStructExceptSuccess-4 2000000 941 ns/op 336 B/op 7 allocs/op -BenchmarkStructExceptFailure-4 1000000 1237 ns/op 384 B/op 10 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-4 2000000 970 ns/op 128 B/op 6 allocs/op -BenchmarkStructSimpleCrossFieldFailure-4 1000000 1560 ns/op 560 B/op 11 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1542 ns/op 176 B/op 9 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 2147 ns/op 608 B/op 14 allocs/op -BenchmarkStructSimpleSuccess-4 2000000 847 ns/op 48 B/op 3 allocs/op -BenchmarkStructSimpleFailure-4 1000000 1497 ns/op 624 B/op 11 allocs/op -BenchmarkStructSimpleSuccessParallel-4 5000000 257 ns/op 48 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 586 ns/op 624 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 300000 5104 ns/op 496 B/op 29 allocs/op -BenchmarkStructComplexFailure-4 200000 9840 ns/op 3400 B/op 71 allocs/op -BenchmarkStructComplexSuccessParallel-4 1000000 1540 ns/op 496 B/op 29 allocs/op -BenchmarkStructComplexFailureParallel-4 500000 3478 ns/op 3400 B/op 71 allocs/op +BenchmarkFieldSuccess-4 10000000 176 ns/op 0 B/op 0 allocs/op +BenchmarkFieldFailure-4 2000000 727 ns/op 432 B/op 4 allocs/op +BenchmarkFieldDiveSuccess-4 500000 3220 ns/op 480 B/op 27 allocs/op +BenchmarkFieldDiveFailure-4 500000 3823 ns/op 912 B/op 31 allocs/op +BenchmarkFieldCustomTypeSuccess-4 5000000 368 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 699 ns/op 432 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-4 1000000 1265 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1182 ns/op 464 B/op 6 allocs/op +BenchmarkStructLevelValidationSuccess-4 2000000 739 ns/op 176 B/op 6 allocs/op +BenchmarkStructLevelValidationFailure-4 1000000 1368 ns/op 640 B/op 11 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 2000000 965 ns/op 80 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1561 ns/op 688 B/op 11 allocs/op +BenchmarkStructPartialSuccess-4 1000000 1285 ns/op 384 B/op 10 allocs/op +BenchmarkStructPartialFailure-4 1000000 1879 ns/op 832 B/op 15 allocs/op +BenchmarkStructExceptSuccess-4 2000000 1038 ns/op 336 B/op 7 allocs/op +BenchmarkStructExceptFailure-4 1000000 1330 ns/op 384 B/op 10 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1081 ns/op 128 B/op 6 allocs/op +BenchmarkStructSimpleCrossFieldFailure-4 1000000 1737 ns/op 592 B/op 11 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1790 ns/op 192 B/op 10 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-4 500000 2431 ns/op 656 B/op 15 allocs/op +BenchmarkStructSimpleSuccess-4 2000000 950 ns/op 48 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1672 ns/op 688 B/op 11 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 271 ns/op 48 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 670 ns/op 688 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 300000 5828 ns/op 544 B/op 32 allocs/op +BenchmarkStructComplexFailure-4 200000 11382 ns/op 3912 B/op 77 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 1739 ns/op 544 B/op 32 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 4682 ns/op 3912 B/op 77 allocs/op ``` How to Contribute diff --git a/baked_in.go b/baked_in.go index 9338cf5..e9fc9a4 100644 --- a/baked_in.go +++ b/baked_in.go @@ -756,7 +756,7 @@ func IsURL(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Va return false } - if len(url.Scheme) == 0 { + if url.Scheme == blank { return false } diff --git a/util.go b/util.go index abe6b03..ce01c7c 100644 --- a/util.go +++ b/util.go @@ -8,6 +8,7 @@ import ( ) const ( + dash = "-" blank = "" namespaceSeparator = "." leftBracket = "[" @@ -86,7 +87,7 @@ func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (re return current, kind, false } - if len(namespace) == 0 { + if namespace == blank { return current, kind, true } @@ -262,7 +263,7 @@ func (v *Validate) parseStruct(current reflect.Value, sName string) *cachedStruc fld = typ.Field(i) - if len(fld.PkgPath) != 0 { + if fld.PkgPath != blank { continue } @@ -273,7 +274,7 @@ func (v *Validate) parseStruct(current reflect.Value, sName string) *cachedStruc } customName = fld.Name - if len(v.fieldNameTag) != 0 { + if v.fieldNameTag != blank { name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0] @@ -309,7 +310,7 @@ func (v *Validate) parseTags(tag, fieldName string) *cachedTag { func (v *Validate) parseTagsRecursive(cTag *cachedTag, tag, fieldName, alias string, isAlias bool) bool { - if len(tag) == 0 { + if tag == blank { return true } @@ -365,7 +366,7 @@ func (v *Validate) parseTagsRecursive(cTag *cachedTag, tag, fieldName, alias str tagVal.tag = alias } - if len(key) == 0 { + if key == blank { panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName))) } diff --git a/validator.go b/validator.go index 65a0914..d3cc543 100644 --- a/validator.go +++ b/validator.go @@ -52,6 +52,7 @@ type StructLevel struct { TopStruct reflect.Value CurrentStruct reflect.Value errPrefix string + nsPrefix string errs ValidationErrors v *Validate } @@ -60,9 +61,29 @@ type StructLevel struct { // Example: had a triple nested struct User, ContactInfo, Country and ran errs := validate.Struct(country) // from within a User struct level validation would call this method like so: // ReportValidationErrors("ContactInfo.", errs) +// NOTE: relativeKey can contain both the Field Relative and Custom name relative paths +// i.e. ReportValidationErrors("ContactInfo.|cInfo", errs) where cInfo represents say the JSON name of +// the relative path; this will be split into 2 variables in the next valiator version. func (sl *StructLevel) ReportValidationErrors(relativeKey string, errs ValidationErrors) { for _, e := range errs { - sl.errs[sl.errPrefix+relativeKey+e.Field] = e + + idx := strings.Index(relativeKey, "|") + var rel string + var cRel string + + if idx != -1 { + rel = relativeKey[:idx] + cRel = relativeKey[idx+1:] + } else { + rel = relativeKey + } + + key := sl.errPrefix + rel + e.Field + + e.FieldNamespace = key + e.NameNamespace = sl.nsPrefix + cRel + e.Name + + sl.errs[key] = e } } @@ -73,38 +94,44 @@ func (sl *StructLevel) ReportError(field reflect.Value, fieldName string, custom field, kind := sl.v.ExtractType(field) - if len(fieldName) == 0 { + if fieldName == blank { panic(fieldNameRequired) } - if len(customName) == 0 { + if customName == blank { customName = fieldName } - if len(tag) == 0 { + if tag == blank { panic(tagRequired) } + ns := sl.errPrefix + fieldName + switch kind { case reflect.Invalid: - sl.errs[sl.errPrefix+fieldName] = &FieldError{ - Name: customName, - Field: fieldName, - Tag: tag, - ActualTag: tag, - Param: blank, - Kind: kind, + sl.errs[ns] = &FieldError{ + FieldNamespace: ns, + NameNamespace: sl.nsPrefix + customName, + Name: customName, + Field: fieldName, + Tag: tag, + ActualTag: tag, + Param: blank, + Kind: kind, } default: - sl.errs[sl.errPrefix+fieldName] = &FieldError{ - Name: customName, - Field: fieldName, - Tag: tag, - ActualTag: tag, - Param: blank, - Value: field.Interface(), - Kind: kind, - Type: field.Type(), + sl.errs[ns] = &FieldError{ + FieldNamespace: ns, + NameNamespace: sl.nsPrefix + customName, + Name: customName, + Field: fieldName, + Tag: tag, + ActualTag: tag, + Param: blank, + Value: field.Interface(), + Kind: kind, + Type: field.Type(), } } } @@ -178,14 +205,16 @@ func (ve ValidationErrors) Error() string { // FieldError contains a single field's validation error along // with other properties that may be needed for error message creation type FieldError struct { - Field string - Name string - Tag string - ActualTag string - Kind reflect.Kind - Type reflect.Type - Param string - Value interface{} + FieldNamespace string + NameNamespace string + Field string + Name string + Tag string + ActualTag string + Kind reflect.Kind + Type reflect.Type + Param string + Value interface{} } // New creates a new Validate instance for use. @@ -241,7 +270,7 @@ func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interfa func (v *Validate) RegisterValidation(key string, fn Func) error { v.initCheck() - if len(key) == 0 { + if key == blank { return errors.New("Function Key cannot be empty") } @@ -306,7 +335,7 @@ func (v *Validate) Field(field interface{}, tag string) error { errs := v.errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) - v.traverseField(fieldVal, fieldVal, fieldVal, blank, errs, false, tag, blank, blank, false, false, nil, nil) + v.traverseField(fieldVal, fieldVal, fieldVal, blank, blank, errs, false, tag, blank, blank, false, false, nil, nil) if len(errs) == 0 { v.errsPool.Put(errs) @@ -326,7 +355,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string errs := v.errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) - v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, errs, false, tag, blank, blank, false, false, nil, nil) + v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, blank, errs, false, tag, blank, blank, false, false, nil, nil) if len(errs) == 0 { v.errsPool.Put(errs) @@ -384,7 +413,7 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) error { errs := v.errsPool.Get().(ValidationErrors) - v.tranverseStruct(sv, sv, sv, blank, errs, true, len(m) != 0, false, m, false) + v.tranverseStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, false, m, false) if len(errs) == 0 { v.errsPool.Put(errs) @@ -406,12 +435,12 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) error { m := map[string]*struct{}{} for _, key := range fields { - m[name+"."+key] = emptyStructPtr + m[name+namespaceSeparator+key] = emptyStructPtr } errs := v.errsPool.Get().(ValidationErrors) - v.tranverseStruct(sv, sv, sv, blank, errs, true, len(m) != 0, true, m, false) + v.tranverseStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, true, m, false) if len(errs) == 0 { v.errsPool.Put(errs) @@ -430,7 +459,7 @@ func (v *Validate) Struct(current interface{}) error { errs := v.errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) - v.tranverseStruct(sv, sv, sv, blank, errs, true, false, false, nil, false) + v.tranverseStruct(sv, sv, sv, blank, blank, errs, true, false, false, nil, false) if len(errs) == 0 { v.errsPool.Put(errs) @@ -441,7 +470,7 @@ func (v *Validate) Struct(current interface{}) error { } // tranverseStruct traverses a structs fields and then passes them to be validated by traverseField -func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]*struct{}, isStructOnly bool) { +func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]*struct{}, isStructOnly bool) { if current.Kind() == reflect.Ptr && !current.IsNil() { current = current.Elem() @@ -457,7 +486,11 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec sName := typ.Name() if useStructName { - errPrefix += sName + "." + errPrefix += sName + namespaceSeparator + + if v.fieldNameTag != blank { + nsPrefix += sName + namespaceSeparator + } } // structonly tag present don't tranverseFields @@ -469,7 +502,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec // is anonymous struct, cannot parse or cache as // it has no name to index by - if len(sName) == 0 { + if sName == blank { var customName string var ok bool @@ -479,7 +512,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec fld = typ.Field(i) - if len(fld.PkgPath) != 0 { + if fld.PkgPath != blank { continue } @@ -493,17 +526,18 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec } customName = fld.Name - if v.fieldNameTag != "" { + + if v.fieldNameTag != blank { name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0] // dash check is for json "-" means don't output in json - if name != "" && name != "-" { + if name != blank && name != dash { customName = name } } - v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.tagName), fld.Name, customName, partial, exclude, includeExclude, nil) + v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, nsPrefix, errs, true, fld.Tag.Get(v.tagName), fld.Name, customName, partial, exclude, includeExclude, nil) } } else { s, ok := v.structCache.Get(typ) @@ -523,7 +557,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec } fld = typ.Field(i) - v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, f.CachedTag.tag, fld.Name, f.AltName, partial, exclude, includeExclude, f.CachedTag) + v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, nsPrefix, errs, true, f.CachedTag.tag, fld.Name, f.AltName, partial, exclude, includeExclude, f.CachedTag) } } } @@ -531,13 +565,13 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec // check if any struct level validations, after all field validations already checked. if v.hasStructLevelFuncs { if fn, ok := v.structLevelFuncs[current.Type()]; ok { - fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, errs: errs}) + fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs}) } } } // 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, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) { +func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, isStructField bool, tag, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) { if tag == skipValidationTag { return @@ -561,29 +595,35 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - if len(tag) > 0 { + if tag != blank { + + ns := errPrefix + name if kind == reflect.Invalid { - errs[errPrefix+name] = &FieldError{ - Name: customName, - Field: name, - Tag: cTag.tags[0].tag, - ActualTag: cTag.tags[0].tagVals[0][0], - Param: cTag.tags[0].tagVals[0][1], - Kind: kind, + errs[ns] = &FieldError{ + FieldNamespace: ns, + NameNamespace: nsPrefix + customName, + Name: customName, + Field: name, + Tag: cTag.tags[0].tag, + ActualTag: cTag.tags[0].tagVals[0][0], + Param: cTag.tags[0].tagVals[0][1], + Kind: kind, } return } - errs[errPrefix+name] = &FieldError{ - Name: customName, - Field: name, - Tag: cTag.tags[0].tag, - ActualTag: cTag.tags[0].tagVals[0][0], - Param: cTag.tags[0].tagVals[0][1], - Value: current.Interface(), - Kind: kind, - Type: current.Type(), + errs[ns] = &FieldError{ + FieldNamespace: ns, + NameNamespace: nsPrefix + customName, + Name: customName, + Field: name, + Tag: cTag.tags[0].tag, + ActualTag: cTag.tags[0].tagVals[0][0], + Param: cTag.tags[0].tagVals[0][1], + Value: current.Interface(), + Kind: kind, + Type: current.Type(), } return @@ -603,12 +643,12 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false, partial, exclude, includeExclude, cTag.isStructOnly) + v.tranverseStruct(topStruct, current, current, errPrefix+name+namespaceSeparator, nsPrefix+customName+namespaceSeparator, errs, false, partial, exclude, includeExclude, cTag.isStructOnly) return } } - if len(tag) == 0 { + if tag == blank { return } @@ -637,7 +677,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. continue } - if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, errs, valTag, name, customName) { + if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, nsPrefix, errs, valTag, name, customName) { return } } @@ -647,9 +687,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, customName, partial, exclude, includeExclude, nil) + v.traverseSlice(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil) case reflect.Map: - v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil) + v.traverseMap(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil) default: // throw error, if not a slice or map then should not have gotten here // bad dive tag @@ -659,23 +699,23 @@ 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, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) { +func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, tag, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) { for i := 0; i < current.Len(); i++ { - v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i), fmt.Sprintf(arrayIndexFieldName, customName, i), partial, exclude, includeExclude, cTag) + v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, nsPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i), fmt.Sprintf(arrayIndexFieldName, customName, i), partial, exclude, includeExclude, cTag) } } // 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, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) { +func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, tag, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) { for _, key := range current.MapKeys() { - v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface()), fmt.Sprintf(mapIndexFieldName, customName, key.Interface()), partial, exclude, includeExclude, cTag) + v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, nsPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface()), fmt.Sprintf(mapIndexFieldName, customName, key.Interface()), partial, exclude, includeExclude, cTag) } } // validateField validates a field based on the provided tag's key and param values and returns true if there is an error or false if all ok -func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, errs ValidationErrors, valTag *tagVals, name, customName string) bool { +func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, nsPrefix string, errs ValidationErrors, valTag *tagVals, name, customName string) bool { var valFunc Func var ok bool @@ -698,25 +738,31 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. errTag += orSeparator + val[0] } + ns := errPrefix + name + if valTag.isAlias { - errs[errPrefix+name] = &FieldError{ - Name: customName, - Field: name, - Tag: valTag.tag, - ActualTag: errTag[1:], - Value: current.Interface(), - Type: currentType, - Kind: currentKind, + errs[ns] = &FieldError{ + FieldNamespace: ns, + NameNamespace: nsPrefix + customName, + Name: customName, + Field: name, + Tag: valTag.tag, + ActualTag: errTag[1:], + Value: current.Interface(), + Type: currentType, + Kind: currentKind, } } else { errs[errPrefix+name] = &FieldError{ - Name: customName, - Field: name, - Tag: errTag[1:], - ActualTag: errTag[1:], - Value: current.Interface(), - Type: currentType, - Kind: currentKind, + FieldNamespace: ns, + NameNamespace: nsPrefix + customName, + Name: customName, + Field: name, + Tag: errTag[1:], + ActualTag: errTag[1:], + Value: current.Interface(), + Type: currentType, + Kind: currentKind, } } @@ -732,15 +778,19 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. return false } - errs[errPrefix+name] = &FieldError{ - Name: customName, - Field: name, - Tag: valTag.tag, - ActualTag: valTag.tagVals[0][0], - Value: current.Interface(), - Param: valTag.tagVals[0][1], - Type: currentType, - Kind: currentKind, + ns := errPrefix + name + + errs[ns] = &FieldError{ + FieldNamespace: ns, + NameNamespace: nsPrefix + customName, + Name: customName, + Field: name, + Tag: valTag.tag, + ActualTag: valTag.tagVals[0][0], + Value: current.Interface(), + Param: valTag.tagVals[0][1], + Type: currentType, + Kind: currentKind, } return true diff --git a/validator_test.go b/validator_test.go index 7f70c61..189e522 100644 --- a/validator_test.go +++ b/validator_test.go @@ -284,8 +284,20 @@ func StructValidationTestStructReturnValidationErrors(v *Validate, structLevel * structLevel.ReportValidationErrors("Inner1.", errs.(ValidationErrors)) } +func StructValidationTestStructReturnValidationErrors2(v *Validate, structLevel *StructLevel) { + + s := structLevel.CurrentStruct.Interface().(TestStructReturnValidationErrors) + + errs := v.Struct(s.Inner1.Inner2) + if errs == nil { + return + } + + structLevel.ReportValidationErrors("Inner1.|Inner1JSON.", errs.(ValidationErrors)) +} + type TestStructReturnValidationErrorsInner2 struct { - String string `validate:"required"` + String string `validate:"required" json:"JSONString"` } type TestStructReturnValidationErrorsInner1 struct { @@ -293,7 +305,52 @@ type TestStructReturnValidationErrorsInner1 struct { } type TestStructReturnValidationErrors struct { - Inner1 *TestStructReturnValidationErrorsInner1 + Inner1 *TestStructReturnValidationErrorsInner1 `json:"Inner1JSON"` +} + +type Inner2Namespace struct { + String []string `validate:"dive,required" json:"JSONString"` +} + +type Inner1Namespace struct { + Inner2 *Inner2Namespace `json:"Inner2JSON"` +} + +type Namespace struct { + Inner1 *Inner1Namespace `json:"Inner1JSON"` +} + +func TestNameNamespace(t *testing.T) { + + config := &Config{ + TagName: "validate", + FieldNameTag: "json", + } + + v1 := New(config) + i2 := &Inner2Namespace{String: []string{"ok", "ok", "ok"}} + i1 := &Inner1Namespace{Inner2: i2} + ns := &Namespace{Inner1: i1} + + errs := v1.Struct(ns) + Equal(t, errs, nil) + + i2.String[1] = "" + + errs = v1.Struct(ns) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "Namespace.Inner1.Inner2.String[1]", "String[1]", "required") + + fe, ok := ve["Namespace.Inner1.Inner2.String[1]"] + Equal(t, ok, true) + + Equal(t, fe.Field, "String[1]") + Equal(t, fe.FieldNamespace, "Namespace.Inner1.Inner2.String[1]") + Equal(t, fe.Name, "JSONString[1]") + Equal(t, fe.NameNamespace, "Namespace.Inner1JSON.Inner2JSON.JSONString[1]") } func TestAnonymous(t *testing.T) { @@ -377,7 +434,62 @@ func TestStructLevelReturnValidationErrors(t *testing.T) { errs = v1.Struct(val) NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 2) AssertError(t, errs, "TestStructReturnValidationErrors.Inner1.Inner2.String", "String", "required") + // this is an extra error reported from struct validation + AssertError(t, errs, "TestStructReturnValidationErrors.Inner1.String", "String", "required") +} + +func TestStructLevelReturnValidationErrorsWithJSON(t *testing.T) { + config := &Config{ + TagName: "validate", + FieldNameTag: "json", + } + + v1 := New(config) + v1.RegisterStructValidation(StructValidationTestStructReturnValidationErrors2, TestStructReturnValidationErrors{}) + + inner2 := &TestStructReturnValidationErrorsInner2{ + String: "I'm HERE", + } + + inner1 := &TestStructReturnValidationErrorsInner1{ + Inner2: inner2, + } + + val := &TestStructReturnValidationErrors{ + Inner1: inner1, + } + + errs := v1.Struct(val) + Equal(t, errs, nil) + + inner2.String = "" + + errs = v1.Struct(val) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 2) + AssertError(t, errs, "TestStructReturnValidationErrors.Inner1.Inner2.String", "String", "required") + // this is an extra error reported from struct validation, it's a badly formatted one, but on purpose + AssertError(t, errs, "TestStructReturnValidationErrors.Inner1.String", "String", "required") + + fe, ok := errs.(ValidationErrors)["TestStructReturnValidationErrors.Inner1.Inner2.String"] + Equal(t, ok, true) + + // check for proper JSON namespace + Equal(t, fe.Field, "String") + Equal(t, fe.Name, "JSONString") + Equal(t, fe.FieldNamespace, "TestStructReturnValidationErrors.Inner1.Inner2.String") + Equal(t, fe.NameNamespace, "TestStructReturnValidationErrors.Inner1JSON.Inner2.JSONString") + + fe, ok = errs.(ValidationErrors)["TestStructReturnValidationErrors.Inner1.String"] + Equal(t, ok, true) + + // check for proper JSON namespace + Equal(t, fe.Field, "String") + Equal(t, fe.Name, "JSONString") + Equal(t, fe.FieldNamespace, "TestStructReturnValidationErrors.Inner1.String") + Equal(t, fe.NameNamespace, "TestStructReturnValidationErrors.Inner1JSON.JSONString") } func TestStructLevelValidations(t *testing.T) {