parent
07f2263b41
commit
4bdd703409
@ -1,989 +0,0 @@ |
|||||||
/** |
|
||||||
* Package validator |
|
||||||
* |
|
||||||
* MISC: |
|
||||||
* - anonymous structs - they don't have names so expect the Struct name within StructErrors to be blank |
|
||||||
* |
|
||||||
*/ |
|
||||||
|
|
||||||
package validator |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"reflect" |
|
||||||
"strings" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
"unicode" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
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{} |
|
||||||
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 { |
|
||||||
// Name of the Struct
|
|
||||||
Struct string |
|
||||||
// Struct Field Errors
|
|
||||||
Errors map[string]*FieldError |
|
||||||
// Struct Fields of type struct and their errors
|
|
||||||
// key = Field Name of current struct, but internally Struct will be the actual struct name unless anonymous struct, it will be blank
|
|
||||||
StructErrors map[string]*StructErrors |
|
||||||
} |
|
||||||
|
|
||||||
// This is intended for use in development + debugging and not intended to be a production error message.
|
|
||||||
// it also allows StructErrors to be used as an Error interface
|
|
||||||
func (e *StructErrors) Error() string { |
|
||||||
buff := bytes.NewBufferString(fmt.Sprintf(structErrMsg, e.Struct)) |
|
||||||
|
|
||||||
for _, err := range e.Errors { |
|
||||||
buff.WriteString(err.Error()) |
|
||||||
buff.WriteString("\n") |
|
||||||
} |
|
||||||
|
|
||||||
for _, err := range e.StructErrors { |
|
||||||
buff.WriteString(err.Error()) |
|
||||||
} |
|
||||||
|
|
||||||
return strings.TrimSpace(buff.String()) |
|
||||||
} |
|
||||||
|
|
||||||
// Flatten flattens the StructErrors hierarchical structure into a flat namespace style field name
|
|
||||||
// for those that want/need it
|
|
||||||
func (e *StructErrors) Flatten() map[string]*FieldError { |
|
||||||
|
|
||||||
if e == nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
errs := map[string]*FieldError{} |
|
||||||
|
|
||||||
for _, f := range e.Errors { |
|
||||||
|
|
||||||
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 { |
|
||||||
|
|
||||||
otherErrs := val.Flatten() |
|
||||||
|
|
||||||
for _, f2 := range otherErrs { |
|
||||||
|
|
||||||
f2.Field = fmt.Sprintf("%s.%s", key, f2.Field) |
|
||||||
errs[f2.Field] = f2 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return errs |
|
||||||
} |
|
||||||
|
|
||||||
// Func accepts all values needed for file and cross field validation
|
|
||||||
// top = top level struct when validating by struct otherwise nil
|
|
||||||
// current = current level struct when validating by struct otherwise optional comparison value
|
|
||||||
// f = field value for validation
|
|
||||||
// param = parameter used in validation i.e. gt=0 param would be 0
|
|
||||||
type Func func(top interface{}, current interface{}, f interface{}, param string) bool |
|
||||||
|
|
||||||
// Validate implements the Validate Struct
|
|
||||||
// NOTE: Fields within are not thread safe and that is on purpose
|
|
||||||
// Functions and Tags should all be predifined before use, so subscribe to the philosiphy
|
|
||||||
// or make it thread safe on your end
|
|
||||||
type Validate struct { |
|
||||||
// tagName being used.
|
|
||||||
tagName string |
|
||||||
// validateFuncs is a map of validation functions and the tag keys
|
|
||||||
validationFuncs map[string]Func |
|
||||||
} |
|
||||||
|
|
||||||
// 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, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 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 { |
|
||||||
return errors.New("Function Key cannot be empty") |
|
||||||
} |
|
||||||
|
|
||||||
if f == nil { |
|
||||||
return errors.New("Function cannot be empty") |
|
||||||
} |
|
||||||
|
|
||||||
v.validationFuncs[key] = f |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// 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{}) *StructErrors { |
|
||||||
|
|
||||||
return v.structRecursive(s, s, s) |
|
||||||
} |
|
||||||
|
|
||||||
// structRecursive validates a struct recursivly and passes the top level and current struct around for use in validator functions and returns a struct containing the errors
|
|
||||||
func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}) *StructErrors { |
|
||||||
|
|
||||||
structValue := reflect.ValueOf(s) |
|
||||||
|
|
||||||
if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { |
|
||||||
return v.structRecursive(top, current, structValue.Elem().Interface()) |
|
||||||
} |
|
||||||
|
|
||||||
if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface { |
|
||||||
panic("interface passed for validation is not a struct") |
|
||||||
} |
|
||||||
|
|
||||||
structType := reflect.TypeOf(s) |
|
||||||
|
|
||||||
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} |
|
||||||
} |
|
||||||
|
|
||||||
validationErrors := structPool.Get().(*StructErrors) |
|
||||||
validationErrors.Struct = structName |
|
||||||
|
|
||||||
for i := 0; i < numFields; i++ { |
|
||||||
|
|
||||||
var valueField reflect.Value |
|
||||||
var cField *cachedField |
|
||||||
var typeField reflect.StructField |
|
||||||
|
|
||||||
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) |
|
||||||
|
|
||||||
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 |
|
||||||
} |
|
||||||
|
|
||||||
cField.name = typeField.Name |
|
||||||
cField.kind = valueField.Kind() |
|
||||||
cField.typ = valueField.Type() |
|
||||||
} |
|
||||||
|
|
||||||
// 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 cField.kind { |
|
||||||
|
|
||||||
case reflect.Struct, reflect.Interface: |
|
||||||
|
|
||||||
if !unicode.IsUpper(rune(cField.name[0])) { |
|
||||||
cs.children-- |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
if cField.isTime { |
|
||||||
|
|
||||||
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 |
|
||||||
} |
|
||||||
|
|
||||||
} else { |
|
||||||
|
|
||||||
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[cField.name] = structErrors |
|
||||||
// free up memory map no longer needed
|
|
||||||
structErrors = nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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(), 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 |
|
||||||
} |
|
||||||
|
|
||||||
return validationErrors |
|
||||||
} |
|
||||||
|
|
||||||
// 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, "", true, nil) |
|
||||||
} |
|
||||||
|
|
||||||
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 || tag == "" { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
if strings.Contains(tag, omitempty) && !hasValue(val, current, f, "") { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
valueField = reflect.ValueOf(f) |
|
||||||
|
|
||||||
if cacheField == nil { |
|
||||||
|
|
||||||
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { |
|
||||||
valueField = valueField.Elem() |
|
||||||
f = valueField.Interface() |
|
||||||
} |
|
||||||
|
|
||||||
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 cField.typ != reflect.TypeOf(time.Time{}) { |
|
||||||
panic("Invalid field passed to fieldWithNameAndValue") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if isSingleField { |
|
||||||
fieldsCache.Set(cField.tag, cField.tags) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var fieldErr *FieldError |
|
||||||
var err error |
|
||||||
|
|
||||||
for _, cTag := range cField.tags { |
|
||||||
|
|
||||||
if cTag.isOrVal { |
|
||||||
|
|
||||||
errTag := "" |
|
||||||
|
|
||||||
for _, val := range cTag.keyVals { |
|
||||||
|
|
||||||
fieldErr, err = v.fieldWithNameAndSingleTag(val, current, f, val[0], val[1], name) |
|
||||||
|
|
||||||
if err == nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
errTag += orSeparator + fieldErr.Tag |
|
||||||
} |
|
||||||
|
|
||||||
errTag = strings.TrimLeft(errTag, orSeparator) |
|
||||||
|
|
||||||
fieldErr.Tag = errTag |
|
||||||
fieldErr.Kind = cField.kind |
|
||||||
fieldErr.Type = cField.typ |
|
||||||
|
|
||||||
return fieldErr |
|
||||||
} |
|
||||||
|
|
||||||
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 |
|
||||||
|
|
||||||
return fieldErr |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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) traverseMap(val interface{}, current interface{}, valueField reflect.Value, cField *cachedField) map[interface{}]error { |
|
||||||
|
|
||||||
errs := map[interface{}]error{} |
|
||||||
|
|
||||||
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 |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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 nil, nil |
|
||||||
} |
|
||||||
|
|
||||||
valFunc, ok := v.validationFuncs[key] |
|
||||||
if !ok { |
|
||||||
panic(fmt.Sprintf("Undefined validation function on field %s", name)) |
|
||||||
} |
|
||||||
|
|
||||||
if err := valFunc(val, current, f, param); err { |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
|
|
||||||
return &FieldError{ |
|
||||||
Field: name, |
|
||||||
Tag: key, |
|
||||||
Value: f, |
|
||||||
Param: param, |
|
||||||
}, errors.New(key) |
|
||||||
} |
|
Loading…
Reference in new issue