Repository: getmoto/moto

Issue:
dynamodb2 – create_table() throws incorrect exception for redundant AttributeDefinitions
If you call `create_table()` of dynamodb2 handler with an entry in an `AttributeDefinitions` parameter that refers to the key not mentioned in any of `KeySchema` entries, this misleading exception is thrown:
```
One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: ['foo'], AttributeDefinitions: ['bar', 'foo']
```
while real message says:
```
One or more parameter values were invalid: Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions
```

Example:
```python
@mock_dynamodb2
def test_index_with_additional_attributes():
    dynamodb = boto3.client("dynamodb", region_name="us-east-1")

    with pytest.raises(ClientError) as ex:
        dynamodb.create_table(
            TableName="test-table",
            AttributeDefinitions=[
                {"AttributeName": "id", "AttributeType": "S"},
                {"AttributeName": "created_at", "AttributeType": "N"}
            ],
            KeySchema=[
                {"AttributeName": "id", "KeyType": "HASH"}
            ],
            BillingMode="PAY_PER_REQUEST"
        )

    ex.value.response["Error"]["Code"].should.equal("ValidationException")
    ex.value.response["Error"]["Message"].should.equal("One or more parameter values were invalid: Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions")  # <-- FAILS
```


Discussion:

Resolving patch:
diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py
--- a/moto/dynamodb2/responses.py
+++ b/moto/dynamodb2/responses.py
@@ -203,15 +203,8 @@ def create_table(self):
         actual_attrs = [item["AttributeName"] for item in attr]
         actual_attrs.sort()
         if actual_attrs != expected_attrs:
-            er = "com.amazonaws.dynamodb.v20111205#ValidationException"
-            return self.error(
-                er,
-                "One or more parameter values were invalid: "
-                "Some index key attributes are not defined in AttributeDefinitions. "
-                "Keys: "
-                + str(expected_attrs)
-                + ", AttributeDefinitions: "
-                + str(actual_attrs),
+            return self._throw_attr_error(
+                actual_attrs, expected_attrs, global_indexes or local_secondary_indexes
             )
         # get the stream specification
         streams = body.get("StreamSpecification")
@@ -231,6 +224,62 @@ def create_table(self):
             er = "com.amazonaws.dynamodb.v20111205#ResourceInUseException"
             return self.error(er, "Resource in use")
 
+    def _throw_attr_error(self, actual_attrs, expected_attrs, indexes):
+        def dump_list(list_):
+            return str(list_).replace("'", "")
+
+        er = "com.amazonaws.dynamodb.v20111205#ValidationException"
+        err_head = "One or more parameter values were invalid: "
+        if len(actual_attrs) > len(expected_attrs):
+            if indexes:
+                return self.error(
+                    er,
+                    err_head
+                    + "Some AttributeDefinitions are not used. AttributeDefinitions: "
+                    + dump_list(actual_attrs)
+                    + ", keys used: "
+                    + dump_list(expected_attrs),
+                )
+            else:
+                return self.error(
+                    er,
+                    err_head
+                    + "Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions",
+                )
+        elif len(actual_attrs) < len(expected_attrs):
+            if indexes:
+                return self.error(
+                    er,
+                    err_head
+                    + "Some index key attributes are not defined in AttributeDefinitions. Keys: "
+                    + dump_list(list(set(expected_attrs) - set(actual_attrs)))
+                    + ", AttributeDefinitions: "
+                    + dump_list(actual_attrs),
+                )
+            else:
+                return self.error(
+                    er, "Invalid KeySchema: Some index key attribute have no definition"
+                )
+        else:
+            if indexes:
+                return self.error(
+                    er,
+                    err_head
+                    + "Some index key attributes are not defined in AttributeDefinitions. Keys: "
+                    + dump_list(list(set(expected_attrs) - set(actual_attrs)))
+                    + ", AttributeDefinitions: "
+                    + dump_list(actual_attrs),
+                )
+            else:
+                return self.error(
+                    er,
+                    err_head
+                    + "Some index key attributes are not defined in AttributeDefinitions. Keys: "
+                    + dump_list(expected_attrs)
+                    + ", AttributeDefinitions: "
+                    + dump_list(actual_attrs),
+                )
+
     def delete_table(self):
         name = self.body["TableName"]
         table = self.dynamodb_backend.delete_table(name)


Test patch:
diff --git a/tests/test_dynamodb2/test_dynamodb_exceptions.py b/tests/test_dynamodb2/test_dynamodb_exceptions.py
--- a/tests/test_dynamodb2/test_dynamodb_exceptions.py
+++ b/tests/test_dynamodb2/test_dynamodb_exceptions.py
@@ -195,3 +195,136 @@ def test_batch_write_item_non_existing_table():
     err = exc.value.response["Error"]
     assert err["Code"].should.equal("ResourceNotFoundException")
     assert err["Message"].should.equal("Requested resource not found")
+
+
+@mock_dynamodb2
+def test_create_table_with_redundant_attributes():
+    dynamodb = boto3.client("dynamodb", region_name="us-east-1")
+
+    with pytest.raises(ClientError) as exc:
+        dynamodb.create_table(
+            TableName="test-table",
+            AttributeDefinitions=[
+                {"AttributeName": "id", "AttributeType": "S"},
+                {"AttributeName": "created_at", "AttributeType": "N"},
+            ],
+            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+            BillingMode="PAY_PER_REQUEST",
+        )
+
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationException")
+    err["Message"].should.equal(
+        "One or more parameter values were invalid: Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions"
+    )
+
+    with pytest.raises(ClientError) as exc:
+        dynamodb.create_table(
+            TableName="test-table",
+            AttributeDefinitions=[
+                {"AttributeName": "id", "AttributeType": "S"},
+                {"AttributeName": "user", "AttributeType": "S"},
+                {"AttributeName": "created_at", "AttributeType": "N"},
+            ],
+            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+            GlobalSecondaryIndexes=[
+                {
+                    "IndexName": "gsi_user-items",
+                    "KeySchema": [{"AttributeName": "user", "KeyType": "HASH"}],
+                    "Projection": {"ProjectionType": "ALL"},
+                }
+            ],
+            BillingMode="PAY_PER_REQUEST",
+        )
+
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationException")
+    err["Message"].should.equal(
+        "One or more parameter values were invalid: Some AttributeDefinitions are not used. AttributeDefinitions: [created_at, id, user], keys used: [id, user]"
+    )
+
+
+@mock_dynamodb2
+def test_create_table_with_missing_attributes():
+    dynamodb = boto3.client("dynamodb", region_name="us-east-1")
+
+    with pytest.raises(ClientError) as exc:
+        dynamodb.create_table(
+            TableName="test-table",
+            AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"},],
+            KeySchema=[
+                {"AttributeName": "id", "KeyType": "HASH"},
+                {"AttributeName": "created_at", "KeyType": "RANGE"},
+            ],
+            BillingMode="PAY_PER_REQUEST",
+        )
+
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationException")
+    err["Message"].should.equal(
+        "Invalid KeySchema: Some index key attribute have no definition"
+    )
+
+    with pytest.raises(ClientError) as exc:
+        dynamodb.create_table(
+            TableName="test-table",
+            AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"},],
+            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+            GlobalSecondaryIndexes=[
+                {
+                    "IndexName": "gsi_user-items",
+                    "KeySchema": [{"AttributeName": "user", "KeyType": "HASH"}],
+                    "Projection": {"ProjectionType": "ALL"},
+                }
+            ],
+            BillingMode="PAY_PER_REQUEST",
+        )
+
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationException")
+    err["Message"].should.equal(
+        "One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [id]"
+    )
+
+
+@mock_dynamodb2
+def test_create_table_with_redundant_and_missing_attributes():
+    dynamodb = boto3.client("dynamodb", region_name="us-east-1")
+
+    with pytest.raises(ClientError) as exc:
+        dynamodb.create_table(
+            TableName="test-table",
+            AttributeDefinitions=[
+                {"AttributeName": "created_at", "AttributeType": "N"}
+            ],
+            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+            BillingMode="PAY_PER_REQUEST",
+        )
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationException")
+    err["Message"].should.equal(
+        "One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [id], AttributeDefinitions: [created_at]"
+    )
+
+    with pytest.raises(ClientError) as exc:
+        dynamodb.create_table(
+            TableName="test-table",
+            AttributeDefinitions=[
+                {"AttributeName": "id", "AttributeType": "S"},
+                {"AttributeName": "created_at", "AttributeType": "N"},
+            ],
+            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+            GlobalSecondaryIndexes=[
+                {
+                    "IndexName": "gsi_user-items",
+                    "KeySchema": [{"AttributeName": "user", "KeyType": "HASH"}],
+                    "Projection": {"ProjectionType": "ALL"},
+                }
+            ],
+            BillingMode="PAY_PER_REQUEST",
+        )
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationException")
+    err["Message"].should.equal(
+        "One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [created_at, id]"
+    )