@@ -2245,7 +2245,11 @@ func TestCrossNamespaceFieldValidation(t *testing.T) {
22452245 Equal (t , current .String (), "<*validator.SliceStruct Value>" )
22462246 Equal (t , current .IsNil (), true )
22472247
2248- PanicMatches (t , func () { v .getStructFieldOKInternal (reflect .ValueOf (1 ), "crazyinput" ) }, "Invalid field namespace" )
2248+ // Test that invalid namespace on primitive type returns found=false instead of panicking
2249+ // This enables cross-field validators like required_if to work with ValidateMap
2250+ _ , kind , _ , ok = v .getStructFieldOKInternal (reflect .ValueOf (1 ), "crazyinput" )
2251+ Equal (t , ok , false )
2252+ Equal (t , kind , reflect .Int )
22492253}
22502254
22512255func TestExistsValidation (t * testing.T ) {
@@ -13967,6 +13971,78 @@ func TestValidate_ValidateMapCtxWithKeys(t *testing.T) {
1396713971 }
1396813972}
1396913973
13974+ // TestValidateMapWithCrossFieldValidators tests that cross-field validators
13975+ // like required_if, required_unless, etc. don't panic when used with ValidateMap.
13976+ // This is a regression test for issue #893.
13977+ //
13978+ // Note: With ValidateMap, cross-field lookups return "not found" since there's no
13979+ // struct context. Validators handle this by using their defaultNotFoundValue:
13980+ // - required_if: condition not met (returns false) → field not required
13981+ // - required_unless: condition not met (returns false) → field required
13982+ // - excluded_if: condition not met (returns false) → field not excluded
13983+ // - excluded_unless: condition not met (returns false) → field must be excluded
13984+ func TestValidateMapWithCrossFieldValidators (t * testing.T ) {
13985+ validate := New ()
13986+
13987+ // Test required_if - should not panic
13988+ // Cross-field lookup returns not found → condition not met → field not required
13989+ data := map [string ]interface {}{
13990+ "name" : "hello" ,
13991+ "id" : 123 ,
13992+ }
13993+ rules := map [string ]interface {}{
13994+ "name" : "required_if=id 345" ,
13995+ "id" : "required" ,
13996+ }
13997+ errs := validate .ValidateMap (data , rules )
13998+ Equal (t , len (errs ), 0 )
13999+
14000+ // Test required_unless - should not panic
14001+ // Cross-field lookup returns not found → condition not met → field required
14002+ // Since name has a value, validation passes
14003+ rules2 := map [string ]interface {}{
14004+ "name" : "required_unless=id 345" ,
14005+ "id" : "required" ,
14006+ }
14007+ errs = validate .ValidateMap (data , rules2 )
14008+ Equal (t , len (errs ), 0 )
14009+
14010+ // Test excluded_if - should not panic
14011+ // Cross-field lookup returns not found → condition not met → field not excluded
14012+ rules3 := map [string ]interface {}{
14013+ "name" : "excluded_if=id 123" ,
14014+ "id" : "required" ,
14015+ }
14016+ errs = validate .ValidateMap (data , rules3 )
14017+ Equal (t , len (errs ), 0 )
14018+
14019+ // Test excluded_unless - should not panic
14020+ // Cross-field lookup returns not found → condition not met → field must be excluded
14021+ // Since name has a value, validation FAILS (this is expected behavior)
14022+ rules4 := map [string ]interface {}{
14023+ "name" : "excluded_unless=id 123" ,
14024+ "id" : "required" ,
14025+ }
14026+ errs = validate .ValidateMap (data , rules4 )
14027+ Equal (t , len (errs ), 1 ) // Fails because name has value but condition can't be verified
14028+
14029+ // Test excluded_unless with empty value - should pass since field is excluded
14030+ dataEmpty := map [string ]interface {}{
14031+ "name" : "" ,
14032+ "id" : 123 ,
14033+ }
14034+ errs = validate .ValidateMap (dataEmpty , rules4 )
14035+ Equal (t , len (errs ), 0 )
14036+
14037+ // Test with empty name - required_if condition not met, so empty is ok
14038+ data2 := map [string ]interface {}{
14039+ "name" : "" ,
14040+ "id" : 123 ,
14041+ }
14042+ errs = validate .ValidateMap (data2 , rules )
14043+ Equal (t , len (errs ), 0 )
14044+ }
14045+
1397014046func TestValidate_VarWithKey (t * testing.T ) {
1397114047 validate := New ()
1397214048 errs := validate .VarWithKey ("email" , "invalidemail" , "required,email" )
0 commit comments