From ff767d07437ca41b95ebe0bed93b7c1a1795e7ff Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Mon, 9 May 2022 13:01:31 +0000 Subject: [PATCH] DynamoDB - error when sending an integer as string (#5108) --- moto/dynamodb/exceptions.py | 6 ++++ moto/dynamodb/models/__init__.py | 10 ++++++ .../exceptions/test_dynamodb_exceptions.py | 34 ++++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/moto/dynamodb/exceptions.py b/moto/dynamodb/exceptions.py index 5f7bed4a1..b08448aa9 100644 --- a/moto/dynamodb/exceptions.py +++ b/moto/dynamodb/exceptions.py @@ -310,3 +310,9 @@ class StreamAlreadyEnabledException(JsonRESTError): def __init__(self): er = "com.amazonaws.dynamodb.v20111205#ResourceInUseException" super().__init__(er, "Cannot enable stream") + + +class InvalidConversion(JsonRESTError): + def __init__(self): + er = "SerializationException" + super().__init__(er, "NUMBER_VALUE cannot be converted to String") diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py index 68e726451..7d1614149 100644 --- a/moto/dynamodb/models/__init__.py +++ b/moto/dynamodb/models/__init__.py @@ -33,6 +33,7 @@ from moto.dynamodb.exceptions import ( ResourceInUseException, StreamAlreadyEnabledException, MockValidationException, + InvalidConversion, ) from moto.dynamodb.models.utilities import bytesize from moto.dynamodb.models.dynamo_type import DynamoType @@ -646,6 +647,13 @@ class Table(CloudFormationModel): if DynamoType(range_value).size() > RANGE_KEY_MAX_LENGTH: raise RangeKeyTooLong + def _validate_item_types(self, item_attrs): + for key, value in item_attrs.items(): + if type(value) == dict: + self._validate_item_types(value) + elif type(value) == int and key == "N": + raise InvalidConversion + def put_item( self, item_attrs, @@ -688,6 +696,8 @@ class Table(CloudFormationModel): self._validate_key_sizes(item_attrs) + self._validate_item_types(item_attrs) + if expected is None: expected = {} lookup_range_value = range_value diff --git a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py index fed98e4d9..1bb7b47f8 100644 --- a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py +++ b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py @@ -1,9 +1,11 @@ import boto3 +import botocore import pytest import sure # noqa # pylint: disable=unused-import from boto3.dynamodb.conditions import Key from botocore.exceptions import ClientError -from moto import mock_dynamodb +from unittest import SkipTest +from moto import mock_dynamodb, settings table_schema = { "KeySchema": [{"AttributeName": "partitionKey", "KeyType": "HASH"}], @@ -547,3 +549,33 @@ def test_update_item_non_existent_table(): err = exc.value.response["Error"] assert err["Code"].should.equal("ResourceNotFoundException") assert err["Message"].should.equal("Requested resource not found") + + +@mock_dynamodb +def test_put_item_wrong_datatype(): + if settings.TEST_SERVER_MODE: + raise SkipTest("Unable to mock a session with Config in ServerMode") + session = botocore.session.Session() + config = botocore.client.Config(parameter_validation=False) + client = session.create_client("dynamodb", region_name="us-east-1", config=config) + client.create_table( + TableName="test2", + KeySchema=[{"AttributeName": "mykey", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "mykey", "AttributeType": "N"}], + BillingMode="PAY_PER_REQUEST", + ) + with pytest.raises(ClientError) as exc: + client.put_item(TableName="test2", Item={"mykey": {"N": 123}}) + err = exc.value.response["Error"] + err["Code"].should.equal("SerializationException") + err["Message"].should.equal("NUMBER_VALUE cannot be converted to String") + + # Same thing - but with a non-key, and nested + with pytest.raises(ClientError) as exc: + client.put_item( + TableName="test2", + Item={"mykey": {"N": "123"}, "nested": {"M": {"sth": {"N": 5}}}}, + ) + err = exc.value.response["Error"] + err["Code"].should.equal("SerializationException") + err["Message"].should.equal("NUMBER_VALUE cannot be converted to String")