Skip to content

Commit 8dba811

Browse files
committed
Return ValidationError for non-string input instead of leaking AttributeError
Validators that reach for string methods on the value -- uuid via UUID(value) -> value.replace, email/hostname via value.count, cron via value.strip -- raise AttributeError when given a non-string (int, float, bool, list, dict). The validator decorator only converted (ValueError, TypeError, UnicodeError) to ValidationError, so AttributeError escaped uncaught: a caller passing untrusted or unknown-typed data to a validator got a crash instead of the documented True/ValidationError result, and inconsistently with the many validators that already return ValidationError for wrong-typed input. Add AttributeError to the decorator's caught set so this whole class of validators handles non-string input uniformly.
1 parent 70de324 commit 8dba811

2 files changed

Lines changed: 19 additions & 2 deletions

File tree

src/validators/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def wrapper(*args: Any, **kwargs: Any):
9191
if func(*args, **kwargs)
9292
else ValidationError(func, _func_args_as_dict(func, *args, **kwargs))
9393
)
94-
except (ValueError, TypeError, UnicodeError) as exp:
94+
except (ValueError, TypeError, UnicodeError, AttributeError) as exp:
9595
if raise_validation_error:
9696
raise ValidationError(
9797
func, _func_args_as_dict(func, *args, **kwargs), str(exp)

tests/test_validation_failure.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
"""Test validation Failure."""
22

3+
# external
4+
import pytest
5+
36
# local
4-
from validators import between
7+
from validators import between, cron, email, hostname, uuid
8+
from validators.utils import ValidationError
59

610
failed_obj_repr = "ValidationError(func=between"
711

@@ -31,3 +35,16 @@ def test_arguments_as_properties(self):
3135
assert self.is_in_between.__dict__["value"] == 3
3236
assert self.is_in_between.__dict__["min_val"] == 4
3337
assert self.is_in_between.__dict__["max_val"] == 5
38+
39+
40+
@pytest.mark.parametrize("validator", [uuid, email, hostname, cron])
41+
@pytest.mark.parametrize("value", [123, 1.5, True, ["x"], {"a": 1}])
42+
def test_returns_validation_error_on_non_string_input(validator, value):
43+
"""Wrong-typed input returns ValidationError, not a leaked exception.
44+
45+
These validators reach for string methods (e.g. ``.replace``/``.count``/
46+
``.strip``) on the value, which raises ``AttributeError`` for non-strings.
47+
The decorator must convert that into a ``ValidationError`` like every other
48+
invalid input, rather than letting it escape.
49+
"""
50+
assert isinstance(validator(value), ValidationError)

0 commit comments

Comments
 (0)