Finish initial array traversal logic

for #78
pull/82/head
joeybloggs 10 years ago
parent a3cf2f5cf3
commit 4afdc19aef
  1. 309
      validator.go
  2. 32
      validator_test.go

@ -32,6 +32,8 @@ const (
sliceErrMsg = "Field validation for \"%s\" index \"%d\" failed on the \"%s\" tag" sliceErrMsg = "Field validation for \"%s\" index \"%d\" failed on the \"%s\" tag"
mapErrMsg = "Field validation for \"%s\" key \"%s\" failed on the \"%s\" tag" mapErrMsg = "Field validation for \"%s\" key \"%s\" failed on the \"%s\" tag"
structErrMsg = "Struct:%s\n" structErrMsg = "Struct:%s\n"
diveTag = "dive"
diveSplit = "," + diveTag
) )
var structPool *pool var structPool *pool
@ -80,13 +82,23 @@ type cachedTags struct {
} }
type cachedField struct { type cachedField struct {
index int index int
name string name string
tags []*cachedTags tags []*cachedTags
tag string tag string
kind reflect.Kind kind reflect.Kind
typ reflect.Type typ reflect.Type
isTime bool isTime bool
isSliceOrArray bool
isMap bool
isTimeSubtype bool
sliceSubtype reflect.Type
mapSubtype reflect.Type
sliceSubKind reflect.Kind
mapSubKind reflect.Kind
// DiveMaxDepth uint64 // zero means no depth
// DiveTags map[uint64]string // map of dive depth and associated tag as string]
diveTag string
} }
type cachedStruct struct { type cachedStruct struct {
@ -181,17 +193,19 @@ var fieldsCache = &fieldsCacheMap{m: map[string][]*cachedTags{}}
// FieldError contains a single field's validation error along // FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation // with other properties that may be needed for error message creation
type FieldError struct { type FieldError struct {
Field string Field string
Tag string Tag string
Kind reflect.Kind Kind reflect.Kind
Type reflect.Type Type reflect.Type
Param string Param string
Value interface{} Value interface{}
IsSliceOrArrayError bool isPlaceholderErr bool
IsMapError bool IsSliceOrArray bool
Key interface{} IsMap bool
Index uint64 // Key interface{}
DiveErrors []*error // counld be FieldError, StructErrors // Index int
SliceOrArrayErrs []error // counld be FieldError, StructErrors
MapErrs map[interface{}]error // counld be FieldError, StructErrors
} }
// This is intended for use in development + debugging and not intended to be a production error message. // This is intended for use in development + debugging and not intended to be a production error message.
@ -211,10 +225,12 @@ type StructErrors struct {
// key = Field Name of current struct, but internally Struct will be the actual struct name unless anonymous struct, it will be blank // key = Field Name of current struct, but internally Struct will be the actual struct name unless anonymous struct, it will be blank
StructErrors map[string]*StructErrors StructErrors map[string]*StructErrors
IsSliceOrArrayError bool // Index int
IsMapError bool // Key interface{}
Key interface{} // IsSliceOrArrayError bool
Index uint64 // IsMapError bool
// Key interface{}
// Index uint64
} }
// This is intended for use in development + debugging and not intended to be a production error message. // This is intended for use in development + debugging and not intended to be a production error message.
@ -393,7 +409,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
typeField = structType.Field(i) typeField = structType.Field(i)
cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName)} cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName), isTime: valueField.Type() == reflect.TypeOf(time.Time{})}
if cField.tag == noValidationTag { if cField.tag == noValidationTag {
cs.children-- cs.children--
@ -426,9 +442,9 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
continue continue
} }
if cField.isTime || valueField.Type() == reflect.TypeOf(time.Time{}) { if cField.isTime {
cField.isTime = true // cField.isTime = true
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
@ -468,8 +484,31 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
} }
} }
default: case reflect.Slice, reflect.Array:
cField.isSliceOrArray = true
cField.sliceSubtype = cField.typ.Elem()
cField.isTimeSubtype = cField.sliceSubtype == reflect.TypeOf(time.Time{})
cField.sliceSubKind = cField.sliceSubtype.Kind()
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference
fieldError = nil
}
case reflect.Map:
cField.isMap = true
cField.mapSubtype = cField.typ.Elem()
cField.isTimeSubtype = cField.mapSubtype == reflect.TypeOf(time.Time{})
cField.mapSubKind = cField.mapSubtype.Kind()
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference
fieldError = nil
}
default:
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference // free up memory reference
@ -506,6 +545,8 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
var cField *cachedField var cField *cachedField
var isCached bool var isCached bool
// var isInDive bool
var valueField reflect.Value
// This is a double check if coming from validate.Struct but need to be here in case function is called directly // This is a double check if coming from validate.Struct but need to be here in case function is called directly
if tag == noValidationTag { if tag == noValidationTag {
@ -516,8 +557,10 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
return nil return nil
} }
valueField = reflect.ValueOf(f)
if cacheField == nil { if cacheField == nil {
valueField := reflect.ValueOf(f) // valueField = reflect.ValueOf(f)
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
valueField = valueField.Elem() valueField = valueField.Elem()
@ -525,6 +568,19 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
} }
cField = &cachedField{name: name, kind: valueField.Kind(), tag: tag, typ: valueField.Type()} cField = &cachedField{name: name, kind: valueField.Kind(), tag: tag, typ: valueField.Type()}
switch cField.kind {
case reflect.Slice, reflect.Array:
cField.isSliceOrArray = true
cField.sliceSubtype = cField.typ.Elem()
cField.isTimeSubtype = cField.sliceSubtype == reflect.TypeOf(time.Time{})
cField.sliceSubKind = cField.sliceSubtype.Kind()
case reflect.Map:
cField.isMap = true
cField.mapSubtype = cField.typ.Elem()
cField.isTimeSubtype = cField.mapSubtype == reflect.TypeOf(time.Time{})
cField.mapSubKind = cField.mapSubtype.Kind()
}
} else { } else {
cField = cacheField cField = cacheField
} }
@ -546,11 +602,37 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
if !isCached { if !isCached {
for _, t := range strings.Split(tag, tagSeparator) { for k, t := range strings.Split(tag, tagSeparator) {
if t == diveTag {
if k == 0 {
cField.diveTag = tag[4:]
} else {
cField.diveTag = strings.SplitN(tag, diveSplit, 2)[1][1:]
}
break
}
orVals := strings.Split(t, orSeparator) orVals := strings.Split(t, orSeparator)
cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))} cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))}
// if isInDive {
// s, ok := cField.DiveTags[cField.DiveMaxDepth]
// if ok {
// cField.DiveTags[cField.DiveMaxDepth] = cField.DiveTags[cField.DiveMaxDepth] + tagSeparator + tag
// } else {
// cField.DiveTags[cField.DiveMaxDepth] = tag
// }
// continue
// } else {
cField.tags = append(cField.tags, cTag) cField.tags = append(cField.tags, cTag)
// }
for i, val := range orVals { for i, val := range orVals {
vals := strings.SplitN(val, tagKeySeparator, 2) vals := strings.SplitN(val, tagKeySeparator, 2)
@ -614,9 +696,180 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
} }
} }
if len(cField.diveTag) > 0 {
if cField.isSliceOrArray {
if errs := v.traverseSliceOrArray(val, current, valueField, cField); errs != nil && len(errs) > 0 {
return &FieldError{
Field: cField.name,
Kind: cField.kind,
Type: cField.typ,
Value: f,
isPlaceholderErr: true,
IsSliceOrArray: true,
// Index: i,
SliceOrArrayErrs: errs,
}
}
// return if error here
} else if cField.isMap {
// return if error here
} else {
// throw error, if not a slice or map then should not have gotten here
}
// dive tags need to be passed to traverse
// traverse needs to call a SliceOrArray recursive function to meet depth requirements
// for depth, diveTag := range cField.DiveTags {
// // error returned should be added to SliceOrArrayErrs
// if errs := v.traverseSliceOrArrayField(val, current, depth, currentDepth+1, diveTag, cField, valueField); len(errs) > 0 {
// // result := &FieldError{
// // Field: cField.name,
// // Kind: cField.kind,
// // Type: cField.typ,
// // Value: valueField.Index(i).Interface(),
// // isPlaceholderErr: true,
// // IsSliceOrArray:true,
// // Index:i,
// // SliceOrArrayErrs:
// // }
// }
// for _, tag := range diveTag {
// fmt.Println("Depth:", depth, " Tag:", tag, " SliceType:", cField.SliceSubtype, " MapType:", cField.MapSubtype, " Kind:", cField.kind)
// }
// }
}
return nil return nil
} }
func (v *Validate) traverseSliceOrArray(val interface{}, current interface{}, valueField reflect.Value, cField *cachedField) []error {
errs := make([]error, 0)
for i := 0; i < valueField.Len(); i++ {
idxField := valueField.Index(i)
switch cField.sliceSubKind {
case reflect.Struct, reflect.Interface:
if cField.isTimeSubtype || idxField.Type() == reflect.TypeOf(time.Time{}) {
if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, cField.name, true, nil); fieldError != nil {
errs = append(errs, fieldError)
}
continue
}
if idxField.Kind() == reflect.Ptr && idxField.IsNil() {
if strings.Contains(cField.tag, omitempty) {
continue
}
if strings.Contains(cField.tag, required) {
errs = append(errs, &FieldError{
Field: cField.name,
Tag: required,
Value: idxField.Interface(),
Kind: reflect.Ptr,
Type: cField.sliceSubtype,
})
continue
}
}
if structErrors := v.structRecursive(val, current, idxField.Interface()); structErrors != nil {
errs = append(errs, structErrors)
}
default:
if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, cField.name, true, nil); fieldError != nil {
errs = append(errs, fieldError)
}
}
}
return errs
}
// func (v *Validate) traverseSliceOrArrayField(val interface{}, current interface{}, depth uint64, currentDepth uint64, diveTags []*cachedTags, cField *cachedField, valueField reflect.Value) []error {
// for i := 0; i < valueField.Len(); i++ {
// if depth != currentDepth {
// switch cField.SliceSubKind {
// case reflect.Slice, reflect.Array:
// return v.fieldWithNameAndValue(val, current, valueField.Index(i).Interface(), cField.tag, cField.name, false, cField, currentDepth)
// // type FieldError struct {
// // Field string
// // Tag string
// // Kind reflect.Kind
// // Type reflect.Type
// // Param string
// // Value interface{}
// // HasErr bool
// // IsSliceOrArray bool
// // IsMap bool
// // Key interface{}
// // Index uint64
// // SliceOrArrayErrs []*error // counld be FieldError, StructErrors
// // MapErrs map[interface{}]*error // counld be FieldError, StructErrors
// // }
// // result := &FieldError{
// // Field: cField.name,
// // Kind: cField.kind,
// // Type: cField.typ,
// // Value: valueField.Index(i).Interface(),
// // isPlaceholderErr: true,
// // IsSliceOrArray:true,
// // Index:i,
// // SliceOrArrayErrs:
// // }
// // validationErrors.Errors[fieldError.Field] = fieldError
// // // free up memory reference
// // fieldError = nil
// // }
// default:
// panic("attempting to dive deeper, but Kind is not a Slice nor Array")
// }
// }
// // switch cField.SliceSubKind {
// // case reflect.Struct, reflect.Interface:
// // // need to check if required tag and or omitempty just like in struct recirsive
// // if cField.isTimeSubtype || valueField.Type() == reflect.TypeOf(time.Time{}) {
// // if fieldError := v.fieldWithNameAndValue(top, current, valueField.Index(i).Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
// // validationErrors.Errors[fieldError.Field] = fieldError
// // // free up memory reference
// // fieldError = nil
// // }
// // }
// // }
// fmt.Println(valueField.Index(i))
// }
// // fmt.Println(v)
// // for _, item := range arr {
// // }
// return nil
// }
func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, key string, param string, name string) (*FieldError, error) { func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, key string, param string, name string) (*FieldError, error) {
// OK to continue because we checked it's existance before getting into this loop // OK to continue because we checked it's existance before getting into this loop

@ -226,6 +226,38 @@ func AssertMapFieldError(t *testing.T, s map[string]*FieldError, field string, e
EqualSkip(t, 2, val.Tag, expectedTag) EqualSkip(t, 2, val.Tag, expectedTag)
} }
func TestArrayDiveValidation(t *testing.T) {
type Test struct {
Errs []string `validate:"gt=0,dive,required"`
}
test := &Test{
Errs: []string{"ok", "", "ok"},
}
errs := validate.Struct(test)
fmt.Println(errs)
// type TestMap struct {
// Errs *map[int]string `validate:"gt=0,dive,required"`
// }
// m := map[int]string{}
// m[1] = "ok"
// m[2] = ""
// m[3] = "ok"
// testMap := &TestMap{
// Errs: &m,
// }
// errs = validate.Struct(testMap)
// fmt.Println(errs)
}
func TestNilStructPointerValidation(t *testing.T) { func TestNilStructPointerValidation(t *testing.T) {
type Inner struct { type Inner struct {
Data string Data string

Loading…
Cancel
Save