diff --git a/aws_lambda_powertools/utilities/validation/base.py b/aws_lambda_powertools/utilities/validation/base.py index 13deb4d24e2..2a337b85971 100644 --- a/aws_lambda_powertools/utilities/validation/base.py +++ b/aws_lambda_powertools/utilities/validation/base.py @@ -32,6 +32,15 @@ def validate_data_against_schema(data: Union[Dict, str], schema: Dict, formats: fastjsonschema.validate(definition=schema, data=data, formats=formats) except (TypeError, AttributeError, fastjsonschema.JsonSchemaDefinitionException) as e: raise InvalidSchemaFormatError(f"Schema received: {schema}, Formats: {formats}. Error: {e}") - except fastjsonschema.JsonSchemaException as e: - message = f"Failed schema validation. Error: {e.message}, Path: {e.path}, Data: {e.value}" # noqa: B306, E501 - raise SchemaValidationError(message) + except fastjsonschema.JsonSchemaValueException as e: + message = f"Failed schema validation. Error: {e.message}, Path: {e.path}, Data: {e.value}" + raise SchemaValidationError( + message, + validation_message=e.message, + name=e.name, + path=e.path, + value=e.value, + definition=e.definition, + rule=e.rule, + rule_definition=e.rule_definition, + ) diff --git a/aws_lambda_powertools/utilities/validation/exceptions.py b/aws_lambda_powertools/utilities/validation/exceptions.py index d4aaa500ec7..7c719ca3119 100644 --- a/aws_lambda_powertools/utilities/validation/exceptions.py +++ b/aws_lambda_powertools/utilities/validation/exceptions.py @@ -1,9 +1,59 @@ +from typing import Any, List, Optional + from ...exceptions import InvalidEnvelopeExpressionError class SchemaValidationError(Exception): """When serialization fail schema validation""" + def __init__( + self, + message: str, + validation_message: Optional[str] = None, + name: Optional[str] = None, + path: Optional[List] = None, + value: Optional[Any] = None, + definition: Optional[Any] = None, + rule: Optional[str] = None, + rule_definition: Optional[Any] = None, + ): + """ + + Parameters + ---------- + message : str + Powertools formatted error message + validation_message : str, optional + Containing human-readable information what is wrong + (e.g. `data.property[index] must be smaller than or equal to 42`) + name : str, optional + name of a path in the data structure + (e.g. `data.property[index]`) + path: List, optional + `path` as an array in the data structure + (e.g. `['data', 'property', 'index']`), + value : Any, optional + The invalid value + definition : Any, optional + The full rule `definition` + (e.g. `42`) + rule : str, optional + `rule` which the `data` is breaking + (e.g. `maximum`) + rule_definition : Any, optional + The specific rule `definition` + (e.g. `42`) + """ + super().__init__(message) + self.message = message + self.validation_message = validation_message + self.name = name + self.path = path + self.value = value + self.definition = definition + self.rule = rule + self.rule_definition = rule_definition + class InvalidSchemaFormatError(Exception): """When JSON Schema is in invalid format""" diff --git a/tests/functional/validator/test_validator.py b/tests/functional/validator/test_validator.py index d8986ba90de..cd5c4168f56 100644 --- a/tests/functional/validator/test_validator.py +++ b/tests/functional/validator/test_validator.py @@ -1,3 +1,5 @@ +import re + import jmespath import pytest from jmespath import functions @@ -22,8 +24,22 @@ def test_validate_base64_string_envelope(schema, wrapped_event_base64_json_strin def test_validate_event_does_not_conform_with_schema(schema): - with pytest.raises(exceptions.SchemaValidationError): - validate(event={"message": "hello_world"}, schema=schema) + data = {"message": "hello_world"} + message = "data must contain ['message', 'username'] properties" + with pytest.raises( + exceptions.SchemaValidationError, + match=re.escape(f"Failed schema validation. Error: {message}, Path: ['data'], Data: {data}"), + ) as e: + validate(event=data, schema=schema) + + assert str(e.value) == e.value.message + assert e.value.validation_message == message + assert e.value.name == "data" + assert e.value.path is not None + assert e.value.value == data + assert e.value.definition == schema + assert e.value.rule == "required" + assert e.value.rule_definition == schema.get("required") def test_validate_json_string_no_envelope(schema, wrapped_event_json_string):