Repository: tiangolo/fastapi

Issue:
Sending incorrect data cause 500 error
I've got this simple example:

```
from typing import List

from fastapi import FastAPI, Body


class User(BaseModel):
    name: str


@app.post('/test/')
async def test(users: List[User], test: str = Body(...)):
    return {'users': users, 'test': test}
```

When sending incorrect payload the server returns 500 error:

```
# curl -s -D - -o /dev/null -X POST "localhost:8080/test/" -H  "accept: application/json" -H  "Content-Type: application/json" -d "[]"
HTTP/1.1 500 Internal Server Error
date: Fri, 24 Jan 2020 10:59:47 GMT
server: uvicorn
content-length: 1264
content-type: text/plain; charset=utf-8
```
Server exception:

```
INFO:     127.0.0.1:60652 - "POST /test/ HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/test/venv38/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 385, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/test/venv38/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/test/venv38/lib/python3.8/site-packages/fastapi/applications.py", line 140, in __call__
    await super().__call__(scope, receive, send)
  File "/test/venv38/lib/python3.8/site-packages/starlette/applications.py", line 134, in __call__
    await self.error_middleware(scope, receive, send)
  File "/test/venv38/lib/python3.8/site-packages/starlette/middleware/errors.py", line 178, in __call__
    raise exc from None
  File "/test/venv38/lib/python3.8/site-packages/starlette/middleware/errors.py", line 156, in __call__
    await self.app(scope, receive, _send)
  File "/test/venv38/lib/python3.8/site-packages/starlette/exceptions.py", line 73, in __call__
    raise exc from None
  File "/test/venv38/lib/python3.8/site-packages/starlette/exceptions.py", line 62, in __call__
    await self.app(scope, receive, sender)
  File "/test/venv38/lib/python3.8/site-packages/starlette/routing.py", line 590, in __call__
    await route(scope, receive, send)
  File "/test/venv38/lib/python3.8/site-packages/starlette/routing.py", line 208, in __call__
    await self.app(scope, receive, send)
  File "/test/venv38/lib/python3.8/site-packages/starlette/routing.py", line 41, in app
    response = await func(request)
  File "/test/venv38/lib/python3.8/site-packages/fastapi/routing.py", line 115, in app
    solved_result = await solve_dependencies(
  File "/test/venv38/lib/python3.8/site-packages/fastapi/dependencies/utils.py", line 547, in solve_dependencies
    ) = await request_body_to_args(  # body_params checked above
  File "/test/venv38/lib/python3.8/site-packages/fastapi/dependencies/utils.py", line 637, in request_body_to_args
    value = received_body.get(field.alias)
AttributeError: 'list' object has no attribute 'get'
```

So the body is not validated in this case?


Discussion:
Have reproduced same error.

Resolving patch:
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -634,7 +634,11 @@ async def request_body_to_args(
                 ) and isinstance(received_body, FormData):
                     value = received_body.getlist(field.alias)
                 else:
-                    value = received_body.get(field.alias)
+                    try:
+                        value = received_body.get(field.alias)
+                    except AttributeError:
+                        errors.append(get_missing_field_error(field.alias))
+                        continue
             if (
                 value is None
                 or (isinstance(field_info, params.Form) and value == "")
@@ -645,18 +649,7 @@ async def request_body_to_args(
                 )
             ):
                 if field.required:
-                    if PYDANTIC_1:
-                        errors.append(
-                            ErrorWrapper(MissingError(), loc=("body", field.alias))
-                        )
-                    else:  # pragma: nocover
-                        errors.append(
-                            ErrorWrapper(  # type: ignore
-                                MissingError(),
-                                loc=("body", field.alias),
-                                config=BaseConfig,
-                            )
-                        )
+                    errors.append(get_missing_field_error(field.alias))
                 else:
                     values[field.name] = deepcopy(field.default)
                 continue
@@ -685,6 +678,16 @@ async def request_body_to_args(
     return values, errors
 
 
+def get_missing_field_error(field_alias: str) -> ErrorWrapper:
+    if PYDANTIC_1:
+        missing_field_error = ErrorWrapper(MissingError(), loc=("body", field_alias))
+    else:  # pragma: no cover
+        missing_field_error = ErrorWrapper(  # type: ignore
+            MissingError(), loc=("body", field_alias), config=BaseConfig,
+        )
+    return missing_field_error
+
+
 def get_schema_compatible_field(*, field: ModelField) -> ModelField:
     out_field = field
     if lenient_issubclass(field.type_, UploadFile):


Test patch:
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
@@ -166,6 +166,30 @@ def test_openapi_schema():
                 ]
             },
         ),
+        (
+            "/items/5",
+            [],
+            422,
+            {
+                "detail": [
+                    {
+                        "loc": ["body", "item"],
+                        "msg": "field required",
+                        "type": "value_error.missing",
+                    },
+                    {
+                        "loc": ["body", "user"],
+                        "msg": "field required",
+                        "type": "value_error.missing",
+                    },
+                    {
+                        "loc": ["body", "importance"],
+                        "msg": "field required",
+                        "type": "value_error.missing",
+                    },
+                ]
+            },
+        ),
     ],
 )
 def test_post_body(path, body, expected_status, expected_response):