From f8202a8bae37eb2d872a4001ba46bddab41728fb Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Mon, 28 Sep 2020 05:56:00 +0300 Subject: [PATCH] skip validation of the nested struct itself when its inner fields are invalid --- validator.go | 4 ++++ validator_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/validator.go b/validator.go index ef9656b..22c3a6b 100644 --- a/validator.go +++ b/validator.go @@ -188,10 +188,14 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr } vFldIsPointerOld := v.fldIsPointer + previousErrorsCount := len(v.errs) v.validateStruct(ctx, current, current, typ, ns, structNs, ct) v.fldIsPointer = vFldIsPointerOld ns = oldNs structNs = oldStructNs + if len(v.errs) > previousErrorsCount { + skipValidations = true + } } if skipValidations { return diff --git a/validator_test.go b/validator_test.go index 8c57e32..573deda 100644 --- a/validator_test.go +++ b/validator_test.go @@ -749,12 +749,35 @@ func TestStructValidation(t *testing.T) { val.RegisterValidation("one", fn1) val.RegisterValidation("two", fn2) errs := val.Struct(ts) - Equal(t, len(errs.(ValidationErrors)), 3) + Equal(t, len(errs.(ValidationErrors)), 2) + // TestStruct.Test.Test gets validated because TestStruct.Test != nil. + // Validators of TestStruct.Test are skipped because TestStruct.Test.Test is invalid AssertError(t, errs, "TestStruct.Test.Test", "TestStruct.Test.Test", "Test", "Test", "required") - AssertError(t, errs, "TestStruct.Test", "TestStruct.Test", "Test", "Test", "one") AssertError(t, errs, "TestStruct.Test2", "TestStruct.Test2", "Test2", "Test2", "two") } +func TestStructValidation_SkipsInnerFieldsOfNullNestedStructs(t *testing.T) { + type inner struct { + Test string `validate:"required"` + } + type TestStruct struct { + Test *inner `validate:"one,dive"` + } + + ts := TestStruct{} + val := New() + + fn1 := func(fl FieldLevel) bool { + return fl.Field().FieldByName("Test").String() == "some value" + } + + val.RegisterValidation("one", fn1) + errs := val.Struct(ts) + Equal(t, len(errs.(ValidationErrors)), 1) + // TestStruct.Test.Test hasn't been validated because TestStruct.Test is nil. + AssertError(t, errs, "TestStruct.Test", "TestStruct.Test", "Test", "Test", "one") +} + func TestStructPartial(t *testing.T) { p1 := []string{ "NoTag", @@ -1051,7 +1074,7 @@ func TestCrossStructLteFieldValidation(t *testing.T) { // this test is for the WARNING about unforeseen validation issues. errs = validate.VarWithValue(test, now, "ltecsfield") NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 7) + Equal(t, len(errs.(ValidationErrors)), 6) AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "ltecsfield") AssertError(t, errs, "Test.String", "Test.String", "String", "String", "ltecsfield") AssertError(t, errs, "Test.Int", "Test.Int", "Int", "Int", "ltecsfield") @@ -8163,9 +8186,26 @@ func TestIsDefault(t *testing.T) { Equal(t, fe.Field(), "String") Equal(t, fe.Namespace(), "Test2.inner.String") Equal(t, fe.Tag(), "isdefault") - fe = errs.(ValidationErrors)[1] + + type Inner3 struct { + String string + } + + type Test3 struct { + Inner Inner3 `validate:"isdefault" json:"inner"` + } + + var t3 Test3 + errs = validate.Struct(t3) + Equal(t, errs, nil) + + t3.Inner.String = "Changed" + errs = validate.Struct(t3) + NotEqual(t, errs, nil) + + fe = errs.(ValidationErrors)[0] Equal(t, fe.Field(), "inner") - Equal(t, fe.Namespace(), "Test2.inner") + Equal(t, fe.Namespace(), "Test3.inner") Equal(t, fe.Tag(), "isdefault") }