DynamoDB: Add validation for GSI attrs set to None (#5843)

This commit is contained in:
Bert Blommers 2023-01-14 14:51:47 -01:00 committed by GitHub
parent e47a2fd120
commit 6e7d3ec938
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 5 deletions

View File

@ -1422,7 +1422,7 @@ class DynamoDBBackend(BaseBackend):
else:
return table.schema
def get_table(self, table_name):
def get_table(self, table_name) -> Table:
if table_name not in self.tables:
raise ResourceNotFoundException()
return self.tables.get(table_name)

View File

@ -13,7 +13,7 @@ from .exceptions import (
ResourceNotFoundException,
ConditionalCheckFailed,
)
from moto.dynamodb.models import dynamodb_backends, dynamo_json_dump
from moto.dynamodb.models import dynamodb_backends, dynamo_json_dump, Table
from moto.utilities.aws_headers import amz_crc32, amzn_request_id
@ -67,7 +67,7 @@ def include_consumed_capacity(val=1.0):
return _inner
def get_empty_keys_on_put(field_updates, table):
def get_empty_keys_on_put(field_updates, table: Table):
"""
Return the first key-name that has an empty value. None if all keys are filled
"""
@ -105,6 +105,16 @@ def put_has_empty_attrs(field_updates, table):
return False
def validate_put_has_gsi_keys_set_to_none(item, table: Table) -> None:
for gsi in table.global_indexes:
for attr in gsi.schema:
attr_name = attr["AttributeName"]
if attr_name in item and item[attr_name] == {"NULL": True}:
raise MockValidationException(
f"One or more parameter values were invalid: Type mismatch for Index Key {attr_name} Expected: S Actual: NULL IndexName: {gsi.name}"
)
def check_projection_expression(expression):
if expression.upper() in ReservedKeywords.get_reserved_keywords():
raise MockValidationException(
@ -375,15 +385,17 @@ class DynamoHandler(BaseResponse):
if return_values not in ("ALL_OLD", "NONE"):
raise MockValidationException("Return values set to invalid value")
empty_key = get_empty_keys_on_put(item, self.dynamodb_backend.get_table(name))
table = self.dynamodb_backend.get_table(name)
empty_key = get_empty_keys_on_put(item, table)
if empty_key:
raise MockValidationException(
f"One or more parameter values were invalid: An AttributeValue may not contain an empty string. Key: {empty_key}"
)
if put_has_empty_attrs(item, self.dynamodb_backend.get_table(name)):
if put_has_empty_attrs(item, table):
raise MockValidationException(
"One or more parameter values were invalid: An number set may not be empty"
)
validate_put_has_gsi_keys_set_to_none(item, table)
overwrite = "Expected" not in self.body
if not overwrite:

View File

@ -899,3 +899,43 @@ def test_put_item__string_as_integer_value():
err = exc.value.response["Error"]
err["Code"].should.equal("SerializationException")
err["Message"].should.equal("Start of structure or map found where not expected")
@mock_dynamodb
def test_gsi_key_cannot_be_empty():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
hello_index = {
"IndexName": "hello-index",
"KeySchema": [{"AttributeName": "hello", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
}
table_name = "lilja-test"
# Let's create a table with [id: str, hello: str], with an index to hello
dynamodb.create_table(
TableName=table_name,
KeySchema=[
{"AttributeName": "id", "KeyType": "HASH"},
],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "hello", "AttributeType": "S"},
],
GlobalSecondaryIndexes=[hello_index],
BillingMode="PAY_PER_REQUEST",
)
table = dynamodb.Table(table_name)
with pytest.raises(ClientError) as exc:
table.put_item(
TableName=table_name,
Item={
"id": "woop",
"hello": None,
},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Type mismatch for Index Key hello Expected: S Actual: NULL IndexName: hello-index"
)