@ -14,38 +14,230 @@ import (
"fmt"
"reflect"
"strings"
"sync"
"time"
"unicode"
)
const (
tagSeparator = ","
orSeparator = "|"
noValidationTag = "-"
tagKeySeparator = "="
structOnlyTag = "structonly"
omitempty = "omitempty"
fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag"
structErrMsg = "Struct:%s\n"
utf8HexComma = "0x2C"
tagSeparator = ","
orSeparator = "|"
noValidationTag = "-"
tagKeySeparator = "="
structOnlyTag = "structonly"
omitempty = "omitempty"
required = "required"
fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag"
sliceErrMsg = "Field validation for \"%s\" failed at index \"%d\" with error(s): %s"
mapErrMsg = "Field validation for \"%s\" failed on key \"%v\" with error(s): %s"
structErrMsg = "Struct:%s\n"
diveTag = "dive"
arrayIndexFieldName = "%s[%d]"
mapIndexFieldName = "%s[%v]"
)
var structPool * sync . Pool
// returns new *StructErrors to the pool
func newStructErrors ( ) interface { } {
return & StructErrors {
Errors : map [ string ] * FieldError { } ,
StructErrors : map [ string ] * StructErrors { } ,
}
}
type cachedTags struct {
keyVals [ ] [ ] string
isOrVal bool
}
type cachedField struct {
index int
name string
tags [ ] * cachedTags
tag string
kind reflect . Kind
typ reflect . Type
isTime bool
isSliceOrArray bool
isMap bool
isTimeSubtype bool
sliceSubtype reflect . Type
mapSubtype reflect . Type
sliceSubKind reflect . Kind
mapSubKind reflect . Kind
dive bool
diveTag string
}
type cachedStruct struct {
children int
name string
kind reflect . Kind
fields [ ] * cachedField
}
type structsCacheMap struct {
lock sync . RWMutex
m map [ reflect . Type ] * cachedStruct
}
func ( s * structsCacheMap ) Get ( key reflect . Type ) ( * cachedStruct , bool ) {
s . lock . RLock ( )
defer s . lock . RUnlock ( )
value , ok := s . m [ key ]
return value , ok
}
func ( s * structsCacheMap ) Set ( key reflect . Type , value * cachedStruct ) {
s . lock . Lock ( )
defer s . lock . Unlock ( )
s . m [ key ] = value
}
var structCache = & structsCacheMap { m : map [ reflect . Type ] * cachedStruct { } }
type fieldsCacheMap struct {
lock sync . RWMutex
m map [ string ] [ ] * cachedTags
}
func ( s * fieldsCacheMap ) Get ( key string ) ( [ ] * cachedTags , bool ) {
s . lock . RLock ( )
defer s . lock . RUnlock ( )
value , ok := s . m [ key ]
return value , ok
}
func ( s * fieldsCacheMap ) Set ( key string , value [ ] * cachedTags ) {
s . lock . Lock ( )
defer s . lock . Unlock ( )
s . m [ key ] = value
}
var fieldsCache = & fieldsCacheMap { m : map [ string ] [ ] * cachedTags { } }
// FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation
type FieldError struct {
Field string
Tag string
Kind reflect . Kind
Type reflect . Type
Param string
Value interface { }
Field string
Tag string
Kind reflect . Kind
Type reflect . Type
Param string
Value interface { }
IsPlaceholderErr bool
IsSliceOrArray bool
IsMap bool
SliceOrArrayErrs map [ int ] 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.
// it also allows FieldError to be used as an Error interface
func ( e * FieldError ) Error ( ) string {
if e . IsPlaceholderErr {
buff := bytes . NewBufferString ( "" )
if e . IsSliceOrArray {
for j , err := range e . SliceOrArrayErrs {
buff . WriteString ( "\n" )
buff . WriteString ( fmt . Sprintf ( sliceErrMsg , e . Field , j , "\n" + err . Error ( ) ) )
}
} else if e . IsMap {
for key , err := range e . MapErrs {
buff . WriteString ( fmt . Sprintf ( mapErrMsg , e . Field , key , "\n" + err . Error ( ) ) )
}
}
return strings . TrimSpace ( buff . String ( ) )
}
return fmt . Sprintf ( fieldErrMsg , e . Field , e . Tag )
}
// Flatten flattens the FieldError hierarchical structure into a flat namespace style field name
// for those that want/need it.
// This is now needed because of the new dive functionality
func ( e * FieldError ) Flatten ( ) map [ string ] * FieldError {
errs := map [ string ] * FieldError { }
if e . IsPlaceholderErr {
if e . IsSliceOrArray {
for key , err := range e . SliceOrArrayErrs {
fe , ok := err . ( * FieldError )
if ok {
if flat := fe . Flatten ( ) ; flat != nil && len ( flat ) > 0 {
for k , v := range flat {
if fe . IsPlaceholderErr {
errs [ fmt . Sprintf ( "[%#v]%s" , key , k ) ] = v
} else {
errs [ fmt . Sprintf ( "[%#v]" , key ) ] = v
}
}
}
} else {
se := err . ( * StructErrors )
if flat := se . Flatten ( ) ; flat != nil && len ( flat ) > 0 {
for k , v := range flat {
errs [ fmt . Sprintf ( "[%#v].%s.%s" , key , se . Struct , k ) ] = v
}
}
}
}
}
if e . IsMap {
for key , err := range e . MapErrs {
fe , ok := err . ( * FieldError )
if ok {
if flat := fe . Flatten ( ) ; flat != nil && len ( flat ) > 0 {
for k , v := range flat {
if fe . IsPlaceholderErr {
errs [ fmt . Sprintf ( "[%#v]%s" , key , k ) ] = v
} else {
errs [ fmt . Sprintf ( "[%#v]" , key ) ] = v
}
}
}
} else {
se := err . ( * StructErrors )
if flat := se . Flatten ( ) ; flat != nil && len ( flat ) > 0 {
for k , v := range flat {
errs [ fmt . Sprintf ( "[%#v].%s.%s" , key , se . Struct , k ) ] = v
}
}
}
}
}
return errs
}
errs [ e . Field ] = e
return errs
}
// StructErrors is hierarchical list of field and struct validation errors
// for a non hierarchical representation please see the Flatten method for StructErrors
type StructErrors struct {
@ -72,7 +264,7 @@ func (e *StructErrors) Error() string {
buff . WriteString ( err . Error ( ) )
}
return buff . String ( )
return strings . TrimSpace ( buff . String ( ) )
}
// Flatten flattens the StructErrors hierarchical structure into a flat namespace style field name
@ -87,7 +279,17 @@ func (e *StructErrors) Flatten() map[string]*FieldError {
for _ , f := range e . Errors {
errs [ f . Field ] = f
if flat := f . Flatten ( ) ; flat != nil && len ( flat ) > 0 {
for k , fe := range flat {
if f . IsPlaceholderErr {
errs [ f . Field + k ] = fe
} else {
errs [ k ] = fe
}
}
}
}
for key , val := range e . StructErrors {
@ -124,6 +326,9 @@ type Validate struct {
// New creates a new Validate instance for use.
func New ( tagName string , funcs map [ string ] Func ) * Validate {
structPool = & sync . Pool { New : newStructErrors }
return & Validate {
tagName : tagName ,
validationFuncs : funcs ,
@ -132,12 +337,23 @@ func New(tagName string, funcs map[string]Func) *Validate {
// SetTag sets tagName of the Validator to one of your choosing after creation
// perhaps to dodge a tag name conflict in a specific section of code
// NOTE: this method is not thread-safe
func ( v * Validate ) SetTag ( tagName string ) {
v . tagName = tagName
}
// SetMaxStructPoolSize sets the struct pools max size. this may be usefull for fine grained
// performance tuning towards your application, however, the default should be fine for
// nearly all cases. only increase if you have a deeply nested struct structure.
// NOTE: this method is not thread-safe
// NOTE: this is only here to keep compatibility with v5, in v6 the method will be removed
func ( v * Validate ) SetMaxStructPoolSize ( max int ) {
structPool = & sync . Pool { New : newStructErrors }
}
// AddFunction adds a validation Func to a Validate's map of validators denoted by the key
// NOTE: if the key already exists, it will get replaced.
// NOTE: this method is not thread-safe
func ( v * Validate ) AddFunction ( key string , f Func ) error {
if len ( key ) == 0 {
@ -176,47 +392,84 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
}
structType := reflect . TypeOf ( s )
structName := structType . Name ( )
validationErrors := & StructErrors {
Struct : structName ,
Errors : map [ string ] * FieldError { } ,
StructErrors : map [ string ] * StructErrors { } ,
var structName string
var numFields int
var cs * cachedStruct
var isCached bool
cs , isCached = structCache . Get ( structType )
if isCached {
structName = cs . name
numFields = cs . children
} else {
structName = structType . Name ( )
numFields = structValue . NumField ( )
cs = & cachedStruct { name : structName , children : numFields }
}
var numFields = structValue . NumField ( )
validationErrors := structPool . Get ( ) . ( * StructErrors )
validationErrors . Struct = structName
for i := 0 ; i < numFields ; i ++ {
valueField := structValue . Field ( i )
typeField := structType . Field ( i )
var valueField reflect . Value
var cField * cachedField
var typeField reflect . StructField
if valueField . Kind ( ) == reflect . Ptr && ! valueField . IsNil ( ) {
valueField = valueField . Elem ( )
}
if isCached {
cField = cs . fields [ i ]
valueField = structValue . Field ( cField . index )
if valueField . Kind ( ) == reflect . Ptr && ! valueField . IsNil ( ) {
valueField = valueField . Elem ( )
}
} else {
valueField = structValue . Field ( i )
tag := typeField . Tag . Get ( v . tagName )
if valueField . Kind ( ) == reflect . Ptr && ! valueField . IsNil ( ) {
valueField = valueField . Elem ( )
}
typeField = structType . Field ( i )
cField = & cachedField { index : i , tag : typeField . Tag . Get ( v . tagName ) , isTime : ( valueField . Type ( ) == reflect . TypeOf ( time . Time { } ) || valueField . Type ( ) == reflect . TypeOf ( & time . Time { } ) ) }
if cField . tag == noValidationTag {
cs . children --
continue
}
// if no validation and not a struct (which may containt fields for validation)
if cField . tag == "" && ( ( valueField . Kind ( ) != reflect . Struct && valueField . Kind ( ) != reflect . Interface ) || valueField . Type ( ) == reflect . TypeOf ( time . Time { } ) ) {
cs . children --
continue
}
if tag == noValidationTag {
continue
cField . name = typeField . Name
cField . kind = valueField . Kind ( )
cField . typ = valueField . Type ( )
}
// if no validation and not a struct (which may containt fields for validation)
if tag == "" && ( ( valueField . Kind ( ) != reflect . Struct && valueField . Kind ( ) != reflect . Interface ) || valueField . Type ( ) == reflect . TypeOf ( time . Time { } ) ) {
continue
// this can happen if the first cache value was nil
// but the second actually has a value
if cField . kind == reflect . Ptr {
cField . kind = valueField . Kind ( )
}
switch valueField . Kind ( ) {
switch cField . kind {
case reflect . Struct , reflect . Interface :
if ! unicode . IsUpper ( rune ( typeField . Name [ 0 ] ) ) {
if ! unicode . IsUpper ( rune ( cField . name [ 0 ] ) ) {
cs . children --
continue
}
if value Field. Type ( ) == reflect . TypeOf ( t ime . Time { } ) {
if c Field. is Time {
if fieldError := v . fieldWithNameAndValue ( top , current , valueField . Interface ( ) , typeField . Name , tag ) ; fieldError != nil {
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
@ -224,28 +477,116 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
} else {
if strings . Contains ( tag , structOnlyTag ) {
if strings . Contains ( cField . tag , structOnlyTag ) {
cs . children --
continue
}
if ( valueField . Kind ( ) == reflect . Ptr || cField . kind == reflect . Interface ) && valueField . IsNil ( ) {
if strings . Contains ( cField . tag , omitempty ) {
goto CACHEFIELD
}
tags := strings . Split ( cField . tag , tagSeparator )
if len ( tags ) > 0 {
var param string
vals := strings . SplitN ( tags [ 0 ] , tagKeySeparator , 2 )
if len ( vals ) > 1 {
param = vals [ 1 ]
}
validationErrors . Errors [ cField . name ] = & FieldError {
Field : cField . name ,
Tag : vals [ 0 ] ,
Param : param ,
Value : valueField . Interface ( ) ,
Kind : valueField . Kind ( ) ,
Type : valueField . Type ( ) ,
}
goto CACHEFIELD
}
}
// if we get here, the field is interface and could be a struct or a field
// and we need to check the inner type and validate
if cField . kind == reflect . Interface {
valueField = valueField . Elem ( )
if valueField . Kind ( ) == reflect . Ptr && ! valueField . IsNil ( ) {
valueField = valueField . Elem ( )
}
if valueField . Kind ( ) == reflect . Struct {
goto VALIDATESTRUCT
}
// sending nil for cField as it was type interface and could be anything
// each time and so must be calculated each time and can't be cached reliably
if fieldError := v . fieldWithNameAndValue ( top , current , valueField . Interface ( ) , cField . tag , cField . name , false , nil ) ; fieldError != nil {
validationErrors . Errors [ fieldError . Field ] = fieldError
// free up memory reference
fieldError = nil
}
goto CACHEFIELD
}
VALIDATESTRUCT :
if structErrors := v . structRecursive ( top , valueField . Interface ( ) , valueField . Interface ( ) ) ; structErrors != nil {
validationErrors . StructErrors [ typeField . Name ] = structErrors
validationErrors . StructErrors [ cField . n ame] = structErrors
// free up memory map no longer needed
structErrors = nil
}
}
default :
case reflect . Slice , reflect . Array :
cField . isSliceOrArray = true
cField . sliceSubtype = cField . typ . Elem ( )
cField . isTimeSubtype = ( cField . sliceSubtype == reflect . TypeOf ( time . Time { } ) || cField . sliceSubtype == reflect . TypeOf ( & time . Time { } ) )
cField . sliceSubKind = cField . sliceSubtype . Kind ( )
if fieldError := v . fieldWithNameAndValue ( top , current , valueField . Interface ( ) , typeField . Name , tag ) ; fieldError != nil {
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 . 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 {
validationErrors . Errors [ fieldError . Field ] = fieldError
// free up memory reference
fieldError = nil
}
}
CACHEFIELD :
if ! isCached {
cs . fields = append ( cs . fields , cField )
}
}
structCache . Set ( structType , cs )
if len ( validationErrors . Errors ) == 0 && len ( validationErrors . StructErrors ) == 0 {
structPool . Put ( validationErrors )
return nil
}
@ -254,20 +595,22 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
// Field allows validation of a single field, still using tag style validation to check multiple errors
func ( v * Validate ) Field ( f interface { } , tag string ) * FieldError {
return v . FieldWithValue ( nil , f , tag )
}
// FieldWithValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors
func ( v * Validate ) FieldWithValue ( val interface { } , f interface { } , tag string ) * FieldError {
return v . fieldWithNameAndValue ( nil , val , f , "" , tag )
return v . fieldWithNameAndValue ( nil , val , f , tag , "" , true , nil )
}
func ( v * Validate ) fieldWithNameAndValue ( val interface { } , current interface { } , f interface { } , name string , tag string ) * FieldError {
func ( v * Validate ) fieldWithNameAndValue ( val interface { } , current interface { } , f interface { } , tag string , name string , isSingleField bool , cacheField * cachedField ) * FieldError {
var cField * cachedField
var isCached 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
if tag == noValidationTag {
if tag == noValidationTag || tag == "" {
return nil
}
@ -275,87 +618,357 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
return nil
}
valueField := reflect . ValueOf ( f )
fieldKind := valueField . Kind ( )
valueField = reflect . ValueOf ( f )
if fieldKind == reflect . Ptr && ! valueField . IsNil ( ) {
return v . fieldWithNameAndValue ( val , current , valueField . Elem ( ) . Interface ( ) , name , tag )
}
if cacheField == nil {
fieldType := valueField . Type ( )
if valueField . Kind ( ) == reflect . Ptr && ! valueField . IsNil ( ) {
valueField = valueField . Elem ( )
f = valueField . Interface ( )
}
switch fieldKind {
cField = & cachedField { name : name , kind : valueField . Kind ( ) , tag : tag , typ : valueField . Type ( ) }
switch cField . kind {
case reflect . Slice , reflect . Array :
isSingleField = false // cached tags mean nothing because it will be split up while diving
cField . isSliceOrArray = true
cField . sliceSubtype = cField . typ . Elem ( )
cField . isTimeSubtype = ( cField . sliceSubtype == reflect . TypeOf ( time . Time { } ) || cField . sliceSubtype == reflect . TypeOf ( & time . Time { } ) )
cField . sliceSubKind = cField . sliceSubtype . Kind ( )
case reflect . Map :
isSingleField = false // cached tags mean nothing because it will be split up while diving
cField . isMap = true
cField . mapSubtype = cField . typ . Elem ( )
cField . isTimeSubtype = ( cField . mapSubtype == reflect . TypeOf ( time . Time { } ) || cField . mapSubtype == reflect . TypeOf ( & time . Time { } ) )
cField . mapSubKind = cField . mapSubtype . Kind ( )
}
} else {
cField = cacheField
}
switch cField . kind {
case reflect . Struct , reflect . Interface , reflect . Invalid :
if fieldType != reflect . TypeOf ( time . Time { } ) {
panic ( "Invalid field passed to ValidateFieldWithTag" )
if cField . typ != reflect . TypeOf ( time . Time { } ) {
panic ( "Invalid field passed to fieldWithNameAndValue " )
}
}
var valErr * FieldError
var err error
valTags := strings . Split ( tag , tagSeparator )
if len ( cField . tags ) == 0 {
if isSingleField {
cField . tags , isCached = fieldsCache . Get ( tag )
}
if ! isCached {
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 }
}
}
for _ , valTag := range valTags {
if isSingleField {
fieldsCache . Set ( cField . tag , cField . tags )
}
}
}
var fieldErr * FieldError
var err error
orVals := strings . Split ( valTag , orSeparator )
for _ , cTag := range cField . tags {
if len ( orVals ) > 1 {
if cTag . isOrVal {
errTag := ""
for _ , val := range orVals {
for _ , val := range cTag . key Vals {
valErr , err = v . fieldWithNameAndSingleTag ( val , current , f , name , val )
field Err, err = v . fieldWithNameAndSingleTag ( val , current , f , val [ 0 ] , val [ 1 ] , name )
if err == nil {
return nil
}
errTag += orSeparator + valErr . Tag
errTag += orSeparator + fieldErr . Tag
}
errTag = strings . TrimLeft ( errTag , orSeparator )
valErr . Tag = errTag
valErr . Kind = fieldKind
fieldErr . Tag = errTag
fieldErr . Kind = cField . kind
fieldErr . Type = cField . typ
return valErr
return field Err
}
if valErr , err = v . fieldWithNameAndSingleTag ( val , current , f , name , valTag ) ; err != nil {
if fieldErr , err = v . fieldWithNameAndSingleTag ( val , current , f , cTag . keyVals [ 0 ] [ 0 ] , cTag . keyVals [ 0 ] [ 1 ] , name ) ; err != nil {
fieldErr . Kind = cField . kind
fieldErr . Type = cField . typ
valErr . Kind = valueField . Kind ( )
valErr . Type = fieldType
return fieldErr
}
}
return valErr
if cField . dive {
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 ,
SliceOrArrayErrs : errs ,
}
}
} else if cField . isMap {
if errs := v . traverseMap ( val , current , valueField , cField ) ; errs != nil && len ( errs ) > 0 {
return & FieldError {
Field : cField . name ,
Kind : cField . kind ,
Type : cField . typ ,
Value : f ,
IsPlaceholderErr : true ,
IsMap : true ,
MapErrs : errs ,
}
}
} else {
// throw error, if not a slice or map then should not have gotten here
panic ( "dive error! can't dive on a non slice or map" )
}
}
return nil
}
func ( v * Validate ) fieldWithNameAndSingleTag ( val interface { } , current interface { } , f interface { } , name string , valTag string ) ( * FieldError , error ) {
func ( v * Validate ) traverseMap ( val interface { } , current interface { } , valueField reflect . Value , cField * cachedField ) map [ interface { } ] error {
vals := strings . Split ( valTag , tagKeySeparator )
key := strings . TrimSpace ( vals [ 0 ] )
errs := map [ interface { } ] error { }
if len ( key ) == 0 {
panic ( fmt . Sprintf ( "Invalid validation tag on field %s" , name ) )
for _ , key := range valueField . MapKeys ( ) {
idxField := valueField . MapIndex ( key )
if cField . mapSubKind == reflect . Ptr && ! idxField . IsNil ( ) {
idxField = idxField . Elem ( )
cField . mapSubKind = idxField . Kind ( )
}
switch cField . mapSubKind {
case reflect . Struct , reflect . Interface :
if cField . isTimeSubtype {
if fieldError := v . fieldWithNameAndValue ( val , current , idxField . Interface ( ) , cField . diveTag , fmt . Sprintf ( mapIndexFieldName , cField . name , key . Interface ( ) ) , false , nil ) ; fieldError != nil {
errs [ key . Interface ( ) ] = fieldError
}
continue
}
if ( idxField . Kind ( ) == reflect . Ptr || idxField . Kind ( ) == reflect . Interface ) && idxField . IsNil ( ) {
if strings . Contains ( cField . diveTag , omitempty ) {
continue
}
tags := strings . Split ( cField . diveTag , tagSeparator )
if len ( tags ) > 0 {
var param string
vals := strings . SplitN ( tags [ 0 ] , tagKeySeparator , 2 )
if len ( vals ) > 1 {
param = vals [ 1 ]
}
errs [ key . Interface ( ) ] = & FieldError {
Field : fmt . Sprintf ( mapIndexFieldName , cField . name , key . Interface ( ) ) ,
Tag : vals [ 0 ] ,
Param : param ,
Value : idxField . Interface ( ) ,
Kind : idxField . Kind ( ) ,
Type : cField . mapSubtype ,
}
}
continue
}
// if we get here, the field is interface and could be a struct or a field
// and we need to check the inner type and validate
if idxField . Kind ( ) == reflect . Interface {
idxField = idxField . Elem ( )
if idxField . Kind ( ) == reflect . Ptr && ! idxField . IsNil ( ) {
idxField = idxField . Elem ( )
}
if idxField . Kind ( ) == reflect . Struct {
goto VALIDATESTRUCT
}
// sending nil for cField as it was type interface and could be anything
// each time and so must be calculated each time and can't be cached reliably
if fieldError := v . fieldWithNameAndValue ( val , current , idxField . Interface ( ) , cField . diveTag , fmt . Sprintf ( mapIndexFieldName , cField . name , key . Interface ( ) ) , false , nil ) ; fieldError != nil {
errs [ key . Interface ( ) ] = fieldError
}
continue
}
VALIDATESTRUCT :
if structErrors := v . structRecursive ( val , current , idxField . Interface ( ) ) ; structErrors != nil {
errs [ key . Interface ( ) ] = structErrors
}
default :
if fieldError := v . fieldWithNameAndValue ( val , current , idxField . Interface ( ) , cField . diveTag , fmt . Sprintf ( mapIndexFieldName , cField . name , key . Interface ( ) ) , false , nil ) ; fieldError != nil {
errs [ key . Interface ( ) ] = fieldError
}
}
}
valErr := & FieldError {
Field : name ,
Tag : key ,
Value : f ,
Param : "" ,
return errs
}
func ( v * Validate ) traverseSliceOrArray ( val interface { } , current interface { } , valueField reflect . Value , cField * cachedField ) map [ int ] error {
errs := map [ int ] error { }
for i := 0 ; i < valueField . Len ( ) ; i ++ {
idxField := valueField . Index ( i )
if cField . sliceSubKind == reflect . Ptr && ! idxField . IsNil ( ) {
idxField = idxField . Elem ( )
cField . sliceSubKind = idxField . Kind ( )
}
switch cField . sliceSubKind {
case reflect . Struct , reflect . Interface :
if cField . isTimeSubtype {
if fieldError := v . fieldWithNameAndValue ( val , current , idxField . Interface ( ) , cField . diveTag , fmt . Sprintf ( arrayIndexFieldName , cField . name , i ) , false , nil ) ; fieldError != nil {
errs [ i ] = fieldError
}
continue
}
if ( idxField . Kind ( ) == reflect . Ptr || idxField . Kind ( ) == reflect . Interface ) && idxField . IsNil ( ) {
if strings . Contains ( cField . diveTag , omitempty ) {
continue
}
tags := strings . Split ( cField . diveTag , tagSeparator )
if len ( tags ) > 0 {
var param string
vals := strings . SplitN ( tags [ 0 ] , tagKeySeparator , 2 )
if len ( vals ) > 1 {
param = vals [ 1 ]
}
errs [ i ] = & FieldError {
Field : fmt . Sprintf ( arrayIndexFieldName , cField . name , i ) ,
Tag : vals [ 0 ] ,
Param : param ,
Value : idxField . Interface ( ) ,
Kind : idxField . Kind ( ) ,
Type : cField . sliceSubtype ,
}
}
continue
}
// if we get here, the field is interface and could be a struct or a field
// and we need to check the inner type and validate
if idxField . Kind ( ) == reflect . Interface {
idxField = idxField . Elem ( )
if idxField . Kind ( ) == reflect . Ptr && ! idxField . IsNil ( ) {
idxField = idxField . Elem ( )
}
if idxField . Kind ( ) == reflect . Struct {
goto VALIDATESTRUCT
}
// sending nil for cField as it was type interface and could be anything
// each time and so must be calculated each time and can't be cached reliably
if fieldError := v . fieldWithNameAndValue ( val , current , idxField . Interface ( ) , cField . diveTag , fmt . Sprintf ( arrayIndexFieldName , cField . name , i ) , false , nil ) ; fieldError != nil {
errs [ i ] = fieldError
}
continue
}
VALIDATESTRUCT :
if structErrors := v . structRecursive ( val , current , idxField . Interface ( ) ) ; structErrors != nil {
errs [ i ] = structErrors
}
default :
if fieldError := v . fieldWithNameAndValue ( val , current , idxField . Interface ( ) , cField . diveTag , fmt . Sprintf ( arrayIndexFieldName , cField . name , i ) , false , nil ) ; fieldError != nil {
errs [ i ] = fieldError
}
}
}
return errs
}
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
if key == omitempty {
return valErr , nil
return nil , nil
}
valFunc , ok := v . validationFuncs [ key ]
@ -363,15 +976,14 @@ func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{
panic ( fmt . Sprintf ( "Undefined validation function on field %s" , name ) )
}
param := ""
if len ( vals ) > 1 {
param = strings . TrimSpace ( vals [ 1 ] )
}
if err := valFunc ( val , current , f , param ) ; ! err {
valErr . Param = param
return valErr , errors . New ( key )
if err := valFunc ( val , current , f , param ) ; err {
return nil , nil
}
return valErr , nil
return & FieldError {
Field : name ,
Tag : key ,
Value : f ,
Param : param ,
} , errors . New ( key )
}