From c7ff296dca4541b59f122426de1600da5c51cf8d Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 28 Jun 2015 21:51:39 -0400 Subject: [PATCH 1/5] correct interface issue add handling of interface validation by determining it's type for issue #85 --- validator.go | 140 +++++++++++++++++++++++++---- validator_test.go | 220 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 344 insertions(+), 16 deletions(-) diff --git a/validator.go b/validator.go index f421c87..db01f1f 100644 --- a/validator.go +++ b/validator.go @@ -353,7 +353,6 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter structName = structType.Name() numFields = structValue.NumField() cs = &cachedStruct{name: structName, children: numFields} - structCache.Set(structType, cs) } validationErrors := structPool.Borrow() @@ -429,24 +428,62 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter continue } - if valueField.Kind() == reflect.Ptr && valueField.IsNil() { + if (valueField.Kind() == reflect.Ptr || cField.kind == reflect.Interface) && valueField.IsNil() { if strings.Contains(cField.tag, omitempty) { - continue + goto CACHEFIELD } - if strings.Contains(cField.tag, required) { + 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: required, + Tag: vals[0], + Param: param, Value: valueField.Interface(), + Kind: valueField.Kind(), + Type: valueField.Type(), } - continue + 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 @@ -486,11 +523,14 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter } } + CACHEFIELD: if !isCached { cs.fields = append(cs.fields, cField) } } + structCache.Set(structType, cs) + if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 { structPool.Return(validationErrors) return nil @@ -709,19 +749,29 @@ func (v *Validate) traverseMap(val interface{}, current interface{}, valueField continue } - if idxField.Kind() == reflect.Ptr && idxField.IsNil() { + if (idxField.Kind() == reflect.Ptr || idxField.Kind() == reflect.Interface) && idxField.IsNil() { - if strings.Contains(cField.tag, omitempty) { + if strings.Contains(cField.diveTag, omitempty) { continue } - if strings.Contains(cField.tag, required) { + 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: required, + Tag: vals[0], + Param: param, Value: idxField.Interface(), - Kind: reflect.Ptr, + Kind: idxField.Kind(), Type: cField.mapSubtype, } } @@ -729,6 +779,30 @@ func (v *Validate) traverseMap(val interface{}, current interface{}, valueField 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 } @@ -768,19 +842,29 @@ func (v *Validate) traverseSliceOrArray(val interface{}, current interface{}, va continue } - if idxField.Kind() == reflect.Ptr && idxField.IsNil() { + if (idxField.Kind() == reflect.Ptr || idxField.Kind() == reflect.Interface) && idxField.IsNil() { - if strings.Contains(cField.tag, omitempty) { + if strings.Contains(cField.diveTag, omitempty) { continue } - if strings.Contains(cField.tag, required) { + 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: required, + Tag: vals[0], + Param: param, Value: idxField.Interface(), - Kind: reflect.Ptr, + Kind: idxField.Kind(), Type: cField.sliceSubtype, } } @@ -788,6 +872,30 @@ func (v *Validate) traverseSliceOrArray(val interface{}, current interface{}, va 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 } diff --git a/validator_test.go b/validator_test.go index 77e4e15..4edc1a9 100644 --- a/validator_test.go +++ b/validator_test.go @@ -226,6 +226,226 @@ func AssertMapFieldError(t *testing.T, s map[string]*FieldError, field string, e EqualSkip(t, 2, val.Tag, expectedTag) } +func TestInterfaceErrValidation(t *testing.T) { + + var v1 interface{} + var v2 interface{} + + v2 = 1 + v1 = v2 + + err := validate.Field(v1, "len=1") + Equal(t, err, nil) + err = validate.Field(v2, "len=1") + Equal(t, err, nil) + + type ExternalCMD struct { + Userid string `json:"userid"` + Action uint32 `json:"action"` + Data interface{} `json:"data,omitempty" validate:"required"` + } + + s := &ExternalCMD{ + Userid: "123456", + Action: 10000, + // Data: 1, + } + + errs := validate.Struct(s) + NotEqual(t, errs, nil) + Equal(t, errs.Errors["Data"].Field, "Data") + Equal(t, errs.Errors["Data"].Tag, "required") + + type ExternalCMD2 struct { + Userid string `json:"userid"` + Action uint32 `json:"action"` + Data interface{} `json:"data,omitempty" validate:"len=1"` + } + + s2 := &ExternalCMD2{ + Userid: "123456", + Action: 10000, + // Data: 1, + } + + errs = validate.Struct(s2) + NotEqual(t, errs, nil) + Equal(t, errs.Errors["Data"].Field, "Data") + Equal(t, errs.Errors["Data"].Tag, "len") + Equal(t, errs.Errors["Data"].Param, "1") + + s3 := &ExternalCMD2{ + Userid: "123456", + Action: 10000, + Data: 2, + } + + errs = validate.Struct(s3) + NotEqual(t, errs, nil) + Equal(t, errs.Errors["Data"].Field, "Data") + Equal(t, errs.Errors["Data"].Tag, "len") + Equal(t, errs.Errors["Data"].Param, "1") + + type Inner struct { + Name string `validate:"required"` + } + + inner := &Inner{ + Name: "", + } + + s4 := &ExternalCMD{ + Userid: "123456", + Action: 10000, + Data: inner, + } + + errs = validate.Struct(s4) + NotEqual(t, errs, nil) + Equal(t, errs.StructErrors["Data"].Struct, "Inner") + Equal(t, errs.StructErrors["Data"].Errors["Name"].Field, "Name") + Equal(t, errs.StructErrors["Data"].Errors["Name"].Tag, "required") + + type TestMapStructPtr struct { + Errs map[int]interface{} `validate:"gt=0,dive,len=2"` + } + + mip := map[int]interface{}{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, "len") + + type TestMultiDimensionalStructs struct { + Errs [][]interface{} `validate:"gt=0,dive,dive,len=2"` + } + + var errStructArray [][]interface{} + + errStructArray = append(errStructArray, []interface{}{&Inner{"ok"}, &Inner{""}, &Inner{""}}) + errStructArray = append(errStructArray, []interface{}{&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 TestMultiDimensionalStructsPtr2 struct { + Errs [][]*Inner `validate:"gt=0,dive,dive,len=2"` + } + + 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) + + m := map[int]interface{}{0: "ok", 3: "", 4: "ok"} + + err = validate.Field(m, "len=3,dive,len=2") + 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) + + arr := []interface{}{"ok", "", "ok"} + + err = validate.Field(arr, "len=3,dive,len=2") + 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) +} + func TestMapDiveValidation(t *testing.T) { m := map[int]string{0: "ok", 3: "", 4: "ok"} From 7d55bfddde2ac1febee4ff0aeebb0934409c9b43 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 29 Jun 2015 09:48:57 -0400 Subject: [PATCH 2/5] 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) From 4d571655620e6785b67c708b19e2cf144350ba4b Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 29 Jun 2015 09:53:08 -0400 Subject: [PATCH 3/5] fix wrong variable used in checking for "[" char for #85 --- validator.go | 34 +++++++++++++++++++++++----------- validator_test.go | 5 +++++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/validator.go b/validator.go index f70948d..8bbd891 100644 --- a/validator.go +++ b/validator.go @@ -248,7 +248,6 @@ func (e *FieldError) Error() string { // 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 { @@ -276,18 +275,31 @@ func (e *FieldError) Flatten() map[string]*FieldError { } } } - } 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 - // } - // } - // } + 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 { + 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 + } + } + } + } } return errs @@ -340,7 +352,7 @@ func (e *StructErrors) flatten(isFromStruct bool) map[string]*FieldError { for k, fe := range flat { - if isFromStruct && f.Field[0:1] == "[" { + if isFromStruct && k[0:1] == "[" { errs[f.Field+k] = fe } else { errs[k] = fe diff --git a/validator_test.go b/validator_test.go index 53ab455..4c4e4a9 100644 --- a/validator_test.go +++ b/validator_test.go @@ -266,6 +266,11 @@ func TestFlattenValidation(t *testing.T) { 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") + // expect Errs[0][1].Inner.Name = error // fmt.Println((fieldErr.SliceOrArrayErrs[0].(*FieldError)).Field) // fmt.Println((fieldErr.SliceOrArrayErrs[0].(*FieldError)).IsPlaceholderErr) From f604b6cc960649ab353f6b2fc6186837e66a895a Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 29 Jun 2015 20:10:13 -0400 Subject: [PATCH 4/5] Complete Flatten logic for #88 --- validator.go | 21 +++++++-- validator_test.go | 115 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 129 insertions(+), 7 deletions(-) diff --git a/validator.go b/validator.go index 8bbd891..722c37a 100644 --- a/validator.go +++ b/validator.go @@ -261,7 +261,12 @@ func (e *FieldError) Flatten() map[string]*FieldError { if flat := fe.Flatten(); flat != nil && len(flat) > 0 { for k, v := range flat { - errs[fmt.Sprintf("[%#v]%s", key, k)] = v + if fe.IsPlaceholderErr { + errs[fmt.Sprintf("[%#v]%s", key, k)] = v + } else { + errs[fmt.Sprintf("[%#v]", key)] = v + } + } } } else { @@ -286,7 +291,11 @@ func (e *FieldError) Flatten() map[string]*FieldError { if flat := fe.Flatten(); flat != nil && len(flat) > 0 { for k, v := range flat { - errs[fmt.Sprintf("[%#v]%s", key, k)] = v + if fe.IsPlaceholderErr { + errs[fmt.Sprintf("[%#v]%s", key, k)] = v + } else { + errs[fmt.Sprintf("[%#v]", key)] = v + } } } } else { @@ -352,7 +361,13 @@ func (e *StructErrors) flatten(isFromStruct bool) map[string]*FieldError { for k, fe := range flat { - if isFromStruct && k[0:1] == "[" { + // fmt.Println(k) + // if isFromStruct && k[0:1] == "[" { + // errs[f.Field+k] = fe + // } else { + // errs[k] = fe + // } + if f.IsPlaceholderErr { errs[f.Field+k] = fe } else { errs[k] = fe diff --git a/validator_test.go b/validator_test.go index 4c4e4a9..1eda6a0 100644 --- a/validator_test.go +++ b/validator_test.go @@ -233,7 +233,7 @@ func TestFlattenValidation(t *testing.T) { } type TestMultiDimensionalStructsPtr struct { - Errs [][]*Inner `validate:"gt=0,dive,dive"` + Errs [][]*Inner `validate:"gt=0,dive,dive,required"` } var errStructPtrArray [][]*Inner @@ -271,9 +271,116 @@ func TestFlattenValidation(t *testing.T) { Equal(t, structErrFlatten.Field, "Name") Equal(t, structErrFlatten.Tag, "required") - // expect Errs[0][1].Inner.Name = error - // fmt.Println((fieldErr.SliceOrArrayErrs[0].(*FieldError)).Field) - // fmt.Println((fieldErr.SliceOrArrayErrs[0].(*FieldError)).IsPlaceholderErr) + 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) { From 92bd6b335a5adfdab7f754d814f6b0f00953d1c4 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 29 Jun 2015 20:14:58 -0400 Subject: [PATCH 5/5] code cleanup for #88 --- doc.go | 2 +- validator.go | 74 +++++----------------------------------------------- 2 files changed, 8 insertions(+), 68 deletions(-) diff --git a/doc.go b/doc.go index 06c940b..f45ef34 100644 --- a/doc.go +++ b/doc.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 [] diff --git a/validator.go b/validator.go index 722c37a..64f0f85 100644 --- a/validator.go +++ b/validator.go @@ -192,57 +192,6 @@ 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 @@ -273,7 +222,7 @@ func (e *FieldError) Flatten() map[string]*FieldError { se := err.(*StructErrors) - if flat := se.flatten(false); flat != nil && len(flat) > 0 { + 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 } @@ -302,7 +251,7 @@ func (e *FieldError) Flatten() map[string]*FieldError { se := err.(*StructErrors) - if flat := se.flatten(false); flat != nil && len(flat) > 0 { + 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 } @@ -348,7 +297,10 @@ func (e *StructErrors) Error() string { return strings.TrimSpace(buff.String()) } -func (e *StructErrors) flatten(isFromStruct bool) map[string]*FieldError { +// 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 } @@ -361,12 +313,6 @@ func (e *StructErrors) flatten(isFromStruct bool) map[string]*FieldError { for k, fe := range flat { - // fmt.Println(k) - // if isFromStruct && k[0:1] == "[" { - // errs[f.Field+k] = fe - // } else { - // errs[k] = fe - // } if f.IsPlaceholderErr { errs[f.Field+k] = fe } else { @@ -378,7 +324,7 @@ func (e *StructErrors) flatten(isFromStruct bool) map[string]*FieldError { for key, val := range e.StructErrors { - otherErrs := val.flatten(isFromStruct) + otherErrs := val.Flatten() for _, f2 := range otherErrs { @@ -390,12 +336,6 @@ func (e *StructErrors) flatten(isFromStruct bool) 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