From dcb763ddc105ce31e1b8488b886052478dcf0a73 Mon Sep 17 00:00:00 2001 From: Zachary Dowd Date: Mon, 29 Jul 2024 14:33:38 +0000 Subject: [PATCH 1/2] Return the output of fastjsonschema.validate() --- .../utilities/validation/base.py | 16 +++++++-- .../utilities/validation/validator.py | 10 ++++-- .../_fastjsonschema/test_validator.py | 6 ++++ tests/functional/validator/conftest.py | 33 +++++++++++++++++++ 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/aws_lambda_powertools/utilities/validation/base.py b/aws_lambda_powertools/utilities/validation/base.py index 9d7a36874aa..d127f6f4002 100644 --- a/aws_lambda_powertools/utilities/validation/base.py +++ b/aws_lambda_powertools/utilities/validation/base.py @@ -14,7 +14,7 @@ def validate_data_against_schema( formats: Optional[Dict] = None, handlers: Optional[Dict] = None, provider_options: Optional[Dict] = None, -): +) -> Union[Dict, str]: """Validate dict data against given JSON Schema Parameters @@ -31,6 +31,12 @@ def validate_data_against_schema( Arguments that will be passed directly to the underlying validation call, in this case fastjsonchema.validate. For all supported arguments see: https://horejsek.github.io/python-fastjsonschema/#fastjsonschema.validate + Returns + ------- + Dict + The validated event. If the schema includes a `default` for fields that are not provided, they will be included + in the response. + Raises ------ SchemaValidationError @@ -42,7 +48,13 @@ def validate_data_against_schema( formats = formats or {} handlers = handlers or {} provider_options = provider_options or {} - fastjsonschema.validate(definition=schema, data=data, formats=formats, handlers=handlers, **provider_options) + return fastjsonschema.validate( + definition=schema, + data=data, + formats=formats, + handlers=handlers, + **provider_options, + ) except (TypeError, AttributeError, fastjsonschema.JsonSchemaDefinitionException) as e: raise InvalidSchemaFormatError(f"Schema received: {schema}, Formats: {formats}. Error: {e}") except fastjsonschema.JsonSchemaValueException as e: diff --git a/aws_lambda_powertools/utilities/validation/validator.py b/aws_lambda_powertools/utilities/validation/validator.py index 74861f7de3f..491121bcc0b 100644 --- a/aws_lambda_powertools/utilities/validation/validator.py +++ b/aws_lambda_powertools/utilities/validation/validator.py @@ -172,7 +172,7 @@ def validate( provider_options: Optional[Dict] = None, envelope: Optional[str] = None, jmespath_options: Optional[Dict] = None, -): +) -> Any: """Standalone function to validate event data using a JSON Schema Typically used when you need more control over the validation process. @@ -245,6 +245,12 @@ def handler(event, context): validate(event=event, schema=json_schema_dict, envelope="awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]") return event + Returns + ------- + Dict + The validated event. If the schema includes a `default` for fields that are not provided, they will be included + in the response. + Raises ------ SchemaValidationError @@ -261,7 +267,7 @@ def handler(event, context): jmespath_options=jmespath_options, ) - validate_data_against_schema( + return validate_data_against_schema( data=event, schema=schema, formats=formats, diff --git a/tests/functional/validator/_fastjsonschema/test_validator.py b/tests/functional/validator/_fastjsonschema/test_validator.py index f18787990ff..a594f5393f9 100644 --- a/tests/functional/validator/_fastjsonschema/test_validator.py +++ b/tests/functional/validator/_fastjsonschema/test_validator.py @@ -16,6 +16,12 @@ def test_validate_raw_event(schema, raw_event): validate(event=raw_event, schema=schema) +def test_validate_raw_event_default(schema_default, raw_event_default): + resp = validate(event=raw_event_default, schema=schema_default) + assert resp["username"] == "blah blah" + assert resp["message"] == "The default message" + + def test_validate_wrapped_event_raw_envelope(schema, wrapped_event): validate(event=wrapped_event, schema=schema, envelope="data.payload") diff --git a/tests/functional/validator/conftest.py b/tests/functional/validator/conftest.py index 3b9033c82d4..922c7ca4dad 100644 --- a/tests/functional/validator/conftest.py +++ b/tests/functional/validator/conftest.py @@ -30,6 +30,34 @@ def schema(): } +@pytest.fixture +def schema_default(): + return { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [{"message": "hello world", "username": "lessa"}, {"username": "lessa"}], + "required": ["username"], + "properties": { + "message": { + "$id": "#/properties/message", + "type": "string", + "title": "The message", + "examples": ["hello world"], + "default": "The default message", + }, + "username": { + "$id": "#/properties/username", + "type": "string", + "title": "The username", + "examples": ["lessa"], + }, + }, + } + + @pytest.fixture def schema_array(): return { @@ -137,6 +165,11 @@ def raw_event(): return {"message": "hello hello", "username": "blah blah"} +@pytest.fixture +def raw_event_default(): + return {"username": "blah blah"} + + @pytest.fixture def wrapped_event(): return {"data": {"payload": {"message": "hello hello", "username": "blah blah"}}} From a457e5fc032aa13b47979d56384a7f85e4c7744c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Mon, 5 Aug 2024 18:40:49 +0100 Subject: [PATCH 2/2] Adding documentation --- .../utilities/validation/base.py | 4 ++-- .../utilities/validation/validator.py | 4 ++-- docs/utilities/validation.md | 4 ++++ tests/functional/validator/conftest.py | 24 +++++++++---------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/aws_lambda_powertools/utilities/validation/base.py b/aws_lambda_powertools/utilities/validation/base.py index d127f6f4002..f3b10a56388 100644 --- a/aws_lambda_powertools/utilities/validation/base.py +++ b/aws_lambda_powertools/utilities/validation/base.py @@ -34,8 +34,8 @@ def validate_data_against_schema( Returns ------- Dict - The validated event. If the schema includes a `default` for fields that are not provided, they will be included - in the response. + The validated event. If the schema specifies a `default` value for fields that are omitted, + those default values will be included in the response. Raises ------ diff --git a/aws_lambda_powertools/utilities/validation/validator.py b/aws_lambda_powertools/utilities/validation/validator.py index 491121bcc0b..679c7d2b25a 100644 --- a/aws_lambda_powertools/utilities/validation/validator.py +++ b/aws_lambda_powertools/utilities/validation/validator.py @@ -248,8 +248,8 @@ def handler(event, context): Returns ------- Dict - The validated event. If the schema includes a `default` for fields that are not provided, they will be included - in the response. + The validated event. If the schema specifies a `default` value for fields that are omitted, + those default values will be included in the response. Raises ------ diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index 51085d417fa..52730016f97 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -67,6 +67,10 @@ It will fail fast with `SchemaValidationError` exception if event or response do **Validate** standalone function is typically used within the Lambda handler, or any other methods that perform data validation. +???+ info + This function returns the validated event as a JSON object. If the schema specifies `default` values for omitted fields, + those default values will be included in the response. + You can also gracefully handle schema validation errors by catching `SchemaValidationError` exception. === "getting_started_validator_standalone_function.py" diff --git a/tests/functional/validator/conftest.py b/tests/functional/validator/conftest.py index 922c7ca4dad..9ec94934592 100644 --- a/tests/functional/validator/conftest.py +++ b/tests/functional/validator/conftest.py @@ -6,8 +6,8 @@ @pytest.fixture def schema(): return { - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://example.com/example.json", + "$schema": "https://json-schema.org/draft-07/schema", + "$id": "https://example.com/example.json", "type": "object", "title": "Sample schema", "description": "The root schema comprises the entire JSON document.", @@ -33,8 +33,8 @@ def schema(): @pytest.fixture def schema_default(): return { - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://example.com/example.json", + "$schema": "https://json-schema.org/draft-07/schema", + "$id": "https://example.com/example.json", "type": "object", "title": "Sample schema", "description": "The root schema comprises the entire JSON document.", @@ -61,8 +61,8 @@ def schema_default(): @pytest.fixture def schema_array(): return { - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://example.com/example.json", + "$schema": "https://json-schema.org/draft-07/schema", + "$id": "https://example.com/example.json", "type": "array", "title": "Sample schema", "description": "Sample JSON Schema for dummy data in an array", @@ -99,8 +99,8 @@ def schema_array(): @pytest.fixture def schema_response(): return { - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://example.com/example.json", + "$schema": "https://json-schema.org/draft-07/schema", + "$id": "https://example.com/example.json", "type": "object", "title": "Sample outgoing schema", "description": "The root schema comprises the entire JSON document.", @@ -117,7 +117,7 @@ def schema_response(): def schema_refs(): return { "ParentSchema": { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://json-schema.org/draft-07/schema", "$id": "testschema://ParentSchema", "type": "object", "title": "Sample schema", @@ -132,7 +132,7 @@ def schema_refs(): }, }, "ChildSchema": { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://json-schema.org/draft-07/schema", "$id": "testschema://ChildSchema", "type": "object", "title": "Sample schema", @@ -440,7 +440,7 @@ def cloudwatch_logs_event(): @pytest.fixture def cloudwatch_logs_schema(): return { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://json-schema.org/draft-07/schema", "$id": "http://example.com/example.json", "type": "array", "title": "Sample schema", @@ -655,7 +655,7 @@ def eventbridge_schema_registry_cloudtrail_v2_s3(): @pytest.fixture def schema_datetime_format(): return { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://json-schema.org/draft-07/schema", "$id": "http://example.com/example.json", "type": "object", "title": "Sample schema with string date-time format",