diff --git a/aws_lambda_powertools/utilities/validation/base.py b/aws_lambda_powertools/utilities/validation/base.py index 9d7a36874aa..f3b10a56388 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 specifies a `default` value for fields that are omitted, + those default values 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..679c7d2b25a 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 specifies a `default` value for fields that are omitted, + those default values 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/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/_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..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.", @@ -30,11 +30,39 @@ def schema(): } +@pytest.fixture +def schema_default(): + return { + "$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.", + "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 { - "$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", @@ -71,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.", @@ -89,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", @@ -104,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", @@ -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"}}} @@ -407,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", @@ -622,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",