@@ -2183,7 +2183,11 @@ func TestCrossNamespaceFieldValidation(t *testing.T) {
21832183 Equal (t , current .String (), "<*validator.SliceStruct Value>" )
21842184 Equal (t , current .IsNil (), true )
21852185
2186- PanicMatches (t , func () { v .getStructFieldOKInternal (reflect .ValueOf (1 ), "crazyinput" ) }, "Invalid field namespace" )
2186+ // Test that invalid namespace on primitive type returns found=false instead of panicking
2187+ // This enables cross-field validators like required_if to work with ValidateMap
2188+ current , kind , _ , ok = v .getStructFieldOKInternal (reflect .ValueOf (1 ), "crazyinput" )
2189+ Equal (t , ok , false )
2190+ Equal (t , kind , reflect .Int )
21872191}
21882192
21892193func TestExistsValidation (t * testing.T ) {
@@ -13905,6 +13909,78 @@ func TestValidate_ValidateMapCtxWithKeys(t *testing.T) {
1390513909 }
1390613910}
1390713911
13912+ // TestValidateMapWithCrossFieldValidators tests that cross-field validators
13913+ // like required_if, required_unless, etc. don't panic when used with ValidateMap.
13914+ // This is a regression test for issue #893.
13915+ //
13916+ // Note: With ValidateMap, cross-field lookups return "not found" since there's no
13917+ // struct context. Validators handle this by using their defaultNotFoundValue:
13918+ // - required_if: condition not met (returns false) → field not required
13919+ // - required_unless: condition not met (returns false) → field required
13920+ // - excluded_if: condition not met (returns false) → field not excluded
13921+ // - excluded_unless: condition not met (returns false) → field must be excluded
13922+ func TestValidateMapWithCrossFieldValidators (t * testing.T ) {
13923+ validate := New ()
13924+
13925+ // Test required_if - should not panic
13926+ // Cross-field lookup returns not found → condition not met → field not required
13927+ data := map [string ]interface {}{
13928+ "name" : "hello" ,
13929+ "id" : 123 ,
13930+ }
13931+ rules := map [string ]interface {}{
13932+ "name" : "required_if=id 345" ,
13933+ "id" : "required" ,
13934+ }
13935+ errs := validate .ValidateMap (data , rules )
13936+ Equal (t , len (errs ), 0 )
13937+
13938+ // Test required_unless - should not panic
13939+ // Cross-field lookup returns not found → condition not met → field required
13940+ // Since name has a value, validation passes
13941+ rules2 := map [string ]interface {}{
13942+ "name" : "required_unless=id 345" ,
13943+ "id" : "required" ,
13944+ }
13945+ errs = validate .ValidateMap (data , rules2 )
13946+ Equal (t , len (errs ), 0 )
13947+
13948+ // Test excluded_if - should not panic
13949+ // Cross-field lookup returns not found → condition not met → field not excluded
13950+ rules3 := map [string ]interface {}{
13951+ "name" : "excluded_if=id 123" ,
13952+ "id" : "required" ,
13953+ }
13954+ errs = validate .ValidateMap (data , rules3 )
13955+ Equal (t , len (errs ), 0 )
13956+
13957+ // Test excluded_unless - should not panic
13958+ // Cross-field lookup returns not found → condition not met → field must be excluded
13959+ // Since name has a value, validation FAILS (this is expected behavior)
13960+ rules4 := map [string ]interface {}{
13961+ "name" : "excluded_unless=id 123" ,
13962+ "id" : "required" ,
13963+ }
13964+ errs = validate .ValidateMap (data , rules4 )
13965+ Equal (t , len (errs ), 1 ) // Fails because name has value but condition can't be verified
13966+
13967+ // Test excluded_unless with empty value - should pass since field is excluded
13968+ dataEmpty := map [string ]interface {}{
13969+ "name" : "" ,
13970+ "id" : 123 ,
13971+ }
13972+ errs = validate .ValidateMap (dataEmpty , rules4 )
13973+ Equal (t , len (errs ), 0 )
13974+
13975+ // Test with empty name - required_if condition not met, so empty is ok
13976+ data2 := map [string ]interface {}{
13977+ "name" : "" ,
13978+ "id" : 123 ,
13979+ }
13980+ errs = validate .ValidateMap (data2 , rules )
13981+ Equal (t , len (errs ), 0 )
13982+ }
13983+
1390813984func TestValidate_VarWithKey (t * testing.T ) {
1390913985 validate := New ()
1391013986 errs := validate .VarWithKey ("email" , "invalidemail" , "required,email" )
0 commit comments