Merge pull request #90 from bluesuncorp/v5-development

Update Flatten logic to handle new dive logic
pull/91/head v5.9.2
Dean Karn 9 years ago
commit d8e0c4d936
  1. 2
      doc.go
  2. 115
      validator.go
  3. 183
      validator_test.go

@ -176,7 +176,7 @@ Here is a list of the current built in validators:
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
Multidimensional nesting is also supported, each level you wish 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 []

@ -20,20 +20,19 @@ import (
)
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"
// diveSplit = "," + diveTag
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]"
)
@ -193,6 +192,82 @@ func (e *FieldError) Error() 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 {
@ -234,7 +309,17 @@ func (e *StructErrors) Flatten() map[string]*FieldError {
for _, f := range e.Errors {
errs[f.Field] = f
if flat := f.Flatten(); flat != nil && len(flat) > 0 {
for k, fe := range flat {
if f.IsPlaceholderErr {
errs[f.Field+k] = fe
} else {
errs[k] = fe
}
}
}
}
for key, val := range e.StructErrors {

@ -226,6 +226,163 @@ func AssertMapFieldError(t *testing.T, s map[string]*FieldError, field string, e
EqualSkip(t, 2, val.Tag, expectedTag)
}
func TestFlattenValidation(t *testing.T) {
type Inner struct {
Name string `validate:"required"`
}
type TestMultiDimensionalStructsPtr struct {
Errs [][]*Inner `validate:"gt=0,dive,dive,required"`
}
var errStructPtrArray [][]*Inner
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{"ok"}})
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 := errs.Errors["Errs"]
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, fieldErr.Field, "Errs")
Equal(t, len(fieldErr.SliceOrArrayErrs), 1)
innerSlice1, ok := fieldErr.SliceOrArrayErrs[0].(*FieldError)
Equal(t, ok, true)
Equal(t, innerSlice1.IsPlaceholderErr, true)
Equal(t, innerSlice1.Field, "Errs[0]")
flatFieldErr, ok := fieldErr.Flatten()["[0][1].Inner.Name"]
Equal(t, ok, true)
Equal(t, flatFieldErr.Field, "Name")
Equal(t, flatFieldErr.Tag, "required")
structErrFlatten, ok := errs.Flatten()["Errs[0][1].Inner.Name"]
Equal(t, ok, true)
Equal(t, structErrFlatten.Field, "Name")
Equal(t, structErrFlatten.Tag, "required")
errStructPtrArray = [][]*Inner{}
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, nil, &Inner{"ok"}})
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 = errs.Errors["Errs"]
Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, fieldErr.Field, "Errs")
Equal(t, len(fieldErr.SliceOrArrayErrs), 1)
innerSlice1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError)
Equal(t, ok, true)
Equal(t, innerSlice1.IsPlaceholderErr, true)
Equal(t, innerSlice1.Field, "Errs[0]")
flatFieldErr, ok = fieldErr.Flatten()["[0][1]"]
Equal(t, ok, true)
Equal(t, flatFieldErr.Field, "Errs[0][1]")
Equal(t, flatFieldErr.Tag, "required")
type TestMapStructPtr struct {
Errs map[int]*Inner `validate:"gt=0,dive,required"`
}
mip := map[int]*Inner{0: &Inner{"ok"}, 3: &Inner{""}, 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)
innerStructError, ok := fieldError.MapErrs[3].(*StructErrors)
Equal(t, ok, true)
Equal(t, innerStructError.Struct, "Inner")
Equal(t, len(innerStructError.Errors), 1)
innerInnerFieldError, ok := innerStructError.Errors["Name"]
Equal(t, ok, true)
Equal(t, innerInnerFieldError.IsPlaceholderErr, false)
Equal(t, innerInnerFieldError.IsSliceOrArray, false)
Equal(t, innerInnerFieldError.Field, "Name")
Equal(t, innerInnerFieldError.Tag, "required")
flatErrs, ok := errs.Flatten()["Errs[3].Inner.Name"]
Equal(t, ok, true)
Equal(t, flatErrs.Field, "Name")
Equal(t, flatErrs.Tag, "required")
mip2 := map[int]*Inner{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}}
msp2 := &TestMapStructPtr{
Errs: mip2,
}
errs = validate.Struct(msp2)
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.IsSliceOrArray, false)
Equal(t, innerFieldError.Field, "Errs[3]")
Equal(t, innerFieldError.Tag, "required")
flatErrs, ok = errs.Flatten()["Errs[3]"]
Equal(t, ok, true)
Equal(t, flatErrs.Field, "Errs[3]")
Equal(t, flatErrs.Tag, "required")
type TestMapInnerArrayStruct struct {
Errs map[int][]string `validate:"gt=0,dive,dive,required"`
}
mias := map[int][]string{0: []string{"ok"}, 3: []string{"ok", ""}, 4: []string{"ok"}}
mia := &TestMapInnerArrayStruct{
Errs: mias,
}
errs = validate.Struct(mia)
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
flatErrs, ok = errs.Flatten()["Errs[3][1]"]
Equal(t, ok, true)
Equal(t, flatErrs.Field, "Errs[3][1]")
Equal(t, flatErrs.Tag, "required")
}
func TestInterfaceErrValidation(t *testing.T) {
var v1 interface{}
@ -578,6 +735,11 @@ func TestArrayDiveValidation(t *testing.T) {
Equal(t, err.IsSliceOrArray, true)
Equal(t, len(err.SliceOrArrayErrs), 1)
// flat := err.Flatten()
// fe, ok := flat["[1]"]
// Equal(t, ok, true)
// Equal(t, fe.Tag, "required")
err = validate.Field(arr, "len=2,dive,required")
NotEqual(t, err, nil)
Equal(t, err.IsPlaceholderErr, false)
@ -606,6 +768,12 @@ func TestArrayDiveValidation(t *testing.T) {
NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1)
// flat = errs.Flatten()
// me, ok := flat["Errs[1]"]
// Equal(t, ok, true)
// Equal(t, me.Field, "Errs[1]")
// Equal(t, me.Tag, "required")
fieldErr, ok := errs.Errors["Errs"]
Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true)
@ -666,6 +834,7 @@ func TestArrayDiveValidation(t *testing.T) {
Equal(t, sliceError1.IsPlaceholderErr, true)
Equal(t, sliceError1.IsSliceOrArray, true)
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
Equal(t, sliceError1.Field, "Errs[0]")
innerSliceError1, ok := sliceError1.SliceOrArrayErrs[1].(*FieldError)
Equal(t, ok, true)
@ -673,6 +842,7 @@ func TestArrayDiveValidation(t *testing.T) {
Equal(t, innerSliceError1.Tag, required)
Equal(t, innerSliceError1.IsSliceOrArray, false)
Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0)
Equal(t, innerSliceError1.Field, "Errs[0][1]")
type Inner struct {
Name string `validate:"required"`
@ -736,12 +906,25 @@ func TestArrayDiveValidation(t *testing.T) {
// for full test coverage
fmt.Sprint(errs.Error())
// flat := errs.Flatten()
// // fmt.Println(errs)
// fmt.Println(flat)
// expect Errs[0][1].Inner.Name
// me, ok := flat["Errs[1]"]
// Equal(t, ok, true)
// Equal(t, me.Field, "Errs[1]")
// Equal(t, me.Tag, "required")
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)
// flat := fieldErr.Flatten()
// fmt.Println(errs)
// fmt.Println(flat)
sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError)
Equal(t, ok, true)
Equal(t, sliceError1.IsPlaceholderErr, true)

Loading…
Cancel
Save