diff --git a/options.go b/options.go index 86a0db21..aff31e51 100644 --- a/options.go +++ b/options.go @@ -24,3 +24,13 @@ func WithPrivateFieldValidation() Option { v.privateFieldValidation = true } } + +// WithEarlyExit configures the validator to immediately stop validation as soon as the first error is encountered +// +// This feature could be an opt-in behavior, allowing to opt into "early exit" validation, without breaking current workflows +// Early exit on the first failure would save time by avoiding unnecessary checks on the remaining fields +func WithEarlyExit() Option { + return func(v *Validate) { + v.earlyExit = true + } +} diff --git a/validator.go b/validator.go index 901e7b50..c9b27fb8 100644 --- a/validator.go +++ b/validator.go @@ -54,7 +54,9 @@ func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, cur var f *cField for i := 0; i < len(cs.fields); i++ { - + if v.v.earlyExit && len(v.errs) > 0 { + return + } f = cs.fields[i] if v.isPartial { diff --git a/validator_instance.go b/validator_instance.go index d9f148db..f548a7f6 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -95,6 +95,7 @@ type Validate struct { hasTagNameFunc bool requiredStructEnabled bool privateFieldValidation bool + earlyExit bool } // New returns a new instance of 'validate' with sane defaults. diff --git a/validator_test.go b/validator_test.go index 5eadb250..ca762555 100644 --- a/validator_test.go +++ b/validator_test.go @@ -12080,7 +12080,7 @@ func TestExcludedIf(t *testing.T) { test11 := struct { Field1 bool - Field2 *string `validate:"excluded_if=Field1 false"` + Field2 *string `validate:"excluded_if=Field1 false"` }{ Field1: false, Field2: nil, @@ -14123,3 +14123,72 @@ func TestPrivateFieldsStruct(t *testing.T) { Equal(t, len(errs), tc.errorNum) } } + +func TestEarlyExitStruct(t *testing.T) { + type tc struct { + stct interface{} + errorNum int + } + + tcs := []tc{ + { + stct: &struct { + f1 int8 `validate:"gt=0"` + f2 int16 `validate:"gt=0"` + f3 int32 `validate:"gt=0"` + f4 int64 `validate:"gt=0"` + }{}, + errorNum: 1, + }, + { + stct: &struct { + f1 string `validate:"min=3"` + f2 string `validate:"min=3"` + }{}, + errorNum: 1, + }, + { + stct: &struct { + f1 struct { + f2 string `validate:"min=3"` + } + f3 int8 `validate:"gt=3"` + }{}, + errorNum: 1, + }, + { + stct: &struct { + f1 string `validate:"min=3"` + f2 struct { + f3 string `validate:"min=3"` + } + }{}, + errorNum: 1, + }, + { + stct: &struct { + f1 *int `validate:"gt=0"` + f2 struct { + f3 string `validate:"min=3"` + } + }{}, + errorNum: 1, + }, + { + stct: &struct { + f1 []string `validate:"required,dive"` + f2 []int8 `validate:"required,dive"` + }{}, + errorNum: 1, + }, + } + + validate := New(WithPrivateFieldValidation(), WithEarlyExit()) + + for _, tc := range tcs { + err := validate.Struct(tc.stct) + NotEqual(t, err, nil) + errs := err.(ValidationErrors) + Equal(t, len(errs), tc.errorNum) + } +}