From 11b887a2840b9b80b6adb1def05581d15987cf7c Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Tue, 21 Sep 2021 13:06:43 -0700 Subject: [PATCH 1/3] feat(validator): include missing data elements from JsonSchemaValueException --- .../utilities/validation/base.py | 15 ++++-- .../utilities/validation/exceptions.py | 46 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) 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..5528c0ac648 100644 --- a/aws_lambda_powertools/utilities/validation/exceptions.py +++ b/aws_lambda_powertools/utilities/validation/exceptions.py @@ -1,9 +1,55 @@ +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 full 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""" From 5cd000670a74932d637c2a418d1ee4bda57b7343 Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Tue, 21 Sep 2021 17:39:53 -0700 Subject: [PATCH 2/3] chore: add a strict comparison --- .../utilities/validation/exceptions.py | 22 +++++++++++-------- tests/functional/validator/test_validator.py | 13 +++++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/aws_lambda_powertools/utilities/validation/exceptions.py b/aws_lambda_powertools/utilities/validation/exceptions.py index 5528c0ac648..7c719ca3119 100644 --- a/aws_lambda_powertools/utilities/validation/exceptions.py +++ b/aws_lambda_powertools/utilities/validation/exceptions.py @@ -24,24 +24,28 @@ def __init__( 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``) + 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]``) + 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']``), + `path` as an array in the data structure + (e.g. `['data', 'property', 'index']`), value : Any, optional - the invalid value + The invalid value definition : Any, optional - The full rule ``definition`` (e.g. ``42``) + The full rule `definition` + (e.g. `42`) rule : str, optional - `rule`` which the ``data`` is breaking (e.g. ``maximum``) + `rule` which the `data` is breaking + (e.g. `maximum`) rule_definition : Any, optional - The full rule ``definition`` (e.g. ``42``) + The specific rule `definition` + (e.g. `42`) """ super().__init__(message) self.message = message - self.validation_message = validation_message self.name = name self.path = path diff --git a/tests/functional/validator/test_validator.py b/tests/functional/validator/test_validator.py index d8986ba90de..82e6c7b7e16 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,15 @@ 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"} + with pytest.raises( + exceptions.SchemaValidationError, + match=re.escape( + "Failed schema validation. Error: data must contain ['message', 'username'] properties, " + f"Path: ['data'], Data: {data}" + ), + ): + validate(event=data, schema=schema) def test_validate_json_string_no_envelope(schema, wrapped_event_json_string): From 4346ae0024ecf1ef7f37c036f31b5f67c40085b4 Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Tue, 21 Sep 2021 19:04:40 -0700 Subject: [PATCH 3/3] test: add more assertions --- tests/functional/validator/test_validator.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/functional/validator/test_validator.py b/tests/functional/validator/test_validator.py index 82e6c7b7e16..cd5c4168f56 100644 --- a/tests/functional/validator/test_validator.py +++ b/tests/functional/validator/test_validator.py @@ -25,15 +25,22 @@ def test_validate_base64_string_envelope(schema, wrapped_event_base64_json_strin def test_validate_event_does_not_conform_with_schema(schema): data = {"message": "hello_world"} + message = "data must contain ['message', 'username'] properties" with pytest.raises( exceptions.SchemaValidationError, - match=re.escape( - "Failed schema validation. Error: data must contain ['message', 'username'] properties, " - f"Path: ['data'], Data: {data}" - ), - ): + 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): # WHEN data key contains a JSON String