From 9596b89a26073eefd34294b1ed706e15fc92bf67 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 12 Jul 2015 21:57:16 -0400 Subject: [PATCH] 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. --- validator.go | 212 +++++++++++++++++++++++++++++++++------------- validator_test.go | 14 ++- 2 files changed, 163 insertions(+), 63 deletions(-) diff --git a/validator.go b/validator.go index 96039d8..abb4dda 100644 --- a/validator.go +++ b/validator.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} + // } + // } +} diff --git a/validator_test.go b/validator_test.go index b31c29d..a236e43 100644 --- a/validator_test.go +++ b/validator_test.go @@ -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)