From 07b2c63e5427d9a94696c2988c0edae7be24884a Mon Sep 17 00:00:00 2001 From: leftjs Date: Sat, 19 Mar 2022 16:52:58 +0800 Subject: [PATCH] Enhanced ValidationCtx method to support nested map in slice #915 --- _examples/map-validation/main.go | 14 +++++ validator_instance.go | 25 ++++++--- validator_test.go | 93 +++++++++++++++++++++++++++++++- 3 files changed, 123 insertions(+), 9 deletions(-) diff --git a/_examples/map-validation/main.go b/_examples/map-validation/main.go index 92e282f5f..dfcfa4f76 100644 --- a/_examples/map-validation/main.go +++ b/_examples/map-validation/main.go @@ -48,6 +48,16 @@ func validateNestedMap() { "mother_name": "Hannah", }, "salary": "1000", + "phones": []map[string]interface{}{ + { + "number": "11-111-1111", + "remark": "home", + }, + { + "number": "22-222-2222", + "remark": "work", + }, + }, }, } @@ -62,6 +72,10 @@ func validateNestedMap() { "mother_name": "required,min=4,max=32", }, "salary": "number", + "phones": map[string]interface{}{ + "number": "required,min=4,max=32", + "remark": "required,min=1,max=32", + }, }, } diff --git a/validator_instance.go b/validator_instance.go index 973964fc2..648738378 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -152,15 +152,24 @@ func (v *Validate) SetTagName(name string) { func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{}, rules map[string]interface{}) map[string]interface{} { errs := make(map[string]interface{}) for field, rule := range rules { - if reflect.ValueOf(rule).Kind() == reflect.Map && reflect.ValueOf(data[field]).Kind() == reflect.Map { - err := v.ValidateMapCtx(ctx, data[field].(map[string]interface{}), rule.(map[string]interface{})) - if len(err) > 0 { - errs[field] = err + if ruleObj, ok := rule.(map[string]interface{}); ok { + if dataObj, ok := data[field].(map[string]interface{}); ok { + err := v.ValidateMapCtx(ctx, dataObj, ruleObj) + if len(err) > 0 { + errs[field] = err + } + } else if dataObjs, ok := data[field].([]map[string]interface{}); ok { + for _, obj := range dataObjs { + err := v.ValidateMapCtx(ctx, obj, ruleObj) + if len(err) > 0 { + errs[field] = err + } + } + } else { + errs[field] = errors.New("The field: '" + field + "' is not a map to dive") } - } else if reflect.ValueOf(rule).Kind() == reflect.Map { - errs[field] = errors.New("The field: '" + field + "' is not a map to dive") - } else { - err := v.VarCtx(ctx, data[field], rule.(string)) + } else if ruleStr, ok := rule.(string); ok { + err := v.VarCtx(ctx, data[field], ruleStr) if err != nil { errs[field] = err } diff --git a/validator_test.go b/validator_test.go index 3730fb9d6..0532aaee6 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11460,7 +11460,7 @@ func TestSemverFormatValidation(t *testing.T) { } } } - + func TestRFC1035LabelFormatValidation(t *testing.T) { tests := []struct { value string `validate:"dns_rfc1035_label"` @@ -11611,3 +11611,94 @@ func TestPostCodeByIso3166Alpha2Field_InvalidKind(t *testing.T) { _ = New().Struct(test{"ABC", 123, false}) t.Errorf("Didn't panic as expected") } + +func TestValidate_ValidateMapCtx(t *testing.T) { + + type args struct { + data map[string]interface{} + rules map[string]interface{} + } + tests := []struct { + name string + args args + want int + }{ + { + name: "test nested map in slice", + args: args{ + data: map[string]interface{}{ + "Test_A": map[string]interface{}{ + "Test_B": "Test_B", + "Test_C": []map[string]interface{}{ + { + "Test_D": "Test_D", + }, + }, + "Test_E": map[string]interface{}{ + "Test_F": "Test_F", + }, + }, + }, + rules: map[string]interface{}{ + "Test_A": map[string]interface{}{ + "Test_B": "min=2", + "Test_C": map[string]interface{}{ + "Test_D": "min=2", + }, + "Test_E": map[string]interface{}{ + "Test_F": "min=2", + }, + }, + }, + }, + want: 0, + }, + + { + name: "test nested map error", + args: args{ + data: map[string]interface{}{ + "Test_A": map[string]interface{}{ + "Test_B": "Test_B", + "Test_C": []interface{}{"Test_D"}, + "Test_E": map[string]interface{}{ + "Test_F": "Test_F", + }, + "Test_G": "Test_G", + "Test_I": []map[string]interface{}{ + { + "Test_J": "Test_J", + }, + }, + }, + }, + rules: map[string]interface{}{ + "Test_A": map[string]interface{}{ + "Test_B": "min=2", + "Test_C": map[string]interface{}{ + "Test_D": "min=2", + }, + "Test_E": map[string]interface{}{ + "Test_F": "min=100", + }, + "Test_G": map[string]interface{}{ + "Test_H": "min=2", + }, + "Test_I": map[string]interface{}{ + "Test_J": "min=100", + }, + }, + }, + }, + want: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + validate := New() + if got := validate.ValidateMapCtx(context.Background(), tt.args.data, tt.args.rules); len(got) != tt.want { + t.Errorf("ValidateMapCtx() = %v, want %v", got, tt.want) + } + }) + } +}