working on traversing field values

whether it's a struct field or just a regular field
working on a single function to handle both to reduce
checking the same values within struct field recursion or
a regular field; this will also help reduce code complexity
and keep things DRY.
pull/114/head
joeybloggs 10 years ago
parent e42d7b683a
commit 9596b89a26
  1. 212
      validator.go
  2. 14
      validator_test.go

@ -13,10 +13,24 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
"time"
) )
const ( const (
fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" utf8HexComma = "0x2C"
tagSeparator = ","
orSeparator = "|"
tagKeySeparator = "="
structOnlyTag = "structonly"
omitempty = "omitempty"
skipValidationTag = "-"
fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag"
invaldField = "Invalid field passed to traverseField"
)
var (
timeType = reflect.TypeOf(time.Time{})
timePtrType = reflect.TypeOf(&time.Time{})
) )
// Validate implements the Validate Struct // Validate implements the Validate Struct
@ -66,10 +80,10 @@ func (ve ValidationErrors) Error() string {
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{}
// IsPlaceholderErr bool // IsPlaceholderErr bool
// IsSliceOrArray bool // IsSliceOrArray bool
// IsMap bool // IsMap bool
@ -94,7 +108,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
errs := map[string]*FieldError{} errs := map[string]*FieldError{}
sv := reflect.ValueOf(current) sv := reflect.ValueOf(current)
v.structRecursive(sv, sv, "", errs) v.tranverseStruct(sv, sv, sv, "", errs)
if len(errs) == 0 { if len(errs) == 0 {
return nil return nil
@ -103,9 +117,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
return errs return errs
} }
// func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}, errs map[string]*FieldError) { func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors) {
func (v *Validate) structRecursive(top reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors) {
if current.Kind() == reflect.Ptr && !current.IsNil() { if current.Kind() == reflect.Ptr && !current.IsNil() {
current = current.Elem() current = current.Elem()
@ -115,58 +127,136 @@ func (v *Validate) structRecursive(top reflect.Value, current reflect.Value, err
panic("value passed for validation is not a struct") panic("value passed for validation is not a struct")
} }
// errs[errPrefix+"Name"] = &FieldError{Field: "Name", Tag: "required"} typ := current.Type()
errPrefix += typ.Name() + "."
numFields := current.NumField()
// if depth < 3 { for i := 0; i < numFields; i++ {
// v.structRecursive(top, current, errs) v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, typ.Field(i).Tag.Get(v.config.TagName))
// } }
} }
// // Struct validates a struct, even it's nested structs, and returns a struct containing the errors func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string) {
// // NOTE: Nested Arrays, or Maps of structs do not get validated only the Array or Map itself; the reason is that there is no good
// // way to represent or report which struct within the array has the error, besides can validate the struct prior to adding it to if tag == skipValidationTag {
// // the Array or Map. return
// func (v *Validate) Struct(s interface{}) map[string]*FieldError { }
// // var err *FieldError kind := current.Kind()
// errs := map[string]*FieldError{}
// errchan := make(chan *FieldError) if kind == reflect.Ptr && !current.IsNil() {
// done := make(chan bool) current = current.Elem()
// // wg := &sync.WaitGroup{} kind = current.Kind()
}
// go v.structRecursive(s, s, s, 0, errchan, done)
typ := current.Type()
// LOOP:
// for { // this also allows for tags 'required' and 'omitempty' to be used on
// select { // nested struct fields because when len(tags) > 0 below and the value is nil
// case err := <-errchan: // then required failes and we check for omitempty just before that
// errs[err.Field] = err if (kind == reflect.Ptr || kind == reflect.Interface) && current.IsNil() {
// // fmt.Println(err)
// case <-done: if strings.Contains(tag, omitempty) {
// // fmt.Println("All Done") return
// break LOOP }
// }
// } tags := strings.Split(tag, tagSeparator)
// return errs if len(tags) > 0 {
// }
var param string
// func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}, depth int, errs chan *FieldError, done chan bool) { vals := strings.SplitN(tags[0], tagKeySeparator, 2)
// errs <- &FieldError{Field: "Name"} if len(vals) > 1 {
param = vals[1]
// if depth < 1 { }
// // wg.Add(1)
// v.structRecursive(s, s, s, depth+1, errs, done) errs[errPrefix+typ.Name()] = &FieldError{
// } Field: typ.Name(),
Tag: vals[0],
// // wg.Wait() Param: param,
Value: current.Interface(),
// if depth == 0 { Kind: kind,
// // wg.Wait() Type: typ,
// done <- true }
// // return
// } else { return
// // wg.Done() }
// } }
// }
switch kind {
case reflect.Invalid:
panic(invaldField)
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()
}
if kind != reflect.Struct {
goto FALLTHROUGH
}
}
if typ != timeType && typ != timePtrType {
if isStructField {
// required passed validationa above so stop here
// if only validating the structs existance.
if strings.Contains(tag, structOnlyTag) {
return
}
v.tranverseStruct(topStruct, current, current, errPrefix, errs)
return
}
panic(invaldField)
}
FALLTHROUGH:
fallthrough
default:
if len(tag) == 0 {
return
}
}
// for _, t := range strings.Split(tag, tagSeparator) {
// if t == diveTag {
// cField.dive = true
// cField.diveTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",")
// break
// }
// orVals := strings.Split(t, orSeparator)
// cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))}
// cField.tags = append(cField.tags, cTag)
// for i, val := range orVals {
// vals := strings.SplitN(val, tagKeySeparator, 2)
// key := strings.TrimSpace(vals[0])
// if len(key) == 0 {
// panic(fmt.Sprintf("Invalid validation tag on field %s", name))
// }
// param := ""
// if len(vals) > 1 {
// param = strings.Replace(vals[1], utf8HexComma, ",", -1)
// }
// cTag.keyVals[i] = []string{key, param}
// }
// }
}

@ -204,11 +204,21 @@ func PanicMatchesSkip(t *testing.T, skip int, fn func(), matches string) {
func TestValidation(t *testing.T) { func TestValidation(t *testing.T) {
type Test struct { type Inner struct {
Name string Name string
} }
tst := &Test{Name: "Dean"} type Test struct {
// Name string `validate:"required"`
Inner *Inner `validate:"required"`
}
inner := &Inner{
Name: "",
}
// tst := &Test{Name: "Dean"}
tst := &Test{Inner: inner}
errs := validate.Struct(tst) errs := validate.Struct(tst)

Loading…
Cancel
Save