Skip to content

Commit bce7aab

Browse files
author
Ran Isenberg
committed
added SQS schema & tests and sns skeleton
1 parent d50e261 commit bce7aab

File tree

10 files changed

+222
-16
lines changed

10 files changed

+222
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Validation utility
22
"""
3-
from .envelopes import DynamoDBEnvelope, EventBridgeEnvelope, UserEnvelope
3+
from .envelopes import DynamoDBEnvelope, EventBridgeEnvelope, SnsEnvelope, SqsEnvelope, UserEnvelope
44
from .validator import validator
55

6-
__all__ = ["UserEnvelope", "DynamoDBEnvelope", "EventBridgeEnvelope", "validator"]
6+
__all__ = ["UserEnvelope", "DynamoDBEnvelope", "EventBridgeEnvelope", "SnsEnvelope", "SqsEnvelope", "validator"]
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
from .base import UserEnvelope
22
from .dynamodb import DynamoDBEnvelope
33
from .event_bridge import EventBridgeEnvelope
4+
from .sns import SnsEnvelope
5+
from .sqs import SqsEnvelope
6+
47

58
__all__ = [
69
"UserEnvelope",
710
"DynamoDBEnvelope",
811
"EventBridgeEnvelope",
12+
"SqsEnvelope",
13+
"SnsEnvelope"
914
]

aws_lambda_powertools/utilities/validation/envelopes/base.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,19 @@ def _parse_user_dict_schema(self, user_event: Dict[str, Any], inbound_schema_mod
1313
try:
1414
return inbound_schema_model(**user_event)
1515
except (ValidationError, TypeError):
16-
logger.exception("Valdation exception while extracting user custom schema")
16+
logger.exception("Validation exception while extracting user custom schema")
1717
raise
1818

1919
def _parse_user_json_string_schema(self, user_event: str, inbound_schema_model: BaseModel) -> Any:
2020
logger.debug("parsing user dictionary schema")
21+
if inbound_schema_model == str:
22+
logger.debug("input is string, returning")
23+
return user_event
24+
logger.debug("trying to parse as json encoded string")
2125
try:
2226
return inbound_schema_model.parse_raw(user_event)
2327
except (ValidationError, TypeError):
24-
logger.exception("Valdation exception while extracting user custom schema")
28+
logger.exception("Validation exception while extracting user custom schema")
2529
raise
2630

2731
@abstractmethod
@@ -34,5 +38,5 @@ def parse(self, event: Dict[str, Any], inbound_schema_model: BaseModel) -> Any:
3438
try:
3539
return inbound_schema_model(**event)
3640
except (ValidationError, TypeError):
37-
logger.exception("Valdation exception received from input user custom envelopes event")
41+
logger.exception("Validation exception received from input user custom envelopes event")
3842
raise

aws_lambda_powertools/utilities/validation/envelopes/dynamodb.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
from pydantic import BaseModel, ValidationError
55

6-
from ..schemas import DynamoDBSchema
7-
from .base import BaseEnvelope
6+
from aws_lambda_powertools.utilities.validation.envelopes.base import BaseEnvelope
7+
from aws_lambda_powertools.utilities.validation.schemas import DynamoDBSchema
88

99
logger = logging.getLogger(__name__)
1010

@@ -14,7 +14,7 @@ def parse(self, event: Dict[str, Any], inbound_schema_model: BaseModel) -> Any:
1414
try:
1515
parsed_envelope = DynamoDBSchema(**event)
1616
except (ValidationError, TypeError):
17-
logger.exception("Valdation exception received from input dynamodb stream event")
17+
logger.exception("Validation exception received from input dynamodb stream event")
1818
raise
1919
output = []
2020
for record in parsed_envelope.Records:

aws_lambda_powertools/utilities/validation/envelopes/event_bridge.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
from pydantic import BaseModel, ValidationError
55

6-
from ..schemas import EventBridgeSchema
7-
from .base import BaseEnvelope
6+
from aws_lambda_powertools.utilities.validation.envelopes.base import BaseEnvelope
7+
from aws_lambda_powertools.utilities.validation.schemas import EventBridgeSchema
8+
89

910
logger = logging.getLogger(__name__)
1011

@@ -14,6 +15,6 @@ def parse(self, event: Dict[str, Any], inbound_schema_model: BaseModel) -> Any:
1415
try:
1516
parsed_envelope = EventBridgeSchema(**event)
1617
except (ValidationError, TypeError):
17-
logger.exception("Valdation exception received from input eventbridge event")
18+
logger.exception("Validation exception received from input eventbridge event")
1819
raise
1920
return self._parse_user_dict_schema(parsed_envelope.detail, inbound_schema_model)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import logging
2+
from typing import Any, Dict
3+
4+
from pydantic import BaseModel, ValidationError
5+
6+
from aws_lambda_powertools.utilities.validation.envelopes.base import BaseEnvelope
7+
from aws_lambda_powertools.utilities.validation.schemas import SnsSchema
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class SnsEnvelope(BaseEnvelope):
13+
def parse(self, event: Dict[str, Any], inbound_schema_model: BaseModel) -> Any:
14+
try:
15+
parsed_envelope = SnsSchema(**event)
16+
except (ValidationError, TypeError):
17+
logger.exception("Validation exception received from input sqs event")
18+
raise
19+
## TODO
20+
return None
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import logging
2+
from typing import Any, Dict
3+
4+
from pydantic import BaseModel, ValidationError
5+
6+
from aws_lambda_powertools.utilities.validation.envelopes.base import BaseEnvelope
7+
from aws_lambda_powertools.utilities.validation.schemas import SqsSchema
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class SqsEnvelope(BaseEnvelope):
13+
def parse(self, event: Dict[str, Any], inbound_schema_model: BaseModel) -> Any:
14+
try:
15+
parsed_envelope = SqsSchema(**event)
16+
except (ValidationError, TypeError):
17+
logger.exception("Validation exception received from input sqs event")
18+
raise
19+
output = []
20+
for record in parsed_envelope.Records:
21+
parsed_msg = self._parse_user_json_string_schema(record.body, inbound_schema_model)
22+
output.append({"body": parsed_msg, "attributes": record.messageAttributes})
23+
return output
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,60 @@
1-
from pydantic import BaseModel
1+
import re
2+
from datetime import datetime
3+
from typing import Dict, List, Optional
4+
5+
from pydantic import BaseModel, root_validator, validator
6+
from typing_extensions import Literal
7+
8+
9+
class SqsAttributesSchema(BaseModel):
10+
ApproximateReceiveCount: str
11+
ApproximateFirstReceiveTimestamp: datetime
12+
MessageDeduplicationId: Optional[str]
13+
MessageGroupId: Optional[str]
14+
SenderId: str
15+
SentTimestamp: datetime
16+
SequenceNumber: Optional[str]
17+
18+
19+
class SqsMsgAttributeSchema(BaseModel):
20+
stringValue: Optional[str]
21+
binaryValue: Optional[str]
22+
stringListValues: List[str] = []
23+
binaryListValues: List[str] = []
24+
dataType: str
25+
26+
@validator("dataType")
27+
def valid_type(cls, v): # noqa: VNE001
28+
pattern = re.compile("Number.*|String.*|Binary.*")
29+
if not pattern.match(v):
30+
raise TypeError("data type is invalid")
31+
return v
32+
33+
@root_validator
34+
def check_str_and_binary_values(cls, values):
35+
binary_val, str_val = values.get("binaryValue", ""), values.get("stringValue", "")
36+
dataType = values.get("dataType")
37+
if not str_val and not binary_val:
38+
raise TypeError("both binaryValue and stringValue are missing")
39+
if dataType.startswith("Binary") and not binary_val:
40+
raise TypeError("binaryValue is missing")
41+
if (dataType.startswith("String") or dataType.startswith("Number")) and not str_val:
42+
raise TypeError("stringValue is missing")
43+
return values
44+
45+
46+
class SqsRecordSchema(BaseModel):
47+
messageId: str
48+
receiptHandle: str
49+
body: str
50+
attributes: SqsAttributesSchema
51+
messageAttributes: Dict[str, SqsMsgAttributeSchema]
52+
md5OfBody: str
53+
md5OfMessageAttributes: str
54+
eventSource: Literal["aws:sqs"]
55+
eventSourceARN: str
56+
awsRegion: str
257

358

459
class SqsSchema(BaseModel):
5-
todo: str
60+
Records: List[SqsRecordSchema]

aws_lambda_powertools/utilities/validation/validator.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
from pydantic import BaseModel, ValidationError
55

66
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
7-
8-
from .envelopes.base import BaseEnvelope
7+
from aws_lambda_powertools.utilities.validation.envelopes.base import BaseEnvelope
98

109
logger = logging.getLogger(__name__)
1110

tests/functional/test_validator.py

+100-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
from pydantic import BaseModel
88
from pydantic.error_wrappers import ValidationError
99

10-
from aws_lambda_powertools.utilities.validation import DynamoDBEnvelope, EventBridgeEnvelope, UserEnvelope, validator
10+
from aws_lambda_powertools.utilities.validation import (
11+
DynamoDBEnvelope,
12+
EventBridgeEnvelope,
13+
SqsEnvelope,
14+
UserEnvelope,
15+
validator
16+
)
1117

1218

1319
class OutboundSchema(BaseModel):
@@ -60,6 +66,7 @@ class MyMessage(BaseModel):
6066
@validator(inbound_schema_model=MyMessage, outbound_schema_model=OutboundSchema, envelope=DynamoDBEnvelope())
6167
def dynamodb_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Optional[Any]]:
6268
assert event["custom"]
69+
assert len(event["custom"]) == 3
6370
# first record
6471
assert not event["custom"][0]["old"]
6572
assert event["custom"][0]["new"].message == "hello"
@@ -169,3 +176,95 @@ def test_eventbridge_fail_inbound_validation():
169176
event = {"greeting": "hello"}
170177
with pytest.raises(ValidationError):
171178
eventbridge_handler(event, LambdaContext())
179+
180+
181+
sqs_event_attribs = {
182+
"test4": {
183+
"stringValue": "dfgdfgfd",
184+
"stringListValues": [],
185+
"binaryListValues": [],
186+
"dataType": "String.custom_type",
187+
},
188+
"test5": {"stringValue": "a,b,c,d", "stringListValues": [], "binaryListValues": [], "dataType": "String"},
189+
"tes6": {"stringValue": "112.1", "stringListValues": [], "binaryListValues": [], "dataType": "Number.mytype"},
190+
"test2": {"stringValue": "111", "stringListValues": [], "binaryListValues": [], "dataType": "Number"},
191+
"test3": {"binaryValue": "w5NNNcOXXXU=", "stringListValues": [], "binaryListValues": [], "dataType": "Binary"},
192+
"test": {"stringValue": "gfgf", "stringListValues": [], "binaryListValues": [], "dataType": "String"},
193+
}
194+
195+
196+
@validator(inbound_schema_model=MyMessage, outbound_schema_model=OutboundSchema, envelope=SqsEnvelope())
197+
def sqs_json_body_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Optional[Any]]:
198+
assert event["custom"]
199+
assert len(event["custom"]) == 1
200+
assert event["orig"]
201+
assert event["custom"][0]["body"].message == "hello"
202+
assert event["custom"][0]["body"].messageId == 8
203+
assert len(event["custom"][0]["attributes"]) == len(sqs_event_attribs)
204+
return {"response_code": 200, "message": "working"}
205+
206+
207+
def test_sqs_ok_json_string_body_validation():
208+
event = {
209+
"Records": [
210+
{
211+
"messageId": "1743e893-cc24-1234-88f8-f80c37dcd923",
212+
"receiptHandle": "AKhXK7azPaZHY0zjmTsdfsdfdsfOgcVob",
213+
"body": '{"message": "hello", "messageId": 8}',
214+
"attributes": {
215+
"ApproximateReceiveCount": "1",
216+
"SentTimestamp": "1598117108660",
217+
"SenderId": "43434dsdfd:sdfds",
218+
"ApproximateFirstReceiveTimestamp": "1598117108667",
219+
},
220+
"messageAttributes": sqs_event_attribs,
221+
"md5OfBody": "4db76498a982d84c188927c585076a6c",
222+
"md5OfMessageAttributes": "7186428dc148b402947274e0bb41e7ee",
223+
"eventSource": "aws:sqs",
224+
"eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:mytest",
225+
"awsRegion": "us-west-1",
226+
}
227+
]
228+
}
229+
sqs_json_body_handler(event, LambdaContext())
230+
231+
232+
@validator(inbound_schema_model=str, outbound_schema_model=OutboundSchema, envelope=SqsEnvelope())
233+
def sqs_string_body_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Optional[Any]]:
234+
assert event["custom"]
235+
assert len(event["custom"]) == 1
236+
assert event["orig"]
237+
assert event["custom"][0]["body"] == "hello how are you"
238+
assert len(event["custom"][0]["attributes"]) == len(sqs_event_attribs)
239+
return {"response_code": 200, "message": "working"}
240+
241+
242+
def test_sqs_ok_json_string_validation():
243+
event = {
244+
"Records": [
245+
{
246+
"messageId": "1743e893-cc24-1234-88f8-f80c37dcd923",
247+
"receiptHandle": "AKhXK7azPaZHY0zjmTsdfsdfdsfOgcVob",
248+
"body": "hello how are you",
249+
"attributes": {
250+
"ApproximateReceiveCount": "1",
251+
"SentTimestamp": "1598117108660",
252+
"SenderId": "43434dsdfd:sdfds",
253+
"ApproximateFirstReceiveTimestamp": "1598117108667",
254+
},
255+
"messageAttributes": sqs_event_attribs,
256+
"md5OfBody": "4db76498a982d84c188927c585076a6c",
257+
"md5OfMessageAttributes": "7186428dc148b402947274e0bb41e7ee",
258+
"eventSource": "aws:sqs",
259+
"eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:mytest",
260+
"awsRegion": "us-west-1",
261+
}
262+
]
263+
}
264+
sqs_string_body_handler(event, LambdaContext())
265+
266+
267+
def test_sqs_fail_inbound_validation():
268+
event = {"greeting": "hello"}
269+
with pytest.raises(ValidationError):
270+
sqs_string_body_handler(event, LambdaContext())

0 commit comments

Comments
 (0)