diff --git a/validator.go b/validator.go index 36fd3ff..a77cbb5 100644 --- a/validator.go +++ b/validator.go @@ -20,21 +20,22 @@ 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\" failed with error(s): %s" - mapErrMsg = "Field validation for \"%s\" failed with key \"%v\" failed with error(s): %s" - structErrMsg = "Struct:%s\n" - diveTag = "dive" - diveSplit = "," + diveTag - indexFieldName = "%s[%d]" + 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 + arrayIndexFieldName = "%s[%d]" + mapIndexFieldName = "%s[%v]" ) var structPool *pool @@ -670,7 +671,18 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f } } else if cField.isMap { - // return if error here + 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") @@ -680,6 +692,65 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f 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.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(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: cField.name, + Tag: required, + Value: idxField.Interface(), + Kind: reflect.Ptr, + Type: cField.sliceSubtype, + } + } + + 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{} @@ -698,7 +769,7 @@ func (v *Validate) traverseSliceOrArray(val interface{}, current interface{}, va if cField.isTimeSubtype { - if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, cField.name, false, nil); fieldError != nil { + if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(arrayIndexFieldName, cField.name, i), false, nil); fieldError != nil { errs[i] = fieldError } @@ -730,7 +801,7 @@ func (v *Validate) traverseSliceOrArray(val interface{}, current interface{}, va } default: - if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(indexFieldName, cField.name, i), false, nil); fieldError != nil { + if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(arrayIndexFieldName, cField.name, i), false, nil); fieldError != nil { errs[i] = fieldError } } diff --git a/validator_test.go b/validator_test.go index a1086ab..207c948 100644 --- a/validator_test.go +++ b/validator_test.go @@ -227,6 +227,18 @@ func AssertMapFieldError(t *testing.T, s map[string]*FieldError, field string, e } func TestMapDiveValidation(t *testing.T) { + + type Test struct { + Errs map[int]string `validate:"gt=0,dive,required"` + } + + test := &Test{ + Errs: map[int]string{0: "ok", 1: "", 4: "ok"}, + } + + errs := validate.Struct(test) + + fmt.Println(errs) } func TestArrayDiveValidation(t *testing.T) { @@ -509,7 +521,7 @@ func TestArrayDiveValidation(t *testing.T) { Equal(t, innerSliceError1.IsPlaceholderErr, false) Equal(t, innerSliceError1.IsSliceOrArray, false) Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0) - Equal(t, innerSliceError1.Field, "Errs[2]") + Equal(t, innerSliceError1.Field, "Errs[2][1]") Equal(t, innerSliceError1.Tag, required) type TestMultiDimensionalTimeTime2 struct { @@ -551,7 +563,7 @@ func TestArrayDiveValidation(t *testing.T) { Equal(t, innerSliceError1.IsPlaceholderErr, false) Equal(t, innerSliceError1.IsSliceOrArray, false) Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0) - Equal(t, innerSliceError1.Field, "Errs[2]") + Equal(t, innerSliceError1.Field, "Errs[2][1]") Equal(t, innerSliceError1.Tag, required) }