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"
"reflect"
"strings"
"time"
)
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
@ -66,10 +80,10 @@ func (ve ValidationErrors) Error() string {
type FieldError struct {
Field string
Tag string
// Kind reflect.Kind
// Type reflect.Type
// Param string
// Value interface{}
Kind reflect.Kind
Type reflect.Type
Param string
Value interface{}
// IsPlaceholderErr bool
// IsSliceOrArray bool
// IsMap bool
@ -94,7 +108,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
errs := map[string]*FieldError{}
sv := reflect.ValueOf(current)
v.structRecursive(sv, sv, "", errs)
v.tranverseStruct(sv, sv, sv, "", errs)
if len(errs) == 0 {
return nil
@ -103,9 +117,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
return errs
}
// func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}, errs map[string]*FieldError) {
func (v *Validate) structRecursive(top reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors) {
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors) {
if current.Kind() == reflect.Ptr && !current.IsNil() {
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")
}
// errs[errPrefix+"Name"] = &FieldError{Field: "Name", Tag: "required"}
typ := current.Type()
errPrefix += typ.Name() + "."
numFields := current.NumField()
// if depth < 3 {
// v.structRecursive(top, current, errs)
// }
for i := 0; i < numFields; i++ {
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
// // 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
// // the Array or Map.
// func (v *Validate) Struct(s interface{}) map[string]*FieldError {
// // var err *FieldError
// errs := map[string]*FieldError{}
// errchan := make(chan *FieldError)
// done := make(chan bool)
// // wg := &sync.WaitGroup{}
// go v.structRecursive(s, s, s, 0, errchan, done)
// LOOP:
// for {
// select {
// case err := <-errchan:
// errs[err.Field] = err
// // fmt.Println(err)
// case <-done:
// // fmt.Println("All Done")
// break LOOP
// }
// }
// return errs
// }
// func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}, depth int, errs chan *FieldError, done chan bool) {
// errs <- &FieldError{Field: "Name"}
// if depth < 1 {
// // wg.Add(1)
// v.structRecursive(s, s, s, depth+1, errs, done)
// }
// // wg.Wait()
// if depth == 0 {
// // wg.Wait()
// done <- true
// // return
// } else {
// // wg.Done()
// }
// }
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string) {
if tag == skipValidationTag {
return
}
kind := current.Kind()
if kind == reflect.Ptr && !current.IsNil() {
current = current.Elem()
kind = current.Kind()
}
typ := current.Type()
// 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() {
if strings.Contains(tag, omitempty) {
return
}
tags := strings.Split(tag, tagSeparator)
if len(tags) > 0 {
var param string
vals := strings.SplitN(tags[0], tagKeySeparator, 2)
if len(vals) > 1 {
param = vals[1]
}
errs[errPrefix+typ.Name()] = &FieldError{
Field: typ.Name(),
Tag: vals[0],
Param: param,
Value: current.Interface(),
Kind: kind,
Type: typ,
}
return
}
}
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) {
type Test struct {
type Inner struct {
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)

Loading…
Cancel
Save