From d2179191d4e80d66aa4d898fee41b90efe0db73a Mon Sep 17 00:00:00 2001 From: Junaid Islam Date: Tue, 3 Jun 2025 23:42:32 +0530 Subject: [PATCH 1/2] fix: Added explicit length check for slices and maps while using omitzero as validate tags --- baked_in.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/baked_in.go b/baked_in.go index c775f5e06..f1387320e 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1794,7 +1794,10 @@ func hasValue(fl FieldLevel) bool { func hasNotZeroValue(fl FieldLevel) bool { field := fl.Field() switch field.Kind() { - case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: + case reflect.Slice, reflect.Map: + // For slices and maps, consider them "not zero" only if they're both non-nil AND have elements + return !field.IsNil() && field.Len() > 0 + case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: return !field.IsNil() default: if fl.(*validate).fldIsPointer && getValue(field) != nil { From dbe35223ee8b2571f7ae5f6a3473f2ae2e0012e6 Mon Sep 17 00:00:00 2001 From: Junaid Islam Date: Tue, 3 Jun 2025 23:44:01 +0530 Subject: [PATCH 2/2] test: added validation tests for omitzero tag with empty maps and slices --- validator_test.go | 120 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/validator_test.go b/validator_test.go index 65a216f12..00a410101 100644 --- a/validator_test.go +++ b/validator_test.go @@ -14633,3 +14633,123 @@ func TestMapEmptyPointerStructValueNoError(t *testing.T) { errs = validate.Struct(obj2) Equal(t, errs, nil) } +func TestOmitZeroWithSlices(t *testing.T) { + // Tests the behavior of omitempty and omitzero with slices + type OmitEmptyExample struct { + Data []string `validate:"omitempty,min=2"` + } + + type OmitZeroExample struct { + Data []string `validate:"omitzero,min=2"` + } + + validate := New() + + t.Run("nil slices", func(t *testing.T) { + // Test 1: nil slices + test1Empty := OmitEmptyExample{Data: nil} + test1Zero := OmitZeroExample{Data: nil} + + err1 := validate.Struct(test1Empty) + err2 := validate.Struct(test1Zero) + + Equal(t, err1, nil) // No error (skipped) + Equal(t, err2, nil) // No error (skipped) + }) + + t.Run("empty but non-nil slices", func(t *testing.T) { + // Test 2: empty but non-nil slices + test2Empty := OmitEmptyExample{Data: []string{}} + test2Zero := OmitZeroExample{Data: []string{}} + + err1 := validate.Struct(test2Empty) + err2 := validate.Struct(test2Zero) + + NotEqual(t, err1, nil) // Error (min=2) + Equal(t, err2, nil) // No error (should be skipped) + }) + + t.Run("single item slices", func(t *testing.T) { + // Test 3: single item slices (still not meeting min=2) + test3Empty := OmitEmptyExample{Data: []string{"one"}} + test3Zero := OmitZeroExample{Data: []string{"one"}} + + err1 := validate.Struct(test3Empty) + err2 := validate.Struct(test3Zero) + + NotEqual(t, err1, nil) // Error (min=2) + NotEqual(t, err2, nil) // Error (min=2) + }) + + t.Run("valid slices", func(t *testing.T) { + // Test 4: valid slices (min=2 satisfied) + test4Empty := OmitEmptyExample{Data: []string{"one", "two"}} + test4Zero := OmitZeroExample{Data: []string{"one", "two"}} + + err1 := validate.Struct(test4Empty) + err2 := validate.Struct(test4Zero) + + Equal(t, err1, nil) // No error + Equal(t, err2, nil) // No error + }) +} +func TestOmitZeroWithMaps(t *testing.T) { + // Tests the behavior of omitempty and omitzero with maps + type OmitEmptyExample struct { + Data map[string]string `validate:"omitempty,min=2"` + } + + type OmitZeroExample struct { + Data map[string]string `validate:"omitzero,min=2"` + } + + validate := New() + + t.Run("nil maps", func(t *testing.T) { + // Test 1: nil maps + test1Empty := OmitEmptyExample{Data: nil} + test1Zero := OmitZeroExample{Data: nil} + + err1 := validate.Struct(test1Empty) + err2 := validate.Struct(test1Zero) + + Equal(t, err1, nil) // No error (skipped) + Equal(t, err2, nil) // No error (skipped) + }) + + t.Run("empty but non-nil maps", func(t *testing.T) { + // Test 2: empty but non-nil maps + test2Empty := OmitEmptyExample{Data: map[string]string{}} + test2Zero := OmitZeroExample{Data: map[string]string{}} + + err1 := validate.Struct(test2Empty) + err2 := validate.Struct(test2Zero) + + NotEqual(t, err1, nil) // Error (min=2) + Equal(t, err2, nil) // No error (should be skipped) + }) + + t.Run("single item maps", func(t *testing.T) { + // Test 3: single item maps (still not meeting min=2) + test3Empty := OmitEmptyExample{Data: map[string]string{"key1": "value1"}} + test3Zero := OmitZeroExample{Data: map[string]string{"key1": "value1"}} + + err1 := validate.Struct(test3Empty) + err2 := validate.Struct(test3Zero) + + NotEqual(t, err1, nil) // Error (min=2) + NotEqual(t, err2, nil) // Error (min=2) + }) + + t.Run("valid maps", func(t *testing.T) { + // Test 4: valid maps (min=2 satisfied) + test4Empty := OmitEmptyExample{Data: map[string]string{"key1": "value1", "key2": "value2"}} + test4Zero := OmitZeroExample{Data: map[string]string{"key1": "value1", "key2": "value2"}} + + err1 := validate.Struct(test4Empty) + err2 := validate.Struct(test4Zero) + + Equal(t, err1, nil) // No error + Equal(t, err2, nil) // No error + }) +}