From 7d55bfddde2ac1febee4ff0aeebb0934409c9b43 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 29 Jun 2015 09:48:57 -0400 Subject: [PATCH] complete flatten logic for array elements for #85 --- validator.go | 158 ++++++++++++++++++++++++++++++++++++++++------ validator_test.go | 71 +++++++++++++++++++++ 2 files changed, 209 insertions(+), 20 deletions(-) diff --git a/validator.go b/validator.go index db01f1f..f70948d 100644 --- a/validator.go +++ b/validator.go @@ -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,112 @@ func (e *FieldError) Error() string { 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 // for a non hierarchical representation please see the Flatten method for StructErrors type StructErrors struct { @@ -222,10 +327,7 @@ func (e *StructErrors) Error() string { 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 { - +func (e *StructErrors) flatten(isFromStruct bool) map[string]*FieldError { if e == nil { return nil } @@ -234,12 +336,22 @@ 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 isFromStruct && f.Field[0:1] == "[" { + errs[f.Field+k] = fe + } else { + errs[k] = fe + } + } + } } for key, val := range e.StructErrors { - otherErrs := val.Flatten() + otherErrs := val.flatten(isFromStruct) for _, f2 := range otherErrs { @@ -251,6 +363,12 @@ func (e *StructErrors) Flatten() map[string]*FieldError { 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 // top = top level struct when validating by struct otherwise nil // current = current level struct when validating by struct otherwise optional comparison value diff --git a/validator_test.go b/validator_test.go index 4edc1a9..53ab455 100644 --- a/validator_test.go +++ b/validator_test.go @@ -226,6 +226,51 @@ 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"` + } + + 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) { var v1 interface{} @@ -578,6 +623,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 +656,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 +722,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 +730,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 +794,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)