Merge pull request #83 from bluesuncorp/v5-development

Merge latest dive changes for slice, array and maps
pull/84/head v5.9
Dean Karn 10 years ago
commit 740b7d0daa
  1. 19
      doc.go
  2. 261
      validator.go
  3. 483
      validator_test.go

@ -173,6 +173,25 @@ Here is a list of the current built in validators:
such as min or max won't run, but if a value is set validation will run. such as min or max won't run, but if a value is set validation will run.
(Usage: omitempty) (Usage: omitempty)
dive
This tells the validator to dive into a slice, array or map and validate that
level of the slice, array or map with the validation tags that follow.
Multidimensional nesting is also supported, each level you with to dive will
require another dive tag. (Usage: dive)
Example: [][]string with validation tag "gt=0,dive,len=1,dive,required"
gt=0 will be applied to []
len=1 will be applied to []string
required will be applied to string
Example2: [][]string with validation tag "gt=0,dive,dive,required"
gt=0 will be applied to []
[]string will be spared validation
required will be applied to string
NOTE: in Example2 if the required validation failed, but all others passed
the hierarchy of FieldError's in the middle with have their IsPlaceHolder field
set to true. If a FieldError has IsSliceOrMap=true or IsMap=true then the
FieldError is a Slice or Map field and if IsPlaceHolder=true then contains errors
within its SliceOrArrayErrs or MapErrs fields.
required required
This validates that the value is not the data types default value. This validates that the value is not the data types default value.
For numbers ensures value is not zero. For strings ensures value is For numbers ensures value is not zero. For strings ensures value is

@ -29,7 +29,13 @@ const (
omitempty = "omitempty" omitempty = "omitempty"
required = "required" required = "required"
fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag" 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" structErrMsg = "Struct:%s\n"
diveTag = "dive"
// diveSplit = "," + diveTag
arrayIndexFieldName = "%s[%d]"
mapIndexFieldName = "%s[%v]"
) )
var structPool *pool var structPool *pool
@ -65,8 +71,6 @@ func (p *pool) Borrow() *StructErrors {
// Return returns a StructErrors to the pool. // Return returns a StructErrors to the pool.
func (p *pool) Return(c *StructErrors) { func (p *pool) Return(c *StructErrors) {
// c.Struct = ""
select { select {
case p.pool <- c: case p.pool <- c:
default: default:
@ -87,6 +91,15 @@ type cachedField struct {
kind reflect.Kind kind reflect.Kind
typ reflect.Type typ reflect.Type
isTime bool 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 { type cachedStruct struct {
@ -145,11 +158,38 @@ type FieldError struct {
Type reflect.Type Type reflect.Type
Param string Param string
Value interface{} 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. // 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 // it also allows FieldError to be used as an Error interface
func (e *FieldError) Error() string { 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) return fmt.Sprintf(fieldErrMsg, e.Field, e.Tag)
} }
@ -179,7 +219,7 @@ func (e *StructErrors) Error() string {
buff.WriteString(err.Error()) 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 // Flatten flattens the StructErrors hierarchical structure into a flat namespace style field name
@ -341,7 +381,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
typeField = structType.Field(i) typeField = structType.Field(i)
cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName)} 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 { if cField.tag == noValidationTag {
cs.children-- cs.children--
@ -374,9 +414,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
continue continue
} }
if cField.isTime || valueField.Type() == reflect.TypeOf(time.Time{}) { if cField.isTime {
cField.isTime = true
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
@ -416,13 +454,36 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
} }
} }
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(), 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 { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference // free up memory reference
fieldError = nil 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
}
} }
if !isCached { if !isCached {
@ -440,13 +501,11 @@ 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 // 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 { func (v *Validate) Field(f interface{}, tag string) *FieldError {
return v.FieldWithValue(nil, f, tag) 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 // 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 { func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError {
return v.fieldWithNameAndValue(nil, val, f, tag, "", true, nil) return v.fieldWithNameAndValue(nil, val, f, tag, "", true, nil)
} }
@ -454,6 +513,7 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
var cField *cachedField var cField *cachedField
var isCached bool 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 // 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 {
@ -464,8 +524,9 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
return nil return nil
} }
valueField = reflect.ValueOf(f)
if cacheField == nil { if cacheField == nil {
valueField := reflect.ValueOf(f)
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
valueField = valueField.Elem() valueField = valueField.Elem()
@ -473,6 +534,21 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
} }
cField = &cachedField{name: name, kind: valueField.Kind(), tag: tag, typ: valueField.Type()} 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 { } else {
cField = cacheField cField = cacheField
} }
@ -482,7 +558,7 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
case reflect.Struct, reflect.Interface, reflect.Invalid: case reflect.Struct, reflect.Interface, reflect.Invalid:
if cField.typ != reflect.TypeOf(time.Time{}) { if cField.typ != reflect.TypeOf(time.Time{}) {
panic("Invalid field passed to ValidateFieldWithTag") panic("Invalid field passed to fieldWithNameAndValue")
} }
} }
@ -496,6 +572,13 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
for _, t := range strings.Split(tag, tagSeparator) { 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) orVals := strings.Split(t, orSeparator)
cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))} cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))}
cField.tags = append(cField.tags, cTag) cField.tags = append(cField.tags, cTag)
@ -562,9 +645,163 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
} }
} }
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 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.IsNil() {
if strings.Contains(cField.tag, omitempty) {
continue
}
if strings.Contains(cField.tag, required) {
errs[key.Interface()] = &FieldError{
Field: fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()),
Tag: required,
Value: idxField.Interface(),
Kind: reflect.Ptr,
Type: cField.mapSubtype,
}
}
continue
}
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.IsNil() {
if strings.Contains(cField.tag, omitempty) {
continue
}
if strings.Contains(cField.tag, required) {
errs[i] = &FieldError{
Field: fmt.Sprintf(arrayIndexFieldName, cField.name, i),
Tag: required,
Value: idxField.Interface(),
Kind: reflect.Ptr,
Type: cField.sliceSubtype,
}
}
continue
}
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) { 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 // OK to continue because we checked it's existance before getting into this loop

@ -226,6 +226,487 @@ func AssertMapFieldError(t *testing.T, s map[string]*FieldError, field string, e
EqualSkip(t, 2, val.Tag, expectedTag) EqualSkip(t, 2, val.Tag, expectedTag)
} }
func TestMapDiveValidation(t *testing.T) {
m := map[int]string{0: "ok", 3: "", 4: "ok"}
err := validate.Field(m, "len=3,dive,required")
NotEqual(t, err, nil)
Equal(t, err.IsPlaceholderErr, true)
Equal(t, err.IsMap, true)
Equal(t, len(err.MapErrs), 1)
err = validate.Field(m, "len=2,dive,required")
NotEqual(t, err, nil)
Equal(t, err.IsPlaceholderErr, false)
Equal(t, err.IsMap, false)
Equal(t, len(err.MapErrs), 0)
type Inner struct {
Name string `validate:"required"`
}
type TestMapStruct struct {
Errs map[int]Inner `validate:"gt=0,dive"`
}
mi := map[int]Inner{0: Inner{"ok"}, 3: Inner{""}, 4: Inner{"ok"}}
ms := &TestMapStruct{
Errs: mi,
}
errs := validate.Struct(ms)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
// for full test coverage
fmt.Sprint(errs.Error())
fieldError := errs.Errors["Errs"]
Equal(t, fieldError.IsPlaceholderErr, true)
Equal(t, fieldError.IsMap, true)
Equal(t, len(fieldError.MapErrs), 1)
structErr, ok := fieldError.MapErrs[3].(*StructErrors)
Equal(t, ok, true)
Equal(t, len(structErr.Errors), 1)
innerErr := structErr.Errors["Name"]
Equal(t, innerErr.IsPlaceholderErr, false)
Equal(t, innerErr.IsMap, false)
Equal(t, len(innerErr.MapErrs), 0)
Equal(t, innerErr.Field, "Name")
Equal(t, innerErr.Tag, "required")
type TestMapTimeStruct struct {
Errs map[int]*time.Time `validate:"gt=0,dive,required"`
}
t1 := time.Now().UTC()
mta := map[int]*time.Time{0: &t1, 3: nil, 4: nil}
mt := &TestMapTimeStruct{
Errs: mta,
}
errs = validate.Struct(mt)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldError = errs.Errors["Errs"]
Equal(t, fieldError.IsPlaceholderErr, true)
Equal(t, fieldError.IsMap, true)
Equal(t, len(fieldError.MapErrs), 2)
innerErr, ok = fieldError.MapErrs[3].(*FieldError)
Equal(t, ok, true)
Equal(t, innerErr.IsPlaceholderErr, false)
Equal(t, innerErr.IsMap, false)
Equal(t, len(innerErr.MapErrs), 0)
Equal(t, innerErr.Field, "Errs[3]")
Equal(t, innerErr.Tag, "required")
type TestMapStructPtr struct {
Errs map[int]*Inner `validate:"gt=0,dive,required"`
}
mip := map[int]*Inner{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}}
msp := &TestMapStructPtr{
Errs: mip,
}
errs = validate.Struct(msp)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldError = errs.Errors["Errs"]
Equal(t, fieldError.IsPlaceholderErr, true)
Equal(t, fieldError.IsMap, true)
Equal(t, len(fieldError.MapErrs), 1)
innerFieldError, ok := fieldError.MapErrs[3].(*FieldError)
Equal(t, ok, true)
Equal(t, innerFieldError.IsPlaceholderErr, false)
Equal(t, innerFieldError.IsMap, false)
Equal(t, len(innerFieldError.MapErrs), 0)
Equal(t, innerFieldError.Field, "Errs[3]")
Equal(t, innerFieldError.Tag, "required")
type TestMapStructPtr2 struct {
Errs map[int]*Inner `validate:"gt=0,dive,omitempty,required"`
}
mip2 := map[int]*Inner{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}}
msp2 := &TestMapStructPtr2{
Errs: mip2,
}
errs = validate.Struct(msp2)
Equal(t, errs, nil)
}
func TestArrayDiveValidation(t *testing.T) {
arr := []string{"ok", "", "ok"}
err := validate.Field(arr, "len=3,dive,required")
NotEqual(t, err, nil)
Equal(t, err.IsPlaceholderErr, true)
Equal(t, err.IsSliceOrArray, true)
Equal(t, len(err.SliceOrArrayErrs), 1)
err = validate.Field(arr, "len=2,dive,required")
NotEqual(t, err, nil)
Equal(t, err.IsPlaceholderErr, false)
Equal(t, err.IsSliceOrArray, false)
Equal(t, len(err.SliceOrArrayErrs), 0)
type BadDive struct {
Name string `validate:"dive"`
}
bd := &BadDive{
Name: "TEST",
}
PanicMatches(t, func() { validate.Struct(bd) }, "dive error! can't dive on a non slice or map")
type Test struct {
Errs []string `validate:"gt=0,dive,required"`
}
test := &Test{
Errs: []string{"ok", "", "ok"},
}
errs := validate.Struct(test)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldErr, ok := errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 1)
innerErr, ok := fieldErr.SliceOrArrayErrs[1].(*FieldError)
Equal(t, ok, true)
Equal(t, innerErr.Tag, required)
Equal(t, innerErr.IsPlaceholderErr, false)
Equal(t, innerErr.Field, "Errs[1]")
test = &Test{
Errs: []string{"ok", "ok", ""},
}
errs = validate.Struct(test)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldErr, ok = errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 1)
innerErr, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError)
Equal(t, ok, true)
Equal(t, innerErr.Tag, required)
Equal(t, innerErr.IsPlaceholderErr, false)
Equal(t, innerErr.Field, "Errs[2]")
type TestMultiDimensional struct {
Errs [][]string `validate:"gt=0,dive,dive,required"`
}
var errArray [][]string
errArray = append(errArray, []string{"ok", "", ""})
errArray = append(errArray, []string{"ok", "", ""})
tm := &TestMultiDimensional{
Errs: errArray,
}
errs = validate.Struct(tm)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldErr, ok = errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 2)
sliceError1, ok := fieldErr.SliceOrArrayErrs[0].(*FieldError)
Equal(t, ok, true)
Equal(t, sliceError1.IsPlaceholderErr, true)
Equal(t, sliceError1.IsSliceOrArray, true)
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
innerSliceError1, ok := sliceError1.SliceOrArrayErrs[1].(*FieldError)
Equal(t, ok, true)
Equal(t, innerSliceError1.IsPlaceholderErr, false)
Equal(t, innerSliceError1.Tag, required)
Equal(t, innerSliceError1.IsSliceOrArray, false)
Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0)
type Inner struct {
Name string `validate:"required"`
}
type TestMultiDimensionalStructs struct {
Errs [][]Inner `validate:"gt=0,dive,dive"`
}
var errStructArray [][]Inner
errStructArray = append(errStructArray, []Inner{Inner{"ok"}, Inner{""}, Inner{""}})
errStructArray = append(errStructArray, []Inner{Inner{"ok"}, Inner{""}, Inner{""}})
tms := &TestMultiDimensionalStructs{
Errs: errStructArray,
}
errs = validate.Struct(tms)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldErr, ok = errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 2)
sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError)
Equal(t, ok, true)
Equal(t, sliceError1.IsPlaceholderErr, true)
Equal(t, sliceError1.IsSliceOrArray, true)
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
innerSliceStructError1, ok := sliceError1.SliceOrArrayErrs[1].(*StructErrors)
Equal(t, ok, true)
Equal(t, len(innerSliceStructError1.Errors), 1)
innerInnersliceError1 := innerSliceStructError1.Errors["Name"]
Equal(t, innerInnersliceError1.IsPlaceholderErr, false)
Equal(t, innerInnersliceError1.IsSliceOrArray, false)
Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0)
type TestMultiDimensionalStructsPtr struct {
Errs [][]*Inner `validate:"gt=0,dive,dive"`
}
var errStructPtrArray [][]*Inner
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}})
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}})
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, nil})
tmsp := &TestMultiDimensionalStructsPtr{
Errs: errStructPtrArray,
}
errs = validate.Struct(tmsp)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
// for full test coverage
fmt.Sprint(errs.Error())
fieldErr, ok = errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 3)
sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError)
Equal(t, ok, true)
Equal(t, sliceError1.IsPlaceholderErr, true)
Equal(t, sliceError1.IsSliceOrArray, true)
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
innerSliceStructError1, ok = sliceError1.SliceOrArrayErrs[1].(*StructErrors)
Equal(t, ok, true)
Equal(t, len(innerSliceStructError1.Errors), 1)
innerInnersliceError1 = innerSliceStructError1.Errors["Name"]
Equal(t, innerInnersliceError1.IsPlaceholderErr, false)
Equal(t, innerInnersliceError1.IsSliceOrArray, false)
Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0)
type TestMultiDimensionalStructsPtr2 struct {
Errs [][]*Inner `validate:"gt=0,dive,dive,required"`
}
var errStructPtr2Array [][]*Inner
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}})
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}})
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil})
tmsp2 := &TestMultiDimensionalStructsPtr2{
Errs: errStructPtr2Array,
}
errs = validate.Struct(tmsp2)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldErr, ok = errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 3)
sliceError1, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError)
Equal(t, ok, true)
Equal(t, sliceError1.IsPlaceholderErr, true)
Equal(t, sliceError1.IsSliceOrArray, true)
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
innerSliceStructError1, ok = sliceError1.SliceOrArrayErrs[1].(*StructErrors)
Equal(t, ok, true)
Equal(t, len(innerSliceStructError1.Errors), 1)
innerSliceStructError2, ok := sliceError1.SliceOrArrayErrs[2].(*FieldError)
Equal(t, ok, true)
Equal(t, innerSliceStructError2.IsPlaceholderErr, false)
Equal(t, innerSliceStructError2.IsSliceOrArray, false)
Equal(t, len(innerSliceStructError2.SliceOrArrayErrs), 0)
Equal(t, innerSliceStructError2.Field, "Errs[2][2]")
innerInnersliceError1 = innerSliceStructError1.Errors["Name"]
Equal(t, innerInnersliceError1.IsPlaceholderErr, false)
Equal(t, innerInnersliceError1.IsSliceOrArray, false)
Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0)
type TestMultiDimensionalStructsPtr3 struct {
Errs [][]*Inner `validate:"gt=0,dive,dive,omitempty"`
}
var errStructPtr3Array [][]*Inner
errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}})
errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}})
errStructPtr3Array = append(errStructPtr3Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil})
tmsp3 := &TestMultiDimensionalStructsPtr3{
Errs: errStructPtr3Array,
}
errs = validate.Struct(tmsp3)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldErr, ok = errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 3)
sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError)
Equal(t, ok, true)
Equal(t, sliceError1.IsPlaceholderErr, true)
Equal(t, sliceError1.IsSliceOrArray, true)
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
innerSliceStructError1, ok = sliceError1.SliceOrArrayErrs[1].(*StructErrors)
Equal(t, ok, true)
Equal(t, len(innerSliceStructError1.Errors), 1)
innerInnersliceError1 = innerSliceStructError1.Errors["Name"]
Equal(t, innerInnersliceError1.IsPlaceholderErr, false)
Equal(t, innerInnersliceError1.IsSliceOrArray, false)
Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0)
type TestMultiDimensionalTimeTime struct {
Errs [][]*time.Time `validate:"gt=0,dive,dive,required"`
}
var errTimePtr3Array [][]*time.Time
t1 := time.Now().UTC()
t2 := time.Now().UTC()
t3 := time.Now().UTC().Add(time.Hour * 24)
errTimePtr3Array = append(errTimePtr3Array, []*time.Time{&t1, &t2, &t3})
errTimePtr3Array = append(errTimePtr3Array, []*time.Time{&t1, &t2, nil})
errTimePtr3Array = append(errTimePtr3Array, []*time.Time{&t1, nil, nil})
tmtp3 := &TestMultiDimensionalTimeTime{
Errs: errTimePtr3Array,
}
errs = validate.Struct(tmtp3)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldErr, ok = errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 2)
sliceError1, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError)
Equal(t, ok, true)
Equal(t, sliceError1.IsPlaceholderErr, true)
Equal(t, sliceError1.IsSliceOrArray, true)
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
innerSliceError1, ok = sliceError1.SliceOrArrayErrs[1].(*FieldError)
Equal(t, ok, true)
Equal(t, innerSliceError1.IsPlaceholderErr, false)
Equal(t, innerSliceError1.IsSliceOrArray, false)
Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0)
Equal(t, innerSliceError1.Field, "Errs[2][1]")
Equal(t, innerSliceError1.Tag, required)
type TestMultiDimensionalTimeTime2 struct {
Errs [][]*time.Time `validate:"gt=0,dive,dive,required"`
}
var errTimeArray [][]*time.Time
t1 = time.Now().UTC()
t2 = time.Now().UTC()
t3 = time.Now().UTC().Add(time.Hour * 24)
errTimeArray = append(errTimeArray, []*time.Time{&t1, &t2, &t3})
errTimeArray = append(errTimeArray, []*time.Time{&t1, &t2, nil})
errTimeArray = append(errTimeArray, []*time.Time{&t1, nil, nil})
tmtp := &TestMultiDimensionalTimeTime2{
Errs: errTimeArray,
}
errs = validate.Struct(tmtp)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
fieldErr, ok = errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 2)
sliceError1, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError)
Equal(t, ok, true)
Equal(t, sliceError1.IsPlaceholderErr, true)
Equal(t, sliceError1.IsSliceOrArray, true)
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
innerSliceError1, ok = sliceError1.SliceOrArrayErrs[1].(*FieldError)
Equal(t, ok, true)
Equal(t, innerSliceError1.IsPlaceholderErr, false)
Equal(t, innerSliceError1.IsSliceOrArray, false)
Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0)
Equal(t, innerSliceError1.Field, "Errs[2][1]")
Equal(t, innerSliceError1.Tag, required)
}
func TestNilStructPointerValidation(t *testing.T) { func TestNilStructPointerValidation(t *testing.T) {
type Inner struct { type Inner struct {
Data string Data string
@ -2863,7 +3344,7 @@ func TestInvalidField(t *testing.T) {
Test: "1", Test: "1",
} }
PanicMatches(t, func() { validate.Field(s, "required") }, "Invalid field passed to ValidateFieldWithTag") PanicMatches(t, func() { validate.Field(s, "required") }, "Invalid field passed to fieldWithNameAndValue")
} }
func TestInvalidTagField(t *testing.T) { func TestInvalidTagField(t *testing.T) {

Loading…
Cancel
Save