diff --git a/src/masonite/request/validation.py b/src/masonite/request/validation.py index 957752a5..ae97c9bd 100644 --- a/src/masonite/request/validation.py +++ b/src/masonite/request/validation.py @@ -8,7 +8,7 @@ class ValidatesRequest: """Request mixin to add inputs validation to requests.""" - def validate(self, *rules: "str|dict|RuleEnclosure") -> "MessageBag": + def validate(self, *rules: "str|dict|RuleEnclosure", messages={}) -> "MessageBag": """Validate request inputs against the given rules.""" validator = Validator() - return validator.validate(self.all(), *rules) + return validator.validate(self.all(), *rules, messages=messages) diff --git a/src/masonite/validation/Validator.py b/src/masonite/validation/Validator.py index 4c0886b4..940bb8e3 100644 --- a/src/masonite/validation/Validator.py +++ b/src/masonite/validation/Validator.py @@ -61,7 +61,6 @@ def handle(self, dictionary): for key in self.validations: if self.negated: - if self.passes(self.find(key, dictionary), key, dictionary): boolean = False if hasattr(self, "negated_message"): @@ -167,7 +166,7 @@ def negated_message(self, attribute): class boolean(BaseValidation): def passes(self, attribute, key, dictionary): - return (attribute in [True, False, 0, 1, '0', '1']) + return attribute in [True, False, 0, 1, "0", "1"] def message(self, attribute): return "The {} must be a boolean.".format(attribute) @@ -580,7 +579,6 @@ def __init__(self, validations, min=1, max=255, messages={}, raises={}): self.max = max def passes(self, attribute, key, dictionary): - attribute = str(attribute) if attribute.isalpha(): @@ -1330,16 +1328,19 @@ def negated_message(self, attribute): class Validator: + messages = {} + def __init__(self): pass - def validate(self, dictionary, *rules): + def validate(self, dictionary, *rules, messages={}): + self.messages = messages rule_errors = {} try: for rule in rules: if isinstance(rule, str): rule = self.parse_string(rule) - # continue + elif isinstance(rule, dict): rule = self.parse_dict(rule, dictionary, rule_errors) continue @@ -1350,6 +1351,7 @@ def validate(self, dictionary, *rules): rule.handle(dictionary) for error, message in rule.errors.items(): + message = self.custom_message(error, rule) or message if error not in rule_errors: rule_errors.update({error: message}) else: @@ -1365,6 +1367,31 @@ def validate(self, dictionary, *rules): return MessageBag(rule_errors) + def custom_message(self, attribute, rule): + path = [] + # Check if rule is negated + if rule.__class__.__name__ == "isnt": + for validation in rule.validations: + validation_name = ( + validation.__class__.__name__ + if type(validation) is not str + else validation + ) + + path.append( + f"isnt_{validation_name}" + if validation.negated + else validation_name + ) + else: + path.append(rule.__class__.__name__) + + dot_name = ".".join([attribute] + path) + + if dot_name in self.messages: + return [self.messages[dot_name]] + return None + def parse_string(self, rule): rule, parameters = rule.split(":")[0], rule.split(":")[1].split(",") return ValidationFactory().registry[rule](parameters) @@ -1377,6 +1404,8 @@ def parse_dict(self, rule, dictionary, rule_errors): rule.handle(dictionary) for error, message in rule.errors.items(): + message = self.custom_message(value, rule) or message + if error not in rule_errors: rule_errors.update({error: message}) else: @@ -1389,6 +1418,8 @@ def run_enclosure(self, enclosure, dictionary): for rule in enclosure.rules(): rule.handle(dictionary) for error, message in rule.errors.items(): + message = self.custom_message(error, rule) or message + if error not in rule_errors: rule_errors.update({error: message}) else: @@ -1419,7 +1450,6 @@ def __getattr__(self, name): class ValidationFactory: - registry = {} def __init__(self): diff --git a/tests/features/validation/test_request_validation.py b/tests/features/validation/test_request_validation.py index b2ac36e6..02259c61 100644 --- a/tests/features/validation/test_request_validation.py +++ b/tests/features/validation/test_request_validation.py @@ -1,6 +1,8 @@ from tests import TestCase +from masonite.validation import email, isnt, is_in, numeric from src.masonite.validation import Validator + class TestValidation(TestCase): def test_can_validate_request(self): request = self.make_request(query_string="email=joe@masoniteproject.com") @@ -22,6 +24,55 @@ def test_can_validate_request_with_no_inputs(self): self.assertEqual(validation.all(), {"email": ["The email field is required."]}) + def test_custom_messages(self): + request = self.make_request(post_data={"not_email": "joe@masonite.com", "age": 20}) + # validator = Validator() + validate = request.validate( + isnt(is_in("age", [20, 21]), numeric("age")), + isnt(email("not_email")), + { + "username": "required", + "valid_email": "required|email", + "secondary_email": "required|email", + }, + messages={ + "username.required": "Custom Message for required Username.", + "not_email.isnt_email": "Custom Message for Email not being an email.", + "valid_email.required": "Custom Message for required Email.", + "valid_email.email": "Custom Message for Email being a valid email.", + "age.isnt_is_in.isnt_numeric": "Custom: Age must not be in 20, 21 and must not be numeric.", + }, + ) + + self.assertIn( + "Custom: Age must not be in 20, 21 and must not be numeric.", + validate.get("age"), + ) + self.assertIn( + "Custom Message for required Email.", + validate.get("valid_email"), + ) + self.assertIn( + "Custom Message for Email being a valid email.", + validate.get("valid_email"), + ) + self.assertIn( + "Custom Message for Email not being an email.", + validate.get("not_email"), + ) + self.assertIn( + "Custom Message for required Username.", + validate.get("username"), + ) + self.assertIn( + "The secondary_email field is required.", + validate.get("secondary_email"), + ) + self.assertIn( + "The secondary_email must be a valid email address.", + validate.get("secondary_email"), + ) + def test_can_forward_validation_calls(self): request = self.make_request(query_string="") validate = Validator() @@ -34,3 +85,4 @@ def test_can_forward_validation_calls(self): self.assertIn("The user field is required.", errors.get("user")) self.assertIn("The email field is required.", errors.get("email")) self.assertIn("The terms must be accepted.", errors.get("terms")) + diff --git a/tests/features/validation/test_validation.py b/tests/features/validation/test_validation.py index 45e5ba50..c43d5cf6 100644 --- a/tests/features/validation/test_validation.py +++ b/tests/features/validation/test_validation.py @@ -1306,8 +1306,7 @@ def test_distinct_with_simple_list(self): self.assertEqual( validate.get("emails"), ["The emails field has duplicate values."] ) - - + class TestDotNotationValidation(unittest.TestCase): def setUp(self): pass