From 8c315f2c840bc749269119a585c1826a342d7d57 Mon Sep 17 00:00:00 2001 From: Rofiq Date: Thu, 10 Jul 2025 22:24:32 +0700 Subject: [PATCH] feat: new option for multiple error returned --- options.go | 10 +++++ validator.go | 16 +++++-- validator_instance.go | 1 + validator_test.go | 97 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 4 deletions(-) diff --git a/options.go b/options.go index 86a0db218..3ddd284e0 100644 --- a/options.go +++ b/options.go @@ -24,3 +24,13 @@ func WithPrivateFieldValidation() Option { v.privateFieldValidation = true } } + +// WithMultipleErrorsReturned enables multi error return from a single struct field. +// +// By opting into this feature you are acknowledging that you are aware of the risks and accept any current or future +// consequences of using this feature. +func WithMultipleErrorsReturned() Option { + return func(v *Validate) { + v.multipleErrorsReturned = true + } +} diff --git a/validator.go b/validator.go index 995b0e19a..7198e7ef9 100644 --- a/validator.go +++ b/validator.go @@ -135,7 +135,9 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr kind: kind, }, ) - return + if !v.v.multipleErrorsReturned { + return + } } v.str1 = string(append(ns, cf.altName...)) @@ -160,7 +162,9 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr typ: current.Type(), }, ) - return + if !v.v.multipleErrorsReturned { + return + } } } @@ -453,7 +457,9 @@ OUTER: ) } - return + if !v.v.multipleErrorsReturned { + return + } } ct = ct.next @@ -492,7 +498,9 @@ OUTER: }, ) - return + if !v.v.multipleErrorsReturned { + return + } } ct = ct.next } diff --git a/validator_instance.go b/validator_instance.go index 9362cd731..94be605af 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -96,6 +96,7 @@ type Validate struct { hasTagNameFunc bool requiredStructEnabled bool privateFieldValidation bool + multipleErrorsReturned bool } // New returns a new instance of 'validate' with sane defaults. diff --git a/validator_test.go b/validator_test.go index 2fcb3ffc3..89808a5a9 100644 --- a/validator_test.go +++ b/validator_test.go @@ -14975,3 +14975,100 @@ func TestRequiredIfWithArrays(t *testing.T) { Equal(t, err, nil) // No error - Text has value }) } + +func TestMultipleErrorsOption(t *testing.T) { + type tc struct { + stct interface{} + errorNum int + } + + tcs := []tc{ + { + stct: &struct { + F1 int8 `validate:"eq=10,gte=10"` + F2 int16 `validate:"eq=10,gte=10"` + F3 int32 `validate:"eq=10,gte=10"` + F4 int64 `validate:"eq=10,gte=10"` + }{}, + errorNum: 8, + }, + { + stct: &struct { + F1 uint8 `validate:"eq=10,gte=10"` + F2 uint16 `validate:"eq=10,gte=10"` + F3 uint32 `validate:"eq=10,gte=10"` + F4 uint64 `validate:"eq=10,gte=10"` + }{ + F1: 100, + }, + errorNum: 7, + }, + { + stct: &struct { + F1 string `validate:"eq=10,gte=10"` + F2 string `validate:"eq=10,gte=10"` + }{}, + errorNum: 4, + }, + { + stct: &struct { + F1 float32 `validate:"eq=10,gte=10"` + F2 float64 `validate:"eq=10,gte=10"` + }{}, + errorNum: 4, + }, + { + stct: struct { + F1 int8 `validate:"eq=10,gte=10"` + F2 int16 `validate:"eq=10,gte=10"` + F3 int32 `validate:"eq=10,gte=10"` + F4 int64 `validate:"eq=10,gte=10"` + }{}, + errorNum: 8, + }, + { + stct: struct { + F1 uint8 `validate:"eq=10,gte=10"` + F2 uint16 `validate:"eq=10,gte=10"` + F3 uint32 `validate:"eq=10,gte=10"` + F4 uint64 `validate:"eq=10,gte=10"` + }{}, + errorNum: 8, + }, + { + stct: struct { + F1 float32 `validate:"eq=10,gte=10"` + F2 float64 `validate:"eq=10,gte=10"` + }{}, + errorNum: 4, + }, + { + stct: struct { + F1 int `validate:"eq=10,gte=10"` + F2 struct { + F3 int `validate:"eq=10,gte=10"` + } + }{}, + errorNum: 4, + }, + { + stct: &struct { + F1 int `validate:"eq=10,gte=10"` + F2 struct { + F3 int `validate:"eq=10,gte=10"` + } + }{}, + errorNum: 4, + }, + } + + validate := New(WithMultipleErrorsReturned()) + + for _, tc := range tcs { + err := validate.Struct(tc.stct) + NotEqual(t, err, nil) + + errs := err.(ValidationErrors) + Equal(t, len(errs), tc.errorNum) + } +}