Skip to content

Commit ec374ef

Browse files
Fix: Validator panics when 'nil' is used along with required if for slices and maps (#1442)
## Fixes Added support to check for 'nil' values in case of maps and slices when used with required if. (Previously only lengths were being checked as a result using nil with required if would lead to a panic since the program tried to convert 'nil' to an integer) Created a separate case for arrays to only check lengths since arrays cannot be nil in go. Check #1437 for more details. **Make sure that you've checked the boxes below before you submit PR:** - [x] Tests exist or have been written that cover this particular change. @go-playground/validator-maintainers
1 parent 0e3e2f9 commit ec374ef

File tree

2 files changed

+227
-1
lines changed

2 files changed

+227
-1
lines changed

baked_in.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1853,7 +1853,13 @@ func requireCheckFieldValue(
18531853
case reflect.Float64:
18541854
return field.Float() == asFloat64(value)
18551855

1856-
case reflect.Slice, reflect.Map, reflect.Array:
1856+
case reflect.Slice, reflect.Map:
1857+
if value == "nil" {
1858+
return field.IsNil()
1859+
}
1860+
return int64(field.Len()) == asInt(value)
1861+
case reflect.Array:
1862+
// Arrays can't be nil, so only compare lengths
18571863
return int64(field.Len()) == asInt(value)
18581864

18591865
case reflect.Bool:

validator_test.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14753,3 +14753,223 @@ func TestOmitZeroWithMaps(t *testing.T) {
1475314753
Equal(t, err2, nil) // No error
1475414754
})
1475514755
}
14756+
func TestRequiredIfWithNilBytesSlice(t *testing.T) {
14757+
// Tests the behavior of required_if with nil byte slices
14758+
type TextOrBytes struct {
14759+
Text string `validate:"required_if=Bytes nil"`
14760+
Bytes []byte `validate:"required_if=Text nil"`
14761+
}
14762+
14763+
validate := New(WithRequiredStructEnabled())
14764+
14765+
t.Run("Text empty, Bytes empty but not nil", func(t *testing.T) {
14766+
// Test 1: Text empty, Bytes empty but not nil
14767+
obj := TextOrBytes{
14768+
Text: "",
14769+
Bytes: []byte{},
14770+
}
14771+
err := validate.Struct(obj)
14772+
14773+
Equal(t, err, nil) // No error - Bytes is not nil, just empty
14774+
})
14775+
14776+
t.Run("Text empty, Bytes nil", func(t *testing.T) {
14777+
// Test 2: Text empty, Bytes nil
14778+
obj := TextOrBytes{
14779+
Text: "",
14780+
Bytes: nil,
14781+
}
14782+
err := validate.Struct(obj)
14783+
14784+
NotEqual(t, err, nil) // Error - Text is required when Bytes is nil
14785+
})
14786+
14787+
t.Run("Text populated, Bytes nil", func(t *testing.T) {
14788+
// Test 3: Text populated, Bytes nil
14789+
obj := TextOrBytes{
14790+
Text: "Hello",
14791+
Bytes: nil,
14792+
}
14793+
err := validate.Struct(obj)
14794+
14795+
Equal(t, err, nil) // No error - Text has value when Bytes is nil
14796+
})
14797+
14798+
t.Run("Text populated, Bytes empty but not nil", func(t *testing.T) {
14799+
// Test 4: Text populated, Bytes empty but not nil
14800+
obj := TextOrBytes{
14801+
Text: "Hello",
14802+
Bytes: []byte{},
14803+
}
14804+
err := validate.Struct(obj)
14805+
14806+
Equal(t, err, nil) // No error - Neither field is nil
14807+
})
14808+
14809+
t.Run("Text empty, Bytes populated", func(t *testing.T) {
14810+
// Test 5: Text empty, Bytes populated
14811+
obj := TextOrBytes{
14812+
Text: "",
14813+
Bytes: []byte("World"),
14814+
}
14815+
err := validate.Struct(obj)
14816+
14817+
Equal(t, err, nil) // No error - Bytes has value when Text is empty
14818+
})
14819+
14820+
t.Run("Both fields populated", func(t *testing.T) {
14821+
// Test 6: Both fields populated
14822+
obj := TextOrBytes{
14823+
Text: "Hello",
14824+
Bytes: []byte("World"),
14825+
}
14826+
err := validate.Struct(obj)
14827+
14828+
Equal(t, err, nil) // No error - Both fields have values
14829+
})
14830+
}
14831+
func TestRequiredIfWithMaps(t *testing.T) {
14832+
// Tests the behavior of required_if with maps
14833+
type TextOrMap struct {
14834+
Text string `validate:"required_if=Data nil"`
14835+
Data map[string]interface{} `validate:"required_if=Text nil"`
14836+
}
14837+
14838+
validate := New(WithRequiredStructEnabled())
14839+
14840+
t.Run("Text empty, Map nil", func(t *testing.T) {
14841+
// Test 1: Text empty, Map nil
14842+
obj := TextOrMap{
14843+
Text: "",
14844+
Data: nil,
14845+
}
14846+
err := validate.Struct(obj)
14847+
14848+
NotEqual(t, err, nil) // Error - Text is required when Data is nil
14849+
})
14850+
14851+
t.Run("Text populated, Map nil", func(t *testing.T) {
14852+
// Test 2: Text populated, Map nil
14853+
obj := TextOrMap{
14854+
Text: "Hello",
14855+
Data: nil,
14856+
}
14857+
err := validate.Struct(obj)
14858+
14859+
Equal(t, err, nil) // No error - Text has value when Data is nil
14860+
})
14861+
14862+
t.Run("Text empty, Map empty but not nil", func(t *testing.T) {
14863+
// Test 3: Text empty, Map empty but not nil
14864+
obj := TextOrMap{
14865+
Text: "",
14866+
Data: map[string]interface{}{},
14867+
}
14868+
err := validate.Struct(obj)
14869+
14870+
Equal(t, err, nil) // No error - Data is not nil, just empty
14871+
})
14872+
14873+
t.Run("Text populated, Map empty but not nil", func(t *testing.T) {
14874+
// Test 4: Text populated, Map empty but not nil
14875+
obj := TextOrMap{
14876+
Text: "Hello",
14877+
Data: map[string]interface{}{},
14878+
}
14879+
err := validate.Struct(obj)
14880+
14881+
Equal(t, err, nil) // No error - Neither field is nil
14882+
})
14883+
14884+
t.Run("Text empty, Map populated", func(t *testing.T) {
14885+
// Test 5: Text empty, Map populated
14886+
obj := TextOrMap{
14887+
Text: "",
14888+
Data: map[string]interface{}{"key": "value"},
14889+
}
14890+
err := validate.Struct(obj)
14891+
14892+
Equal(t, err, nil) // No error - Data has value when Text is empty
14893+
})
14894+
14895+
t.Run("Both fields populated", func(t *testing.T) {
14896+
// Test 6: Both fields populated
14897+
obj := TextOrMap{
14898+
Text: "Hello",
14899+
Data: map[string]interface{}{"key": "value"},
14900+
}
14901+
err := validate.Struct(obj)
14902+
14903+
Equal(t, err, nil) // No error - Both fields have values
14904+
})
14905+
}
14906+
func TestRequiredIfWithArrays(t *testing.T) {
14907+
// Tests the behavior of required_if with arrays
14908+
type TextOrArray struct {
14909+
Text string `validate:"required_if=Data 3"` // Check if array length is 3
14910+
Data [3]string `validate:"required_if=Text nil"` // Check if Text is empty
14911+
}
14912+
14913+
// Also test with a different length to verify behavior
14914+
type TextOrSmallArray struct {
14915+
Text string `validate:"required_if=Data 2"` // Check if array length is 2 (which it never will be for [3]string)
14916+
Data [3]string `validate:"required_if=Text nil"` // Check if Text is empty
14917+
}
14918+
14919+
validate := New(WithRequiredStructEnabled())
14920+
14921+
t.Run("Array length always matches declared size", func(t *testing.T) {
14922+
// Test 1: Text empty, Array has length 3
14923+
obj := TextOrArray{
14924+
Text: "",
14925+
Data: [3]string{}, // Zero values but length is 3
14926+
}
14927+
err := validate.Struct(obj)
14928+
14929+
NotEqual(t, err, nil) // Error - Text is required when Data length equals 3
14930+
})
14931+
14932+
t.Run("Text populated, Array always has declared length", func(t *testing.T) {
14933+
// Test 2: Text populated, Array has length 3
14934+
obj := TextOrArray{
14935+
Text: "Hello",
14936+
Data: [3]string{},
14937+
}
14938+
err := validate.Struct(obj)
14939+
14940+
Equal(t, err, nil) // No error - Text has value
14941+
})
14942+
14943+
t.Run("Length check that never matches doesn't trigger validation", func(t *testing.T) {
14944+
// Test 3: Text empty, Array has length 3 but we check for length 2
14945+
obj := TextOrSmallArray{
14946+
Text: "",
14947+
Data: [3]string{},
14948+
}
14949+
err := validate.Struct(obj)
14950+
14951+
Equal(t, err, nil) // No error - Data length never equals 2
14952+
})
14953+
14954+
t.Run("Array values don't affect length check", func(t *testing.T) {
14955+
// Test 4: Text empty, Array partially populated but still length 3
14956+
obj := TextOrArray{
14957+
Text: "",
14958+
Data: [3]string{"One", "Two", ""},
14959+
}
14960+
err := validate.Struct(obj)
14961+
14962+
NotEqual(t, err, nil) // Error - Text is required when Data length equals 3
14963+
})
14964+
14965+
t.Run("Array values with Text populated passes validation", func(t *testing.T) {
14966+
// Test 5: Text populated, Array with values
14967+
obj := TextOrArray{
14968+
Text: "Hello",
14969+
Data: [3]string{"One", "Two", "Three"},
14970+
}
14971+
err := validate.Struct(obj)
14972+
14973+
Equal(t, err, nil) // No error - Text has value
14974+
})
14975+
}

0 commit comments

Comments
 (0)