You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
990 lines
26 KiB
990 lines
26 KiB
9 years ago
|
/**
|
||
|
* 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)
|
||
|
}
|