complete flatten logic for array elements

for #85
pull/89/head
joeybloggs 10 years ago
parent d627af88ac
commit 7d55bfddde
  1. 158
      validator.go
  2. 71
      validator_test.go

@ -20,20 +20,19 @@ import (
) )
const ( const (
utf8HexComma = "0x2C" utf8HexComma = "0x2C"
tagSeparator = "," tagSeparator = ","
orSeparator = "|" orSeparator = "|"
noValidationTag = "-" noValidationTag = "-"
tagKeySeparator = "=" tagKeySeparator = "="
structOnlyTag = "structonly" structOnlyTag = "structonly"
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" 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" mapErrMsg = "Field validation for \"%s\" failed on key \"%v\" with error(s): %s"
structErrMsg = "Struct:%s\n" structErrMsg = "Struct:%s\n"
diveTag = "dive" diveTag = "dive"
// diveSplit = "," + diveTag
arrayIndexFieldName = "%s[%d]" arrayIndexFieldName = "%s[%d]"
mapIndexFieldName = "%s[%v]" mapIndexFieldName = "%s[%v]"
) )
@ -193,6 +192,112 @@ func (e *FieldError) Error() string {
return fmt.Sprintf(fieldErrMsg, e.Field, e.Tag) return fmt.Sprintf(fieldErrMsg, e.Field, e.Tag)
} }
// func (e *FieldError) flatten(isFromStruct bool) 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(isFromStruct); flat != nil && len(flat) > 0 {
// for k, v := range flat {
// errs[fmt.Sprintf("[%#v]%s", key, k)] = v
// }
// }
// } else {
// se := err.(*StructErrors)
// if flat := se.flatten(isFromStruct); 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 _, err := range e.MapErrs {
// // if flat := err.Flatten(); flat != nil && len(flat) > 0 {
// // for k, v := range flat {
// // errs[k] = v
// // }
// // }
// // }
// }
// return errs
// }
// errs[e.Field] = e
// return errs
// }
// 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 {
// return e.flatten(false)
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 {
errs[fmt.Sprintf("[%#v]%s", key, k)] = v
}
}
} else {
se := err.(*StructErrors)
if flat := se.flatten(false); 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 _, err := range e.MapErrs {
// if flat := err.Flatten(); flat != nil && len(flat) > 0 {
// for k, v := range flat {
// errs[k] = v
// }
// }
// }
}
return errs
}
errs[e.Field] = e
return errs
}
// StructErrors is hierarchical list of field and struct validation errors // StructErrors is hierarchical list of field and struct validation errors
// for a non hierarchical representation please see the Flatten method for StructErrors // for a non hierarchical representation please see the Flatten method for StructErrors
type StructErrors struct { type StructErrors struct {
@ -222,10 +327,7 @@ func (e *StructErrors) Error() string {
return strings.TrimSpace(buff.String()) return strings.TrimSpace(buff.String())
} }
// Flatten flattens the StructErrors hierarchical structure into a flat namespace style field name func (e *StructErrors) flatten(isFromStruct bool) map[string]*FieldError {
// for those that want/need it
func (e *StructErrors) Flatten() map[string]*FieldError {
if e == nil { if e == nil {
return nil return nil
} }
@ -234,12 +336,22 @@ func (e *StructErrors) Flatten() map[string]*FieldError {
for _, f := range e.Errors { for _, f := range e.Errors {
errs[f.Field] = f if flat := f.Flatten(); flat != nil && len(flat) > 0 {
for k, fe := range flat {
if isFromStruct && f.Field[0:1] == "[" {
errs[f.Field+k] = fe
} else {
errs[k] = fe
}
}
}
} }
for key, val := range e.StructErrors { for key, val := range e.StructErrors {
otherErrs := val.Flatten() otherErrs := val.flatten(isFromStruct)
for _, f2 := range otherErrs { for _, f2 := range otherErrs {
@ -251,6 +363,12 @@ func (e *StructErrors) Flatten() map[string]*FieldError {
return errs return errs
} }
// 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 {
return e.flatten(true)
}
// Func accepts all values needed for file and cross field validation // Func accepts all values needed for file and cross field validation
// top = top level struct when validating by struct otherwise nil // top = top level struct when validating by struct otherwise nil
// current = current level struct when validating by struct otherwise optional comparison value // current = current level struct when validating by struct otherwise optional comparison value

@ -226,6 +226,51 @@ 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 TestFlattenValidation(t *testing.T) {
type Inner struct {
Name string `validate:"required"`
}
type TestMultiDimensionalStructsPtr struct {
Errs [][]*Inner `validate:"gt=0,dive,dive"`
}
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")
// expect Errs[0][1].Inner.Name = error
// fmt.Println((fieldErr.SliceOrArrayErrs[0].(*FieldError)).Field)
// fmt.Println((fieldErr.SliceOrArrayErrs[0].(*FieldError)).IsPlaceholderErr)
}
func TestInterfaceErrValidation(t *testing.T) { func TestInterfaceErrValidation(t *testing.T) {
var v1 interface{} var v1 interface{}
@ -578,6 +623,11 @@ func TestArrayDiveValidation(t *testing.T) {
Equal(t, err.IsSliceOrArray, true) Equal(t, err.IsSliceOrArray, true)
Equal(t, len(err.SliceOrArrayErrs), 1) 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") err = validate.Field(arr, "len=2,dive,required")
NotEqual(t, err, nil) NotEqual(t, err, nil)
Equal(t, err.IsPlaceholderErr, false) Equal(t, err.IsPlaceholderErr, false)
@ -606,6 +656,12 @@ func TestArrayDiveValidation(t *testing.T) {
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
Equal(t, len(errs.Errors), 1) 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"] fieldErr, ok := errs.Errors["Errs"]
Equal(t, ok, true) Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true) Equal(t, fieldErr.IsPlaceholderErr, true)
@ -666,6 +722,7 @@ func TestArrayDiveValidation(t *testing.T) {
Equal(t, sliceError1.IsPlaceholderErr, true) Equal(t, sliceError1.IsPlaceholderErr, true)
Equal(t, sliceError1.IsSliceOrArray, true) Equal(t, sliceError1.IsSliceOrArray, true)
Equal(t, len(sliceError1.SliceOrArrayErrs), 2) Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
Equal(t, sliceError1.Field, "Errs[0]")
innerSliceError1, ok := sliceError1.SliceOrArrayErrs[1].(*FieldError) innerSliceError1, ok := sliceError1.SliceOrArrayErrs[1].(*FieldError)
Equal(t, ok, true) Equal(t, ok, true)
@ -673,6 +730,7 @@ func TestArrayDiveValidation(t *testing.T) {
Equal(t, innerSliceError1.Tag, required) Equal(t, innerSliceError1.Tag, required)
Equal(t, innerSliceError1.IsSliceOrArray, false) Equal(t, innerSliceError1.IsSliceOrArray, false)
Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0) Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0)
Equal(t, innerSliceError1.Field, "Errs[0][1]")
type Inner struct { type Inner struct {
Name string `validate:"required"` Name string `validate:"required"`
@ -736,12 +794,25 @@ func TestArrayDiveValidation(t *testing.T) {
// for full test coverage // for full test coverage
fmt.Sprint(errs.Error()) 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"] fieldErr, ok = errs.Errors["Errs"]
Equal(t, ok, true) Equal(t, ok, true)
Equal(t, fieldErr.IsPlaceholderErr, true) Equal(t, fieldErr.IsPlaceholderErr, true)
Equal(t, fieldErr.IsSliceOrArray, true) Equal(t, fieldErr.IsSliceOrArray, true)
Equal(t, len(fieldErr.SliceOrArrayErrs), 3) Equal(t, len(fieldErr.SliceOrArrayErrs), 3)
// flat := fieldErr.Flatten()
// fmt.Println(errs)
// fmt.Println(flat)
sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError) sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError)
Equal(t, ok, true) Equal(t, ok, true)
Equal(t, sliceError1.IsPlaceholderErr, true) Equal(t, sliceError1.IsPlaceholderErr, true)

Loading…
Cancel
Save