diff --git a/CHANGELOG.md b/CHANGELOG.md
index e1128ce44e9..f139fc3d21c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Added
+- **Utilities**: Add new `Validator` utility to validate inbound events and responses using JSON Schema
+
## [1.5.0] - 2020-09-04
### Added
diff --git a/Makefile b/Makefile
index 20da3040bb9..2b841f362c7 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ target:
dev:
pip install --upgrade pip poetry pre-commit
- poetry install
+ poetry install --extras "jmespath"
pre-commit install
dev-docs:
diff --git a/aws_lambda_powertools/utilities/validation/__init__.py b/aws_lambda_powertools/utilities/validation/__init__.py
new file mode 100644
index 00000000000..94706e3214d
--- /dev/null
+++ b/aws_lambda_powertools/utilities/validation/__init__.py
@@ -0,0 +1,14 @@
+"""
+Simple validator to enforce incoming/outgoing event conforms with JSON Schema
+"""
+
+from .exceptions import InvalidEnvelopeExpressionError, InvalidSchemaFormatError, SchemaValidationError
+from .validator import validate, validator
+
+__all__ = [
+ "validate",
+ "validator",
+ "InvalidSchemaFormatError",
+ "SchemaValidationError",
+ "InvalidEnvelopeExpressionError",
+]
diff --git a/aws_lambda_powertools/utilities/validation/base.py b/aws_lambda_powertools/utilities/validation/base.py
new file mode 100644
index 00000000000..eab7f89064d
--- /dev/null
+++ b/aws_lambda_powertools/utilities/validation/base.py
@@ -0,0 +1,65 @@
+import logging
+from typing import Any, Dict
+
+import fastjsonschema
+import jmespath
+from jmespath.exceptions import LexerError
+
+from .exceptions import InvalidEnvelopeExpressionError, InvalidSchemaFormatError, SchemaValidationError
+from .jmespath_functions import PowertoolsFunctions
+
+logger = logging.getLogger(__name__)
+
+
+def validate_data_against_schema(data: Dict, schema: Dict):
+ """Validate dict data against given JSON Schema
+
+ Parameters
+ ----------
+ data : Dict
+ Data set to be validated
+ schema : Dict
+ JSON Schema to validate against
+
+ Raises
+ ------
+ SchemaValidationError
+ When schema validation fails against data set
+ InvalidSchemaFormatError
+ When JSON schema provided is invalid
+ """
+ try:
+ fastjsonschema.validate(definition=schema, data=data)
+ 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 (TypeError, AttributeError) as e:
+ raise InvalidSchemaFormatError(f"Schema received: {schema}. Error: {e}")
+
+
+def unwrap_event_from_envelope(data: Dict, envelope: str, jmespath_options: Dict) -> Any:
+ """Searches data using JMESPath expression
+
+ Parameters
+ ----------
+ data : Dict
+ Data set to be filtered
+ envelope : str
+ JMESPath expression to filter data against
+ jmespath_options : Dict
+ Alternative JMESPath options to be included when filtering expr
+
+ Returns
+ -------
+ Any
+ Data found using JMESPath expression given in envelope
+ """
+ if not jmespath_options:
+ jmespath_options = {"custom_functions": PowertoolsFunctions()}
+
+ try:
+ logger.debug(f"Envelope detected: {envelope}. JMESPath options: {jmespath_options}")
+ return jmespath.search(envelope, data, options=jmespath.Options(**jmespath_options))
+ except (LexerError, TypeError, UnicodeError) as e:
+ message = f"Failed to unwrap event from envelope using expression. Error: {e} Exp: {envelope}, Data: {data}" # noqa: B306, E501
+ raise InvalidEnvelopeExpressionError(message)
diff --git a/aws_lambda_powertools/utilities/validation/envelopes.py b/aws_lambda_powertools/utilities/validation/envelopes.py
new file mode 100644
index 00000000000..7bc84fce614
--- /dev/null
+++ b/aws_lambda_powertools/utilities/validation/envelopes.py
@@ -0,0 +1,10 @@
+"""Built-in envelopes"""
+
+API_GATEWAY_REST = "powertools_json(body)"
+API_GATEWAY_HTTP = API_GATEWAY_REST
+SQS = "Records[*].powertools_json(body)"
+SNS = "Records[0].Sns.Message | powertools_json(@)"
+EVENTBRIDGE = "detail"
+CLOUDWATCH_EVENTS_SCHEDULED = EVENTBRIDGE
+KINESIS_DATA_STREAM = "Records[*].kinesis.powertools_json(powertools_base64(data))"
+CLOUDWATCH_LOGS = "awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]"
diff --git a/aws_lambda_powertools/utilities/validation/exceptions.py b/aws_lambda_powertools/utilities/validation/exceptions.py
new file mode 100644
index 00000000000..6b51fe5ca28
--- /dev/null
+++ b/aws_lambda_powertools/utilities/validation/exceptions.py
@@ -0,0 +1,14 @@
+class SchemaValidationError(Exception):
+ """When serialization fail schema validation"""
+
+ pass
+
+
+class InvalidSchemaFormatError(Exception):
+ """When JSON Schema is in invalid format"""
+
+ pass
+
+
+class InvalidEnvelopeExpressionError(Exception):
+ """When JMESPath fails to parse expression"""
diff --git a/aws_lambda_powertools/utilities/validation/jmespath_functions.py b/aws_lambda_powertools/utilities/validation/jmespath_functions.py
new file mode 100644
index 00000000000..b23ab477d6b
--- /dev/null
+++ b/aws_lambda_powertools/utilities/validation/jmespath_functions.py
@@ -0,0 +1,22 @@
+import base64
+import gzip
+import json
+
+import jmespath
+
+
+class PowertoolsFunctions(jmespath.functions.Functions):
+ @jmespath.functions.signature({"types": ["string"]})
+ def _func_powertools_json(self, value):
+ return json.loads(value)
+
+ @jmespath.functions.signature({"types": ["string"]})
+ def _func_powertools_base64(self, value):
+ return base64.b64decode(value).decode()
+
+ @jmespath.functions.signature({"types": ["string"]})
+ def _func_powertools_base64_gzip(self, value):
+ encoded = base64.b64decode(value)
+ uncompressed = gzip.decompress(encoded)
+
+ return uncompressed.decode()
diff --git a/aws_lambda_powertools/utilities/validation/validator.py b/aws_lambda_powertools/utilities/validation/validator.py
new file mode 100644
index 00000000000..c404e90f55a
--- /dev/null
+++ b/aws_lambda_powertools/utilities/validation/validator.py
@@ -0,0 +1,204 @@
+import logging
+from typing import Any, Callable, Dict, Union
+
+from ...middleware_factory import lambda_handler_decorator
+from .base import unwrap_event_from_envelope, validate_data_against_schema
+
+logger = logging.getLogger(__name__)
+
+
+@lambda_handler_decorator
+def validator(
+ handler: Callable,
+ event: Union[Dict, str],
+ context: Any,
+ inbound_schema: Dict = None,
+ outbound_schema: Dict = None,
+ envelope: str = None,
+ jmespath_options: Dict = None,
+) -> Any:
+ """Lambda handler decorator to validate incoming/outbound data using a JSON Schema
+
+ Example
+ -------
+
+ **Validate incoming event**
+
+ from aws_lambda_powertools.utilities.validation import validator
+
+ @validator(inbound_schema=json_schema_dict)
+ def handler(event, context):
+ return event
+
+ **Validate incoming and outgoing event**
+
+ from aws_lambda_powertools.utilities.validation import validator
+
+ @validator(inbound_schema=json_schema_dict, outbound_schema=response_json_schema_dict)
+ def handler(event, context):
+ return event
+
+ **Unwrap event before validating against actual payload - using built-in envelopes**
+
+ from aws_lambda_powertools.utilities.validation import validator, envelopes
+
+ @validator(inbound_schema=json_schema_dict, envelope=envelopes.API_GATEWAY_REST)
+ def handler(event, context):
+ return event
+
+ **Unwrap event before validating against actual payload - using custom JMESPath expression**
+
+ from aws_lambda_powertools.utilities.validation import validator
+
+ @validator(inbound_schema=json_schema_dict, envelope="payload[*].my_data")
+ def handler(event, context):
+ return event
+
+ **Unwrap and deserialize JSON string event before validating against actual payload - using built-in functions**
+
+ from aws_lambda_powertools.utilities.validation import validator
+
+ @validator(inbound_schema=json_schema_dict, envelope="Records[*].powertools_json(body)")
+ def handler(event, context):
+ return event
+
+ **Unwrap, decode base64 and deserialize JSON string event before validating against actual payload - using built-in functions** # noqa: E501
+
+ from aws_lambda_powertools.utilities.validation import validator
+
+ @validator(inbound_schema=json_schema_dict, envelope="Records[*].kinesis.powertools_json(powertools_base64(data))")
+ def handler(event, context):
+ return event
+
+ **Unwrap, decompress ZIP archive and deserialize JSON string event before validating against actual payload - using built-in functions** # noqa: E501
+
+ from aws_lambda_powertools.utilities.validation import validator
+
+ @validator(inbound_schema=json_schema_dict, envelope="awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]")
+ def handler(event, context):
+ return event
+
+ Parameters
+ ----------
+ handler : Callable
+ Method to annotate on
+ event : Dict
+ Lambda event to be validated
+ context : Any
+ Lambda context object
+ inbound_schema : Dict
+ JSON Schema to validate incoming event
+ outbound_schema : Dict
+ JSON Schema to validate outbound event
+ envelope : Dict
+ JMESPath expression to filter data against
+ jmespath_options : Dict
+ Alternative JMESPath options to be included when filtering expr
+
+ Returns
+ -------
+ Any
+ Lambda handler response
+
+ Raises
+ ------
+ SchemaValidationError
+ When schema validation fails against data set
+ InvalidSchemaFormatError
+ When JSON schema provided is invalid
+ InvalidEnvelopeExpressionError
+ When JMESPath expression to unwrap event is invalid
+ """
+ if envelope:
+ event = unwrap_event_from_envelope(data=event, envelope=envelope, jmespath_options=jmespath_options)
+
+ if inbound_schema:
+ logger.debug("Validating inbound event")
+ validate_data_against_schema(data=event, schema=inbound_schema)
+
+ response = handler(event, context)
+
+ if outbound_schema:
+ logger.debug("Validating outbound event")
+ validate_data_against_schema(data=response, schema=outbound_schema)
+
+ return response
+
+
+def validate(event: Dict, schema: Dict = None, envelope: str = None, jmespath_options: Dict = None):
+ """Standalone function to validate event data using a JSON Schema
+
+ Typically used when you need more control over the validation process.
+
+ **Validate event**
+
+ from aws_lambda_powertools.utilities.validation import validate
+
+ def handler(event, context):
+ validate(event=event, schema=json_schema_dict)
+ return event
+
+ **Unwrap event before validating against actual payload - using built-in envelopes**
+
+ from aws_lambda_powertools.utilities.validation import validate, envelopes
+
+ def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope=envelopes.API_GATEWAY_REST)
+ return event
+
+ **Unwrap event before validating against actual payload - using custom JMESPath expression**
+
+ from aws_lambda_powertools.utilities.validation import validate
+
+ def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope="payload[*].my_data")
+ return event
+
+ **Unwrap and deserialize JSON string event before validating against actual payload - using built-in functions**
+
+ from aws_lambda_powertools.utilities.validation import validate
+
+ def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope="Records[*].powertools_json(body)")
+ return event
+
+ **Unwrap, decode base64 and deserialize JSON string event before validating against actual payload - using built-in functions**
+
+ from aws_lambda_powertools.utilities.validation import validate
+
+ def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope="Records[*].kinesis.powertools_json(powertools_base64(data))")
+ return event
+
+ **Unwrap, decompress ZIP archive and deserialize JSON string event before validating against actual payload - using built-in functions** # noqa: E501
+
+ from aws_lambda_powertools.utilities.validation import validate
+
+ def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope="awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]")
+ return event
+
+ Parameters
+ ----------
+ event : Dict
+ Lambda event to be validated
+ schema : Dict
+ JSON Schema to validate incoming event
+ envelope : Dict
+ JMESPath expression to filter data against
+ jmespath_options : Dict
+ Alternative JMESPath options to be included when filtering expr
+
+ Raises
+ ------
+ SchemaValidationError
+ When schema validation fails against data set
+ InvalidSchemaFormatError
+ When JSON schema provided is invalid
+ InvalidEnvelopeExpressionError
+ When JMESPath expression to unwrap event is invalid
+ """
+ if envelope:
+ event = unwrap_event_from_envelope(data=event, envelope=envelope, jmespath_options=jmespath_options)
+
+ validate_data_against_schema(data=event, schema=schema)
diff --git a/docs/content/utilities/validation.mdx b/docs/content/utilities/validation.mdx
new file mode 100644
index 00000000000..74b762a096e
--- /dev/null
+++ b/docs/content/utilities/validation.mdx
@@ -0,0 +1,236 @@
+---
+title: Validation
+description: Utility
+---
+
+
+import Note from "../../src/components/Note"
+
+This utility provides JSON Schema validation for events and responses, including JMESPath support to unwrap events before validation.
+
+**Key features**
+
+* Validate incoming event and response
+* JMESPath support to unwrap events before validation applies
+* Built-in envelopes to unwrap popular event sources payloads
+
+## Validating events
+
+You can validate inbound and outbound events using `validator` decorator.
+
+You can also use the standalone `validate` function, if you want more control over the validation process such as handling a validation error.
+
+We support any JSONSchema draft supported by [fastjsonschema](https://horejsek.github.io/python-fastjsonschema/) library.
+
+
+ Both validator
decorator and validate
standalone function expects your JSON Schema to be
+ a dictionary, not a filename.
+
+
+
+### Validator decorator
+
+**Validator** decorator is typically used to validate either inbound or functions' response.
+
+It will fail fast with `SchemaValidationError` exception if event or response doesn't conform with given JSON Schema.
+
+```python:title=validator_decorator.py
+from aws_lambda_powertools.utilities.validation import validator
+
+json_schema_dict = {..}
+response_json_schema_dict = {..}
+
+@validator(inbound_schema=json_schema_dict, outbound_schema=response_json_schema_dict)
+def handler(event, context):
+ return event
+```
+
+**NOTE**: It's not a requirement to validate both inbound and outbound schemas - You can either use one, or both.
+
+### Validate function
+
+**Validate** standalone function is typically used within the Lambda handler, or any other methods that perform data validation.
+
+You can also gracefully handle schema validation errors by catching `SchemaValidationError` exception.
+
+```python:title=validator_decorator.py
+from aws_lambda_powertools.utilities.validation import validate
+from aws_lambda_powertools.utilities.validation.exceptions import SchemaValidationError
+
+json_schema_dict = {..}
+
+def handler(event, context):
+ try:
+ validate(event=event, schema=json_schema_dict)
+ except SchemaValidationError as e:
+ # do something before re-raising
+ raise
+
+ return event
+```
+
+## Unwrapping events prior to validation
+
+You might want to validate only a portion of your event - This is where the `envelope` parameter is for.
+
+Envelopes are [JMESPath expressions](https://jmespath.org/tutorial.html) to extract a portion of JSON you want before applying JSON Schema validation.
+
+Here is a sample custom EventBridge event, where we only validate what's inside the `detail` key:
+
+```json:title=sample_wrapped_event.json
+{
+ "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
+ "detail-type": "Scheduled Event",
+ "source": "aws.events",
+ "account": "123456789012",
+ "time": "1970-01-01T00:00:00Z",
+ "region": "us-east-1",
+ "resources": ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"],
+ "detail": {"message": "hello hello", "username": "blah blah"}, // highlight-line
+}
+```
+
+Here is how you'd use the `envelope` parameter to extract the payload inside the `detail` key before validating:
+
+```python:title=unwrapping_events.py
+from aws_lambda_powertools.utilities.validation import validator, validate
+
+json_schema_dict = {..}
+
+@validator(inbound_schema=json_schema_dict, envelope="detail") # highlight-line
+def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope="detail") # highlight-line
+ return event
+```
+
+This is quite powerful because you can use JMESPath Query language to extract records from [arrays, slice and dice](https://jmespath.org/tutorial.html#list-and-slice-projections), to [pipe expressions](https://jmespath.org/tutorial.html#pipe-expressions) and [function expressions](https://jmespath.org/tutorial.html#functions), where you'd extract what you need before validating the actual payload.
+
+## Built-in envelopes
+
+This utility comes with built-in envelopes to easily extract the payload from popular event sources.
+
+```python:title=unwrapping_popular_event_sources.py
+from aws_lambda_powertools.utilities.validation import envelopes, validate, validator
+
+json_schema_dict = {..}
+
+@validator(inbound_schema=json_schema_dict, envelope=envelopes.EVENTBRIDGE) # highlight-line
+def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope=envelopes.EVENTBRIDGE) # highlight-line
+ return event
+```
+
+Here is a handy table with built-in envelopes along with their JMESPath expressions in case you want to build your own.
+
+Envelope name | JMESPath expression
+------------------------------------------------- | ---------------------------------------------------------------------------------
+**API_GATEWAY_REST** | "powertools_json(body)"
+**API_GATEWAY_HTTP** | "powertools_json(body)"
+**SQS** | "Records[*].powertools_json(body)"
+**SNS** | "Records[0].Sns.Message | powertools_json(@)"
+**EVENTBRIDGE** | "detail"
+**CLOUDWATCH_EVENTS_SCHEDULED** | "detail"
+**KINESIS_DATA_STREAM** | "Records[*].kinesis.powertools_json(powertools_base64(data))"
+**CLOUDWATCH_LOGS** | "awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]"
+
+## Built-in JMESPath functions
+
+You might have events or responses that contain non-encoded JSON, where you need to decode before validating them.
+
+You can use our built-in JMESPath functions within your expressions to do exactly that to decode JSON Strings, base64, and uncompress gzip data.
+
+
+ We use these for built-in envelopes to easily to decode and unwrap events from sources like Kinesis, CloudWatch Logs, etc.
+
+
+### powertools_json function
+
+Use `powertools_json` function to decode any JSON String.
+
+This sample will decode the value within the `data` key into a valid JSON before we can validate it.
+
+```python:title=powertools_json_jmespath_function.py
+from aws_lambda_powertools.utilities.validation import validate
+
+json_schema_dict = {..}
+sample_event = {
+ 'data': '{"payload": {"message": "hello hello", "username": "blah blah"}}'
+}
+
+def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope="powertools_json(data)") # highlight-line
+ return event
+
+handler(event=sample_event, context={})
+```
+
+### powertools_base64 function
+
+Use `powertools_base64` function to decode any base64 data.
+
+This sample will decode the base64 value within the `data` key, and decode the JSON string into a valid JSON before we can validate it.
+
+```python:title=powertools_json_jmespath_function.py
+from aws_lambda_powertools.utilities.validation import validate
+
+json_schema_dict = {..}
+sample_event = {
+ "data": "eyJtZXNzYWdlIjogImhlbGxvIGhlbGxvIiwgInVzZXJuYW1lIjogImJsYWggYmxhaCJ9="
+}
+
+def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope="powertools_json(powertools_base64(data))") # highlight-line
+ return event
+
+handler(event=sample_event, context={})
+```
+
+### powertools_base64_gzip function
+
+Use `powertools_base64_gzip` function to decompress and decode base64 data.
+
+This sample will decompress and decode base64 data, then use JMESPath pipeline expression to pass the result for decoding its JSON string.
+
+```python:title=powertools_json_jmespath_function.py
+from aws_lambda_powertools.utilities.validation import validate
+
+json_schema_dict = {..}
+sample_event = {
+ "data": "H4sIACZAXl8C/52PzUrEMBhFX2UILpX8tPbHXWHqIOiq3Q1F0ubrWEiakqTWofTdTYYB0YWL2d5zvnuTFellBIOedoiyKH5M0iwnlKH7HZL6dDB6ngLDfLFYctUKjie9gHFaS/sAX1xNEq525QxwFXRGGMEkx4Th491rUZdV3YiIZ6Ljfd+lfSyAtZloacQgAkqSJCGhxM6t7cwwuUGPz4N0YKyvO6I9WDeMPMSo8Z4Ca/kJ6vMEYW5f1MX7W1lVxaG8vqX8hNFdjlc0iCBBSF4ERT/3Pl7RbMGMXF2KZMh/C+gDpNS7RRsp0OaRGzx0/t8e0jgmcczyLCWEePhni/23JWalzjdu0a3ZvgEaNLXeugEAAA=="
+}
+
+def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope="powertools_base64_gzip(data) | powertools_json(@)") # highlight-line
+ return event
+
+handler(event=sample_event, context={})
+```
+
+## Bring your own JMESPath function
+
+
+ This should only be used for advanced use cases where you have special formats not covered by the built-in functions.
+
+ This will replace all provided built-in functions such as `powertools_json`, so you will no longer be able to use them.
+
+
+For special binary formats that you want to decode before applying JSON Schema validation, you can bring your own [JMESPath function](https://github.com/jmespath/jmespath.py#custom-functions) and any additional option via `jmespath_options` param.
+
+```python:title=custom_jmespath_function
+from aws_lambda_powertools.utilities.validation import validate
+from jmespath import functions
+
+json_schema_dict = {..}
+
+class CustomFunctions(functions.Functions):
+
+ @functions.signature({'types': ['string']})
+ def _func_special_decoder(self, s):
+ return my_custom_decoder_logic(s)
+
+custom_jmespath_options = {"custom_functions": CustomFunctions()}
+
+def handler(event, context):
+ validate(event=event, schema=json_schema_dict, envelope="", jmespath_options=**custom_jmespath_options) # highlight-line
+ return event
+```
diff --git a/docs/gatsby-config.js b/docs/gatsby-config.js
index a4286e0d55f..171499c3a22 100644
--- a/docs/gatsby-config.js
+++ b/docs/gatsby-config.js
@@ -34,6 +34,7 @@ module.exports = {
'utilities/parameters',
'utilities/batch',
'utilities/typing',
+ 'utilities/validation'
],
},
navConfig: {
diff --git a/poetry.lock b/poetry.lock
index e7b7cdff1db..303b2447966 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -104,13 +104,12 @@ description = "Classes Without Boilerplate"
name = "attrs"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "19.3.0"
+version = "20.1.0"
[package.extras]
-azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
-dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
-docs = ["sphinx", "zope.interface"]
-tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"]
+docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
+tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
[[package]]
category = "main"
@@ -218,10 +217,11 @@ version = "7.1.2"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
+marker = "sys_platform == \"win32\" or platform_system == \"Windows\" or python_version > \"3.4\""
name = "colorama"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.4.1"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.4.3"
[[package]]
category = "dev"
@@ -229,7 +229,7 @@ description = "Code coverage measurement for Python"
name = "coverage"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "5.2"
+version = "5.2.1"
[package.dependencies]
[package.dependencies.toml]
@@ -270,7 +270,7 @@ description = "Fastest Python implementation of JSON schema"
name = "fastjsonschema"
optional = false
python-versions = "*"
-version = "2.14.4"
+version = "2.14.5"
[package.extras]
devel = ["colorama", "jsonschema", "json-spec", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"]
@@ -589,7 +589,7 @@ description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
python-versions = ">=3.5"
-version = "8.4.0"
+version = "8.5.0"
[[package]]
category = "dev"
@@ -733,7 +733,7 @@ description = "Pytest plugin for measuring coverage."
name = "pytest-cov"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.10.0"
+version = "2.10.1"
[package.dependencies]
coverage = ">=4.4"
@@ -781,14 +781,17 @@ description = "Code Metrics in Python"
name = "radon"
optional = false
python-versions = "*"
-version = "4.1.0"
+version = "4.2.0"
[package.dependencies]
-colorama = "0.4.1"
flake8-polyfill = "*"
future = "*"
mando = ">=0.6,<0.7"
+[package.dependencies.colorama]
+python = ">=3.5"
+version = ">=0.4.1"
+
[[package]]
category = "dev"
description = "Alternative regular expression module, to replace re."
@@ -848,7 +851,7 @@ description = "Manage dynamic plugins for Python applications"
name = "stevedore"
optional = false
python-versions = ">=3.6"
-version = "3.1.0"
+version = "3.2.0"
[package.dependencies]
pbr = ">=2.0.0,<2.1.0 || >2.1.0"
@@ -892,7 +895,7 @@ description = "Backported and Experimental Type Hints for Python 3.5+"
name = "typing-extensions"
optional = false
python-versions = "*"
-version = "3.7.4.2"
+version = "3.7.4.3"
[[package]]
category = "main"
@@ -900,7 +903,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and
name = "urllib3"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "1.25.9"
+version = "1.25.10"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
@@ -942,15 +945,20 @@ description = "Yet another URL library"
name = "yarl"
optional = false
python-versions = ">=3.5"
-version = "1.4.2"
+version = "1.5.1"
[package.dependencies]
idna = ">=2.0"
multidict = ">=4.0"
+[package.dependencies.typing-extensions]
+python = "<3.8"
+version = ">=3.7.4"
+
[[package]]
category = "main"
description = "Backport of pathlib-compatible object wrapper for zip files"
+marker = "python_version < \"3.8\""
name = "zipp"
optional = false
python-versions = ">=3.6"
@@ -960,8 +968,11 @@ version = "3.1.0"
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
+[extras]
+jmespath = ["jmespath"]
+
[metadata]
-content-hash = "18607a712e4a4a05de7350ecbcf26327a4fb45bb8609dc7f3d19b7610c2faafc"
+content-hash = "73a725bb90970d6a99d39eb2fc833937e4576f5fe729d60e9b26d505e08a6ea0"
lock-version = "1.0"
python-versions = "^3.6"
@@ -1005,8 +1016,8 @@ atomicwrites = [
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
attrs = [
- {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
- {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
+ {file = "attrs-20.1.0-py2.py3-none-any.whl", hash = "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff"},
+ {file = "attrs-20.1.0.tar.gz", hash = "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a"},
]
aws-xray-sdk = [
{file = "aws-xray-sdk-2.6.0.tar.gz", hash = "sha256:abf5b90f740e1f402e23414c9670e59cb9772e235e271fef2bce62b9100cbc77"},
@@ -1041,44 +1052,44 @@ click = [
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
]
colorama = [
- {file = "colorama-0.4.1-py2.py3-none-any.whl", hash = "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"},
- {file = "colorama-0.4.1.tar.gz", hash = "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d"},
+ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
+ {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
]
coverage = [
- {file = "coverage-5.2-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:d9ad0a988ae20face62520785ec3595a5e64f35a21762a57d115dae0b8fb894a"},
- {file = "coverage-5.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:4bb385a747e6ae8a65290b3df60d6c8a692a5599dc66c9fa3520e667886f2e10"},
- {file = "coverage-5.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9702e2cb1c6dec01fb8e1a64c015817c0800a6eca287552c47a5ee0ebddccf62"},
- {file = "coverage-5.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:42fa45a29f1059eda4d3c7b509589cc0343cd6bbf083d6118216830cd1a51613"},
- {file = "coverage-5.2-cp27-cp27m-win32.whl", hash = "sha256:41d88736c42f4a22c494c32cc48a05828236e37c991bd9760f8923415e3169e4"},
- {file = "coverage-5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:bbb387811f7a18bdc61a2ea3d102be0c7e239b0db9c83be7bfa50f095db5b92a"},
- {file = "coverage-5.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3740b796015b889e46c260ff18b84683fa2e30f0f75a171fb10d2bf9fb91fc70"},
- {file = "coverage-5.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ebf2431b2d457ae5217f3a1179533c456f3272ded16f8ed0b32961a6d90e38ee"},
- {file = "coverage-5.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:d54d7ea74cc00482a2410d63bf10aa34ebe1c49ac50779652106c867f9986d6b"},
- {file = "coverage-5.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:87bdc8135b8ee739840eee19b184804e5d57f518578ffc797f5afa2c3c297913"},
- {file = "coverage-5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ed9a21502e9223f563e071759f769c3d6a2e1ba5328c31e86830368e8d78bc9c"},
- {file = "coverage-5.2-cp35-cp35m-win32.whl", hash = "sha256:509294f3e76d3f26b35083973fbc952e01e1727656d979b11182f273f08aa80b"},
- {file = "coverage-5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:ca63dae130a2e788f2b249200f01d7fa240f24da0596501d387a50e57aa7075e"},
- {file = "coverage-5.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:5c74c5b6045969b07c9fb36b665c9cac84d6c174a809fc1b21bdc06c7836d9a0"},
- {file = "coverage-5.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c32aa13cc3fe86b0f744dfe35a7f879ee33ac0a560684fef0f3e1580352b818f"},
- {file = "coverage-5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1e58fca3d9ec1a423f1b7f2aa34af4f733cbfa9020c8fe39ca451b6071237405"},
- {file = "coverage-5.2-cp36-cp36m-win32.whl", hash = "sha256:3b2c34690f613525672697910894b60d15800ac7e779fbd0fccf532486c1ba40"},
- {file = "coverage-5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a4d511012beb967a39580ba7d2549edf1e6865a33e5fe51e4dce550522b3ac0e"},
- {file = "coverage-5.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:32ecee61a43be509b91a526819717d5e5650e009a8d5eda8631a59c721d5f3b6"},
- {file = "coverage-5.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6f91b4492c5cde83bfe462f5b2b997cdf96a138f7c58b1140f05de5751623cf1"},
- {file = "coverage-5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bfcc811883699ed49afc58b1ed9f80428a18eb9166422bce3c31a53dba00fd1d"},
- {file = "coverage-5.2-cp37-cp37m-win32.whl", hash = "sha256:60a3d36297b65c7f78329b80120f72947140f45b5c7a017ea730f9112b40f2ec"},
- {file = "coverage-5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:12eaccd86d9a373aea59869bc9cfa0ab6ba8b1477752110cb4c10d165474f703"},
- {file = "coverage-5.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:d82db1b9a92cb5c67661ca6616bdca6ff931deceebb98eecbd328812dab52032"},
- {file = "coverage-5.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:214eb2110217f2636a9329bc766507ab71a3a06a8ea30cdeebb47c24dce5972d"},
- {file = "coverage-5.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8a3decd12e7934d0254939e2bf434bf04a5890c5bf91a982685021786a08087e"},
- {file = "coverage-5.2-cp38-cp38-win32.whl", hash = "sha256:1dcebae667b73fd4aa69237e6afb39abc2f27520f2358590c1b13dd90e32abe7"},
- {file = "coverage-5.2-cp38-cp38-win_amd64.whl", hash = "sha256:f50632ef2d749f541ca8e6c07c9928a37f87505ce3a9f20c8446ad310f1aa87b"},
- {file = "coverage-5.2-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:7403675df5e27745571aba1c957c7da2dacb537c21e14007ec3a417bf31f7f3d"},
- {file = "coverage-5.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0fc4e0d91350d6f43ef6a61f64a48e917637e1dcfcba4b4b7d543c628ef82c2d"},
- {file = "coverage-5.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:25fe74b5b2f1b4abb11e103bb7984daca8f8292683957d0738cd692f6a7cc64c"},
- {file = "coverage-5.2-cp39-cp39-win32.whl", hash = "sha256:d67599521dff98ec8c34cd9652cbcfe16ed076a2209625fca9dc7419b6370e5c"},
- {file = "coverage-5.2-cp39-cp39-win_amd64.whl", hash = "sha256:10f2a618a6e75adf64329f828a6a5b40244c1c50f5ef4ce4109e904e69c71bd2"},
- {file = "coverage-5.2.tar.gz", hash = "sha256:1874bdc943654ba46d28f179c1846f5710eda3aeb265ff029e0ac2b52daae404"},
+ {file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"},
+ {file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"},
+ {file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"},
+ {file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"},
+ {file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"},
+ {file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"},
+ {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"},
+ {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"},
+ {file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"},
+ {file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"},
+ {file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"},
+ {file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"},
+ {file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"},
+ {file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"},
+ {file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"},
+ {file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"},
+ {file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"},
+ {file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"},
+ {file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"},
+ {file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"},
+ {file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"},
+ {file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"},
+ {file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"},
+ {file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"},
+ {file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"},
+ {file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"},
+ {file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"},
+ {file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"},
+ {file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"},
+ {file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"},
+ {file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"},
+ {file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"},
+ {file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"},
+ {file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"},
]
dataclasses = [
{file = "dataclasses-0.7-py3-none-any.whl", hash = "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836"},
@@ -1093,8 +1104,8 @@ eradicate = [
{file = "eradicate-1.0.tar.gz", hash = "sha256:4ffda82aae6fd49dfffa777a857cb758d77502a1f2e0f54c9ac5155a39d2d01a"},
]
fastjsonschema = [
- {file = "fastjsonschema-2.14.4-py3-none-any.whl", hash = "sha256:02a39b518077cc73c1a537f27776527dc6c1e5012d530eb8ac0d1062efbabff7"},
- {file = "fastjsonschema-2.14.4.tar.gz", hash = "sha256:7292cde54f1c30172f78557509ad4cb152f374087fc844bd113a83e2ac494dd6"},
+ {file = "fastjsonschema-2.14.5-py3-none-any.whl", hash = "sha256:467593c61f5ba8307205a3536313a774b37df91c9a937c5267c11aee5256e77e"},
+ {file = "fastjsonschema-2.14.5.tar.gz", hash = "sha256:afbc235655f06356e46caa80190512e4d9222abfaca856041be5a74c665fa094"},
]
flake8 = [
{file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"},
@@ -1223,8 +1234,8 @@ mccabe = [
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
more-itertools = [
- {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"},
- {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"},
+ {file = "more-itertools-8.5.0.tar.gz", hash = "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20"},
+ {file = "more_itertools-8.5.0-py3-none-any.whl", hash = "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"},
]
multidict = [
{file = "multidict-4.7.6-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000"},
@@ -1288,8 +1299,8 @@ pytest-asyncio = [
{file = "pytest-asyncio-0.12.0.tar.gz", hash = "sha256:475bd2f3dc0bc11d2463656b3cbaafdbec5a47b47508ea0b329ee693040eebd2"},
]
pytest-cov = [
- {file = "pytest-cov-2.10.0.tar.gz", hash = "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87"},
- {file = "pytest_cov-2.10.0-py2.py3-none-any.whl", hash = "sha256:6e6d18092dce6fad667cd7020deed816f858ad3b49d5b5e2b1cc1c97a4dba65c"},
+ {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"},
+ {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"},
]
pytest-mock = [
{file = "pytest-mock-2.0.0.tar.gz", hash = "sha256:b35eb281e93aafed138db25c8772b95d3756108b601947f89af503f8c629413f"},
@@ -1313,8 +1324,8 @@ pyyaml = [
{file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
]
radon = [
- {file = "radon-4.1.0-py2.py3-none-any.whl", hash = "sha256:0c18111ec6cfe7f664bf9db6c51586714ac8c6d9741542706df8a85aca39b99a"},
- {file = "radon-4.1.0.tar.gz", hash = "sha256:56082c52206db45027d4a73612e1b21663c4cc2be3760fee769d966fd7efdd6d"},
+ {file = "radon-4.2.0-py2.py3-none-any.whl", hash = "sha256:215e42c8748b5ca8ddf7c061831600b9e73e9c48770a81eeaaeeb066697aee15"},
+ {file = "radon-4.2.0.tar.gz", hash = "sha256:b73f6f469c15c9616e0f7ce12080a9ecdee9f2335bdbb5ccea1f2bae26e8d20d"},
]
regex = [
{file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"},
@@ -1356,8 +1367,8 @@ smmap = [
{file = "smmap-3.0.4.tar.gz", hash = "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"},
]
stevedore = [
- {file = "stevedore-3.1.0-py3-none-any.whl", hash = "sha256:9fb12884b510fdc25f8a883bb390b8ff82f67863fb360891a33135bcb2ce8c54"},
- {file = "stevedore-3.1.0.tar.gz", hash = "sha256:79270bd5fb4a052e76932e9fef6e19afa77090c4000f2680eb8c2e887d2e6e36"},
+ {file = "stevedore-3.2.0-py3-none-any.whl", hash = "sha256:c8f4f0ebbc394e52ddf49de8bcc3cf8ad2b4425ebac494106bbc5e3661ac7633"},
+ {file = "stevedore-3.2.0.tar.gz", hash = "sha256:38791aa5bed922b0a844513c5f9ed37774b68edc609e5ab8ab8d8fe0ce4315e5"},
]
testfixtures = [
{file = "testfixtures-6.14.1-py2.py3-none-any.whl", hash = "sha256:30566e24a1b34e4d3f8c13abf62557d01eeb4480bcb8f1745467bfb0d415a7d9"},
@@ -1391,13 +1402,13 @@ typed-ast = [
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
]
typing-extensions = [
- {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"},
- {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"},
- {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"},
+ {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
+ {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
+ {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
]
urllib3 = [
- {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
- {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
+ {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
+ {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
]
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
@@ -1411,23 +1422,23 @@ xenon = [
{file = "xenon-0.7.0.tar.gz", hash = "sha256:5e6433c9297d965bf666256a0a030b6e13660ab87680220c4eb07241f101625b"},
]
yarl = [
- {file = "yarl-1.4.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b"},
- {file = "yarl-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1"},
- {file = "yarl-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080"},
- {file = "yarl-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a"},
- {file = "yarl-1.4.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f"},
- {file = "yarl-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea"},
- {file = "yarl-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb"},
- {file = "yarl-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70"},
- {file = "yarl-1.4.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d"},
- {file = "yarl-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce"},
- {file = "yarl-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"},
- {file = "yarl-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce"},
- {file = "yarl-1.4.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b"},
- {file = "yarl-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae"},
- {file = "yarl-1.4.2-cp38-cp38-win32.whl", hash = "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462"},
- {file = "yarl-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6"},
- {file = "yarl-1.4.2.tar.gz", hash = "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b"},
+ {file = "yarl-1.5.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:db6db0f45d2c63ddb1a9d18d1b9b22f308e52c83638c26b422d520a815c4b3fb"},
+ {file = "yarl-1.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17668ec6722b1b7a3a05cc0167659f6c95b436d25a36c2d52db0eca7d3f72593"},
+ {file = "yarl-1.5.1-cp35-cp35m-win32.whl", hash = "sha256:040b237f58ff7d800e6e0fd89c8439b841f777dd99b4a9cca04d6935564b9409"},
+ {file = "yarl-1.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:f18d68f2be6bf0e89f1521af2b1bb46e66ab0018faafa81d70f358153170a317"},
+ {file = "yarl-1.5.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c52ce2883dc193824989a9b97a76ca86ecd1fa7955b14f87bf367a61b6232511"},
+ {file = "yarl-1.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ce584af5de8830d8701b8979b18fcf450cef9a382b1a3c8ef189bedc408faf1e"},
+ {file = "yarl-1.5.1-cp36-cp36m-win32.whl", hash = "sha256:df89642981b94e7db5596818499c4b2219028f2a528c9c37cc1de45bf2fd3a3f"},
+ {file = "yarl-1.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3a584b28086bc93c888a6c2aa5c92ed1ae20932f078c46509a66dce9ea5533f2"},
+ {file = "yarl-1.5.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:da456eeec17fa8aa4594d9a9f27c0b1060b6a75f2419fe0c00609587b2695f4a"},
+ {file = "yarl-1.5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bc2f976c0e918659f723401c4f834deb8a8e7798a71be4382e024bcc3f7e23a8"},
+ {file = "yarl-1.5.1-cp37-cp37m-win32.whl", hash = "sha256:4439be27e4eee76c7632c2427ca5e73703151b22cae23e64adb243a9c2f565d8"},
+ {file = "yarl-1.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:48e918b05850fffb070a496d2b5f97fc31d15d94ca33d3d08a4f86e26d4e7c5d"},
+ {file = "yarl-1.5.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9b930776c0ae0c691776f4d2891ebc5362af86f152dd0da463a6614074cb1b02"},
+ {file = "yarl-1.5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b3b9ad80f8b68519cc3372a6ca85ae02cc5a8807723ac366b53c0f089db19e4a"},
+ {file = "yarl-1.5.1-cp38-cp38-win32.whl", hash = "sha256:f379b7f83f23fe12823085cd6b906edc49df969eb99757f58ff382349a3303c6"},
+ {file = "yarl-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:9102b59e8337f9874638fcfc9ac3734a0cfadb100e47d55c20d0dc6087fb4692"},
+ {file = "yarl-1.5.1.tar.gz", hash = "sha256:c22c75b5f394f3d47105045ea551e08a3e804dc7e01b37800ca35b58f856c3d6"},
]
zipp = [
{file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
diff --git a/pyproject.toml b/pyproject.toml
index 1fc075e8382..7a5f6866816 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,8 +20,9 @@ license = "MIT-0"
[tool.poetry.dependencies]
python = "^3.6"
aws-xray-sdk = "^2.5.0"
-fastjsonschema = "~=2.14.4"
+fastjsonschema = "^2.14.5"
boto3 = "^1.12"
+jmespath = "^0.10.0"
[tool.poetry.dev-dependencies]
coverage = {extras = ["toml"], version = "^5.0.3"}
diff --git a/tests/functional/validator/__init__.py b/tests/functional/validator/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/functional/validator/conftest.py b/tests/functional/validator/conftest.py
new file mode 100644
index 00000000000..5c154b5aab4
--- /dev/null
+++ b/tests/functional/validator/conftest.py
@@ -0,0 +1,358 @@
+import json
+
+import pytest
+
+
+@pytest.fixture
+def schema():
+ 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"}],
+ "required": ["message", "username"],
+ "properties": {
+ "message": {
+ "$id": "#/properties/message",
+ "type": "string",
+ "title": "The message",
+ "examples": ["hello world"],
+ },
+ "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",
+ "type": "array",
+ "title": "Sample schema",
+ "description": "Sample JSON Schema for dummy data in an array",
+ "examples": [[{"username": "lessa", "message": "hello world"}]],
+ "additionalItems": True,
+ "items": {
+ "$id": "#/items",
+ "anyOf": [
+ {
+ "$id": "#/items/anyOf/0",
+ "type": "object",
+ "description": "Dummy data in an array",
+ "required": ["message", "username"],
+ "properties": {
+ "message": {
+ "$id": "#/items/anyOf/0/properties/message",
+ "type": "string",
+ "title": "The message",
+ "examples": ["hello world"],
+ },
+ "username": {
+ "$id": "#/items/anyOf/0/properties/usernam",
+ "type": "string",
+ "title": "The username",
+ "examples": ["lessa"],
+ },
+ },
+ }
+ ],
+ },
+ }
+
+
+@pytest.fixture
+def schema_response():
+ return {
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "$id": "http://example.com/example.json",
+ "type": "object",
+ "title": "Sample outgoing schema",
+ "description": "The root schema comprises the entire JSON document.",
+ "examples": [{"statusCode": 200, "body": "response"}],
+ "required": ["statusCode", "body"],
+ "properties": {
+ "statusCode": {"$id": "#/properties/statusCode", "type": "integer", "title": "The statusCode"},
+ "body": {"$id": "#/properties/body", "type": "string", "title": "The response"},
+ },
+ }
+
+
+@pytest.fixture
+def raw_event():
+ return {"message": "hello hello", "username": "blah blah"}
+
+
+@pytest.fixture
+def wrapped_event():
+ return {"data": {"payload": {"message": "hello hello", "username": "blah blah"}}}
+
+
+@pytest.fixture
+def wrapped_event_json_string():
+ return {"data": json.dumps({"payload": {"message": "hello hello", "username": "blah blah"}})}
+
+
+@pytest.fixture
+def wrapped_event_base64_json_string():
+ return {"data": "eyJtZXNzYWdlIjogImhlbGxvIGhlbGxvIiwgInVzZXJuYW1lIjogImJsYWggYmxhaCJ9="}
+
+
+@pytest.fixture
+def raw_response():
+ return {"statusCode": 200, "body": "response"}
+
+
+@pytest.fixture
+def apigateway_event():
+ return {
+ "body": '{"message": "hello world", "username": "lessa"}',
+ "resource": "/{proxy+}",
+ "path": "/path/to/resource",
+ "httpMethod": "POST",
+ "isBase64Encoded": True,
+ "queryStringParameters": {"foo": "bar"},
+ "multiValueQueryStringParameters": {"foo": ["bar"]},
+ "pathParameters": {"proxy": "/path/to/resource"},
+ "stageVariables": {"baz": "qux"},
+ "headers": {
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "Accept-Encoding": "gzip, deflate, sdch",
+ "Accept-Language": "en-US,en;q=0.8",
+ "Cache-Control": "max-age=0",
+ "CloudFront-Forwarded-Proto": "https",
+ "CloudFront-Is-Desktop-Viewer": "true",
+ "CloudFront-Is-Mobile-Viewer": "false",
+ "CloudFront-Is-SmartTV-Viewer": "false",
+ "CloudFront-Is-Tablet-Viewer": "false",
+ "CloudFront-Viewer-Country": "US",
+ "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Custom User Agent String",
+ "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
+ "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
+ "X-Forwarded-Port": "443",
+ "X-Forwarded-Proto": "https",
+ },
+ "multiValueHeaders": {
+ "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"],
+ "Accept-Encoding": ["gzip, deflate, sdch"],
+ "Accept-Language": ["en-US,en;q=0.8"],
+ "Cache-Control": ["max-age=0"],
+ "CloudFront-Forwarded-Proto": ["https"],
+ "CloudFront-Is-Desktop-Viewer": ["true"],
+ "CloudFront-Is-Mobile-Viewer": ["false"],
+ "CloudFront-Is-SmartTV-Viewer": ["false"],
+ "CloudFront-Is-Tablet-Viewer": ["false"],
+ "CloudFront-Viewer-Country": ["US"],
+ "Host": ["0123456789.execute-api.us-east-1.amazonaws.com"],
+ "Upgrade-Insecure-Requests": ["1"],
+ "User-Agent": ["Custom User Agent String"],
+ "Via": ["1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)"],
+ "X-Amz-Cf-Id": ["cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA=="],
+ "X-Forwarded-For": ["127.0.0.1, 127.0.0.2"],
+ "X-Forwarded-Port": ["443"],
+ "X-Forwarded-Proto": ["https"],
+ },
+ "requestContext": {
+ "accountId": "123456789012",
+ "resourceId": "123456",
+ "stage": "prod",
+ "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
+ "requestTime": "09/Apr/2015:12:34:56 +0000",
+ "requestTimeEpoch": 1428582896000,
+ "path": "/prod/path/to/resource",
+ "resourcePath": "/{proxy+}",
+ "httpMethod": "POST",
+ "apiId": "1234567890",
+ "protocol": "HTTP/1.1",
+ },
+ }
+
+
+@pytest.fixture
+def sns_event():
+ return {
+ "Records": [
+ {
+ "EventSource": "aws:sns",
+ "EventVersion": "1.0",
+ "EventSubscriptionArn": "arn:aws:sns:us-east-1::ExampleTopic",
+ "Sns": {
+ "Type": "Notification",
+ "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
+ "TopicArn": "arn:aws:sns:us-east-1:123456789012:ExampleTopic",
+ "Subject": "example subject",
+ "Message": '{"message": "hello world", "username": "lessa"}',
+ "Timestamp": "1970-01-01T00:00:00.000Z",
+ "SignatureVersion": "1",
+ "Signature": "EXAMPLE",
+ "SigningCertUrl": "EXAMPLE",
+ "UnsubscribeUrl": "EXAMPLE",
+ "MessageAttributes": {
+ "Test": {"Type": "String", "Value": "TestString"},
+ "TestBinary": {"Type": "Binary", "Value": "TestBinary"},
+ },
+ },
+ }
+ ]
+ }
+
+
+@pytest.fixture
+def kinesis_event():
+ return {
+ "Records": [
+ {
+ "kinesis": {
+ "partitionKey": "partitionKey-03",
+ "kinesisSchemaVersion": "1.0",
+ "data": "eyJtZXNzYWdlIjogImhlbGxvIGhlbGxvIiwgInVzZXJuYW1lIjogImJsYWggYmxhaCJ9=",
+ "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
+ "approximateArrivalTimestamp": 1428537600.0,
+ },
+ "eventSource": "aws:kinesis",
+ "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",
+ "invokeIdentityArn": "arn:aws:iam::EXAMPLE",
+ "eventVersion": "1.0",
+ "eventName": "aws:kinesis:record",
+ "eventSourceARN": "arn:aws:kinesis:EXAMPLE",
+ "awsRegion": "us-east-1",
+ }
+ ]
+ }
+
+
+@pytest.fixture
+def eventbridge_event():
+ return {
+ "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
+ "detail-type": "Scheduled Event",
+ "source": "aws.events",
+ "account": "123456789012",
+ "time": "1970-01-01T00:00:00Z",
+ "region": "us-east-1",
+ "resources": ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"],
+ "detail": {"message": "hello hello", "username": "blah blah"},
+ }
+
+
+@pytest.fixture
+def sqs_event():
+ return {
+ "Records": [
+ {
+ "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
+ "receiptHandle": "MessageReceiptHandle",
+ "body": '{"message": "hello world", "username": "lessa"}',
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1523232000000",
+ "SenderId": "123456789012",
+ "ApproximateFirstReceiveTimestamp": "1523232000001",
+ },
+ "messageAttributes": {},
+ "md5OfBody": "7b270e59b47ff90a553787216d55d91d",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
+ "awsRegion": "us-east-1",
+ },
+ ]
+ }
+
+
+@pytest.fixture
+def cloudwatch_logs_event():
+ return {
+ "awslogs": {
+ "data": "H4sIACZAXl8C/52PzUrEMBhFX2UILpX8tPbHXWHqIOiq3Q1F0ubrWEiakqTWofTdTYYB0YWL2d5zvnuTFellBIOedoiyKH5M0iwnlKH7HZL6dDB6ngLDfLFYctUKjie9gHFaS/sAX1xNEq525QxwFXRGGMEkx4Th491rUZdV3YiIZ6Ljfd+lfSyAtZloacQgAkqSJCGhxM6t7cwwuUGPz4N0YKyvO6I9WDeMPMSo8Z4Ca/kJ6vMEYW5f1MX7W1lVxaG8vqX8hNFdjlc0iCBBSF4ERT/3Pl7RbMGMXF2KZMh/C+gDpNS7RRsp0OaRGzx0/t8e0jgmcczyLCWEePhni/23JWalzjdu0a3ZvgEaNLXeugEAAA==" # noqa: E501
+ }
+ }
+
+
+@pytest.fixture
+def cloudwatch_logs_schema():
+ return {
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "$id": "http://example.com/example.json",
+ "type": "array",
+ "title": "Sample schema",
+ "description": "Sample JSON Schema for CloudWatch Logs logEvents using structured dummy data",
+ "examples": [
+ [
+ {
+ "id": "eventId1",
+ "message": {"username": "lessa", "message": "hello world"},
+ "timestamp": 1440442987000,
+ },
+ {
+ "id": "eventId2",
+ "message": {"username": "dummy", "message": "hello world"},
+ "timestamp": 1440442987001,
+ },
+ ]
+ ],
+ "additionalItems": True,
+ "items": {
+ "$id": "#/items",
+ "anyOf": [
+ {
+ "$id": "#/items/anyOf/0",
+ "type": "object",
+ "title": "The first anyOf schema",
+ "description": "Actual log data found in CloudWatch Logs logEvents key",
+ "required": ["id", "message", "timestamp"],
+ "properties": {
+ "id": {
+ "$id": "#/items/anyOf/0/properties/id",
+ "type": "string",
+ "title": "The id schema",
+ "description": "Unique identifier for log event",
+ "default": "",
+ "examples": ["eventId1"],
+ },
+ "message": {
+ "$id": "#/items/anyOf/0/properties/message",
+ "type": "object",
+ "title": "The message schema",
+ "description": "Log data captured in CloudWatch Logs",
+ "default": {},
+ "examples": [{"username": "lessa", "message": "hello world"}],
+ "required": ["username", "message"],
+ "properties": {
+ "username": {
+ "$id": "#/items/anyOf/0/properties/message/properties/username",
+ "type": "string",
+ "title": "The username",
+ "examples": ["lessa"],
+ },
+ "message": {
+ "$id": "#/items/anyOf/0/properties/message/properties/message",
+ "type": "string",
+ "title": "The message",
+ "examples": ["hello world"],
+ },
+ },
+ "additionalProperties": True,
+ },
+ "timestamp": {
+ "$id": "#/items/anyOf/0/properties/timestamp",
+ "type": "integer",
+ "title": "The timestamp schema",
+ "description": "Log event epoch timestamp in milliseconds",
+ "default": 0,
+ "examples": [1440442987000],
+ },
+ },
+ }
+ ],
+ },
+ }
diff --git a/tests/functional/validator/test_validator.py b/tests/functional/validator/test_validator.py
new file mode 100644
index 00000000000..7d2a8465529
--- /dev/null
+++ b/tests/functional/validator/test_validator.py
@@ -0,0 +1,123 @@
+import jmespath
+import pytest
+from jmespath import functions
+
+from aws_lambda_powertools.utilities.validation import envelopes, exceptions, validate, validator
+
+
+def test_validate_raw_event(schema, raw_event):
+ validate(event=raw_event, schema=schema)
+
+
+def test_validate_wrapped_event_raw_envelope(schema, wrapped_event):
+ validate(event=wrapped_event, schema=schema, envelope="data.payload")
+
+
+def test_validate_json_string_envelope(schema, wrapped_event_json_string):
+ validate(event=wrapped_event_json_string, schema=schema, envelope="powertools_json(data).payload")
+
+
+def test_validate_base64_string_envelope(schema, wrapped_event_base64_json_string):
+ validate(event=wrapped_event_base64_json_string, schema=schema, envelope="powertools_json(powertools_base64(data))")
+
+
+def test_validate_event_does_not_conform_with_schema(schema):
+ with pytest.raises(exceptions.SchemaValidationError):
+ validate(event={"message": "hello_world"}, schema=schema)
+
+
+def test_validate_json_string_no_envelope(schema, wrapped_event_json_string):
+ # WHEN data key contains a JSON String
+ with pytest.raises(exceptions.SchemaValidationError, match=".*data must be object"):
+ validate(event=wrapped_event_json_string, schema=schema, envelope="data.payload")
+
+
+def test_validate_invalid_schema_format(raw_event):
+ with pytest.raises(exceptions.InvalidSchemaFormatError):
+ validate(event=raw_event, schema="schema.json")
+
+
+def test_validate_invalid_envelope_expression(schema, wrapped_event):
+ with pytest.raises(exceptions.InvalidEnvelopeExpressionError):
+ validate(event=wrapped_event, schema=schema, envelope=True)
+
+
+def test_validate_invalid_event(schema):
+ b64_event = "eyJtZXNzYWdlIjogImhlbGxvIGhlbGxvIiwgInVzZXJuYW1lIjogImJsYWggYmxhaCJ9="
+ with pytest.raises(exceptions.SchemaValidationError):
+ validate(event=b64_event, schema=schema)
+
+
+def test_apigateway_envelope(schema, apigateway_event):
+ # Payload v1 and v2 remains consistent where the payload is (body)
+ validate(event=apigateway_event, schema=schema, envelope=envelopes.API_GATEWAY_REST)
+ validate(event=apigateway_event, schema=schema, envelope=envelopes.API_GATEWAY_HTTP)
+
+
+def test_sqs_envelope(sqs_event, schema_array):
+ validate(event=sqs_event, schema=schema_array, envelope=envelopes.SQS)
+
+
+def test_sns_envelope(schema, sns_event):
+ validate(event=sns_event, schema=schema, envelope=envelopes.SNS)
+
+
+def test_eventbridge_envelope(schema, eventbridge_event):
+ validate(event=eventbridge_event, schema=schema, envelope=envelopes.EVENTBRIDGE)
+
+
+def test_kinesis_data_stream_envelope(schema_array, kinesis_event):
+ validate(event=kinesis_event, schema=schema_array, envelope=envelopes.KINESIS_DATA_STREAM)
+
+
+def test_cloudwatch_logs_envelope(cloudwatch_logs_schema, cloudwatch_logs_event):
+ validate(event=cloudwatch_logs_event, schema=cloudwatch_logs_schema, envelope=envelopes.CLOUDWATCH_LOGS)
+
+
+def test_validator_incoming(schema, raw_event):
+ @validator(inbound_schema=schema)
+ def lambda_handler(evt, context):
+ pass
+
+ lambda_handler(raw_event, {})
+
+
+def test_validator_outgoing(schema_response, raw_response):
+ @validator(outbound_schema=schema_response)
+ def lambda_handler(evt, context):
+ return raw_response
+
+ lambda_handler({}, {})
+
+
+def test_validator_incoming_and_outgoing(schema, schema_response, raw_event, raw_response):
+ @validator(inbound_schema=schema, outbound_schema=schema_response)
+ def lambda_handler(evt, context):
+ return raw_response
+
+ lambda_handler(raw_event, {})
+
+
+def test_validator_propagates_exception(schema, raw_event, schema_response):
+ @validator(inbound_schema=schema, outbound_schema=schema_response)
+ def lambda_handler(evt, context):
+ raise ValueError("Bubble up")
+
+ with pytest.raises(ValueError):
+ lambda_handler(raw_event, {})
+
+
+def test_custom_jmespath_function_overrides_builtin_functions(schema, wrapped_event_json_string):
+ class CustomFunctions(functions.Functions):
+ @functions.signature({"types": ["string"]})
+ def _func_echo_decoder(self, value):
+ return value
+
+ jmespath_opts = {"custom_functions": CustomFunctions()}
+ with pytest.raises(jmespath.exceptions.UnknownFunctionError, match="Unknown function: powertools_json()"):
+ validate(
+ event=wrapped_event_json_string,
+ schema=schema,
+ envelope="powertools_json(data).payload",
+ jmespath_options=jmespath_opts,
+ )