@ -13,10 +13,24 @@ import (
"fmt"
"reflect"
"strings"
"time"
)
const (
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}
// }
// }
}