Skip to content

Commit d4270f1

Browse files
committed
feat: add support for fastjsonschema's handlers param in validation utilities
1 parent dedbdef commit d4270f1

File tree

4 files changed

+79
-5
lines changed

4 files changed

+79
-5
lines changed

aws_lambda_powertools/utilities/validation/base.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
logger = logging.getLogger(__name__)
99

1010

11-
def validate_data_against_schema(data: Union[Dict, str], schema: Dict, formats: Optional[Dict] = None):
11+
def validate_data_against_schema(
12+
data: Union[Dict, str], schema: Dict, formats: Optional[Dict] = None, handlers: Optional[Dict] = None
13+
):
1214
"""Validate dict data against given JSON Schema
1315
1416
Parameters
@@ -19,6 +21,8 @@ def validate_data_against_schema(data: Union[Dict, str], schema: Dict, formats:
1921
JSON Schema to validate against
2022
formats: Dict
2123
Custom formats containing a key (e.g. int64) and a value expressed as regex or callback returning bool
24+
handlers: Dict
25+
Custom methods to retrieve remote schemes, keyed off of URI scheme
2226
2327
Raises
2428
------
@@ -29,7 +33,8 @@ def validate_data_against_schema(data: Union[Dict, str], schema: Dict, formats:
2933
"""
3034
try:
3135
formats = formats or {}
32-
fastjsonschema.validate(definition=schema, data=data, formats=formats)
36+
handlers = handlers or {}
37+
fastjsonschema.validate(definition=schema, data=data, formats=formats, handlers=handlers)
3338
except (TypeError, AttributeError, fastjsonschema.JsonSchemaDefinitionException) as e:
3439
raise InvalidSchemaFormatError(f"Schema received: {schema}, Formats: {formats}. Error: {e}")
3540
except fastjsonschema.JsonSchemaValueException as e:

aws_lambda_powertools/utilities/validation/validator.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ def validator(
1616
context: Any,
1717
inbound_schema: Optional[Dict] = None,
1818
inbound_formats: Optional[Dict] = None,
19+
inbound_handlers: Optional[Dict] = None,
1920
outbound_schema: Optional[Dict] = None,
2021
outbound_formats: Optional[Dict] = None,
22+
outbound_handlers: Optional[Dict] = None,
2123
envelope: str = "",
2224
jmespath_options: Optional[Dict] = None,
2325
**kwargs: Any,
@@ -44,6 +46,10 @@ def validator(
4446
Custom formats containing a key (e.g. int64) and a value expressed as regex or callback returning bool
4547
outbound_formats: Dict
4648
Custom formats containing a key (e.g. int64) and a value expressed as regex or callback returning bool
49+
inbound_handlers: Dict
50+
Custom methods to retrieve remote schemes, keyed off of URI scheme
51+
outbound_handlers: Dict
52+
Custom methods to retrieve remote schemes, keyed off of URI scheme
4753
4854
Example
4955
-------
@@ -127,13 +133,17 @@ def handler(event, context):
127133

128134
if inbound_schema:
129135
logger.debug("Validating inbound event")
130-
validate_data_against_schema(data=event, schema=inbound_schema, formats=inbound_formats)
136+
validate_data_against_schema(
137+
data=event, schema=inbound_schema, formats=inbound_formats, handlers=inbound_handlers
138+
)
131139

132140
response = handler(event, context, **kwargs)
133141

134142
if outbound_schema:
135143
logger.debug("Validating outbound event")
136-
validate_data_against_schema(data=response, schema=outbound_schema, formats=outbound_formats)
144+
validate_data_against_schema(
145+
data=response, schema=outbound_schema, formats=outbound_formats, handlers=outbound_handlers
146+
)
137147

138148
return response
139149

@@ -142,6 +152,7 @@ def validate(
142152
event: Any,
143153
schema: Dict,
144154
formats: Optional[Dict] = None,
155+
handlers: Optional[Dict] = None,
145156
envelope: Optional[str] = None,
146157
jmespath_options: Optional[Dict] = None,
147158
):
@@ -161,6 +172,8 @@ def validate(
161172
Alternative JMESPath options to be included when filtering expr
162173
formats: Dict
163174
Custom formats containing a key (e.g. int64) and a value expressed as regex or callback returning bool
175+
handlers: Dict
176+
Custom methods to retrieve remote schemes, keyed off of URI scheme
164177
165178
Example
166179
-------
@@ -229,4 +242,4 @@ def handler(event, context):
229242
jmespath_options=jmespath_options,
230243
)
231244

232-
validate_data_against_schema(data=event, schema=schema, formats=formats)
245+
validate_data_against_schema(data=event, schema=schema, formats=formats, handlers=handlers)

tests/functional/validator/conftest.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,53 @@ def schema_response():
8585
}
8686

8787

88+
@pytest.fixture
89+
def schema_refs():
90+
return {
91+
"ParentSchema": {
92+
"$schema": "http://json-schema.org/draft-07/schema",
93+
"$id": "testschema://ParentSchema",
94+
"type": "object",
95+
"title": "Sample schema",
96+
"description": "Sample JSON Schema that references another schema",
97+
"examples": [{"parent_object": {"child_string": "hello world"}}],
98+
"required": "parent_object",
99+
"properties": {
100+
"parent_object": {
101+
"$id": "#/properties/parent_object",
102+
"$ref": "testschema://ChildSchema",
103+
},
104+
},
105+
},
106+
"ChildSchema": {
107+
"$schema": "http://json-schema.org/draft-07/schema",
108+
"$id": "testschema://ChildSchema",
109+
"type": "object",
110+
"title": "Sample schema",
111+
"description": "Sample JSON Schema that is referenced by another schema",
112+
"examples": [{"child_string": "hello world"}],
113+
"required": "child_string",
114+
"properties": {
115+
"child_string": {
116+
"$id": "#/properties/child_string",
117+
"type": "string",
118+
"title": "The child string",
119+
"examples": ["hello world"],
120+
},
121+
},
122+
},
123+
}
124+
125+
126+
@pytest.fixture
127+
def schema_ref_handlers(schema_refs):
128+
def handle_test_schema(uri):
129+
schema_key = uri.split("://")[1]
130+
return schema_refs[schema_key]
131+
132+
return {"testschema": handle_test_schema}
133+
134+
88135
@pytest.fixture
89136
def raw_event():
90137
return {"message": "hello hello", "username": "blah blah"}
@@ -105,6 +152,11 @@ def wrapped_event_base64_json_string():
105152
return {"data": "eyJtZXNzYWdlIjogImhlbGxvIGhlbGxvIiwgInVzZXJuYW1lIjogImJsYWggYmxhaCJ9="}
106153

107154

155+
@pytest.fixture
156+
def parent_ref_event():
157+
return {"parent_object": {"child_string": "hello world"}}
158+
159+
108160
@pytest.fixture
109161
def raw_response():
110162
return {"statusCode": 200, "body": "response"}

tests/functional/validator/test_validator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ def test_validate_invalid_custom_format(
8383
)
8484

8585

86+
def test_validate_custom_handlers(schema_refs, schema_ref_handlers, parent_ref_event):
87+
validate(event=parent_ref_event, schema=schema_refs["ParentSchema"], handlers=schema_ref_handlers)
88+
89+
8690
def test_validate_invalid_envelope_expression(schema, wrapped_event):
8791
with pytest.raises(exceptions.InvalidEnvelopeExpressionError):
8892
validate(event=wrapped_event, schema=schema, envelope=True)

0 commit comments

Comments
 (0)