diff --git a/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py b/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py index 7339ed33fce..0fedf55dc25 100644 --- a/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py +++ b/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py @@ -73,6 +73,14 @@ def _deserialize_bool(self, value: bool) -> bool: return value def _deserialize_n(self, value: str) -> Decimal: + value = value.lstrip("0") + if len(value) > 38: + # See: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number + # Calculate the number of trailing zeros after the 38th character + tail = len(value[38:]) - len(value[38:].rstrip("0")) + # Trim the value: remove trailing zeros if any, or just take the first 38 characters + value = value[:-tail] if tail > 0 else value[:38] + return DYNAMODB_CONTEXT.create_decimal(value) def _deserialize_s(self, value: str) -> str: diff --git a/tests/unit/data_classes/required_dependencies/test_dynamo_db_stream_event.py b/tests/unit/data_classes/required_dependencies/test_dynamo_db_stream_event.py index f7672abd69b..464d9a0eba1 100644 --- a/tests/unit/data_classes/required_dependencies/test_dynamo_db_stream_event.py +++ b/tests/unit/data_classes/required_dependencies/test_dynamo_db_stream_event.py @@ -8,14 +8,15 @@ ) from tests.functional.utils import load_event +DECIMAL_CONTEXT = Context( + Emin=-128, + Emax=126, + prec=38, + traps=[Clamped, Overflow, Inexact, Rounded, Underflow], +) + def test_dynamodb_stream_trigger_event(): - decimal_context = Context( - Emin=-128, - Emax=126, - prec=38, - traps=[Clamped, Overflow, Inexact, Rounded, Underflow], - ) raw_event = load_event("dynamoStreamEvent.json") parsed_event = DynamoDBStreamEvent(raw_event) @@ -36,7 +37,7 @@ def test_dynamodb_stream_trigger_event(): assert dynamodb.approximate_creation_date_time == record_raw["dynamodb"]["ApproximateCreationDateTime"] keys = dynamodb.keys assert keys is not None - assert keys["Id"] == decimal_context.create_decimal(101) + assert keys["Id"] == DECIMAL_CONTEXT.create_decimal(101) assert dynamodb.new_image.get("Message") == record_raw["dynamodb"]["NewImage"]["Message"]["S"] assert dynamodb.old_image is None assert dynamodb.sequence_number == record_raw["dynamodb"]["SequenceNumber"] @@ -44,6 +45,36 @@ def test_dynamodb_stream_trigger_event(): assert dynamodb.stream_view_type == StreamViewType.NEW_AND_OLD_IMAGES +def test_dynamodb_stream_record_deserialization_large_int(): + data = { + "Keys": {"key1": {"attr1": "value1"}}, + "NewImage": { + "Name": {"S": "Joe"}, + "Age": {"N": "000000011011111111111111000000000000000000000000000000"}, + }, + } + record = StreamRecord(data) + assert record.new_image == { + "Name": "Joe", + "Age": DECIMAL_CONTEXT.create_decimal("11011111111111111000000000000000000000"), + } + + +def test_dynamodb_stream_record_deserialization_large_int_without_trailing_zeros(): + data = { + "Keys": {"key1": {"attr1": "value1"}}, + "NewImage": { + "Name": {"S": "Joe"}, + "Age": {"N": "000000011011111111111112222222222221111111111111111111111"}, + }, + } + record = StreamRecord(data) + assert record.new_image == { + "Name": "Joe", + "Age": DECIMAL_CONTEXT.create_decimal("11011111111111112222222222221111111111"), + } + + def test_dynamodb_stream_record_deserialization(): byte_list = [s.encode("utf-8") for s in ["item1", "item2"]] decimal_context = Context(