mirror of
https://github.com/Fishwaldo/validator.git
synced 2025-03-17 12:41:33 +00:00
Merge pull request #229 from go-playground/v8-development
Add FieldNamespace and NameNamespace + string comparison optimizations
This commit is contained in:
commit
e8d470cd7a
5 changed files with 297 additions and 134 deletions
60
README.md
60
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
11
util.go
11
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)))
|
||||
}
|
||||
|
||||
|
|
242
validator.go
242
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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue