diff --git a/fastapi/applications.py b/fastapi/applications.py index 02193312b..5bc2c2798 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -18,10 +18,15 @@ from fastapi import routing from fastapi.datastructures import Default, DefaultPlaceholder from fastapi.exception_handlers import ( http_exception_handler, + request_malformed_exception_handler, request_validation_exception_handler, websocket_request_validation_exception_handler, ) -from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError +from fastapi.exceptions import ( + RequestMalformedError, + RequestValidationError, + WebSocketRequestValidationError, +) from fastapi.logger import logger from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware from fastapi.openapi.docs import ( @@ -984,6 +989,9 @@ class FastAPI(Starlette): Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]] ] = {} if exception_handlers is None else dict(exception_handlers) self.exception_handlers.setdefault(HTTPException, http_exception_handler) + self.exception_handlers.setdefault( + RequestMalformedError, request_malformed_exception_handler + ) self.exception_handlers.setdefault( RequestValidationError, request_validation_exception_handler ) diff --git a/fastapi/exception_handlers.py b/fastapi/exception_handlers.py index 475dd7bdd..5cc0976af 100644 --- a/fastapi/exception_handlers.py +++ b/fastapi/exception_handlers.py @@ -1,5 +1,9 @@ from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError +from fastapi.exceptions import ( + RequestMalformedError, + RequestValidationError, + WebSocketRequestValidationError, +) from fastapi.utils import is_body_allowed_for_status_code from fastapi.websockets import WebSocket from starlette.exceptions import HTTPException @@ -17,6 +21,15 @@ async def http_exception_handler(request: Request, exc: HTTPException) -> Respon ) +async def request_malformed_exception_handler( + request: Request, exc: RequestMalformedError +) -> JSONResponse: + return JSONResponse( + status_code=400, + content={"detail": jsonable_encoder(exc.errors())}, + ) + + async def request_validation_exception_handler( request: Request, exc: RequestValidationError ) -> JSONResponse: diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index a46e82350..33f3cf9c0 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -199,6 +199,18 @@ class ValidationException(Exception): return message.rstrip() +class RequestMalformedError(ValidationException): + def __init__( + self, + errors: Sequence[Any], + *, + body: Any = None, + endpoint_ctx: Optional[EndpointContext] = None, + ) -> None: + super().__init__(errors, endpoint_ctx=endpoint_ctx) + self.body = body + + class RequestValidationError(ValidationException): def __init__( self, diff --git a/fastapi/routing.py b/fastapi/routing.py index 9be2b44bc..a0c45f32f 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -48,6 +48,7 @@ from fastapi.encoders import jsonable_encoder from fastapi.exceptions import ( EndpointContext, FastAPIError, + RequestMalformedError, RequestValidationError, ResponseValidationError, WebSocketRequestValidationError, @@ -388,7 +389,7 @@ def get_request_handler( else: body = body_bytes except json.JSONDecodeError as e: - validation_error = RequestValidationError( + raise RequestMalformedError( [ { "type": "json_invalid", @@ -400,8 +401,7 @@ def get_request_handler( ], body=e.doc, endpoint_ctx=endpoint_ctx, - ) - raise validation_error from e + ) from e except HTTPException: # If a middleware raises an HTTPException, it should be raised again raise diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py index f8b5aee8d..a595b0ec1 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -200,7 +200,7 @@ def test_post_broken_body(client: TestClient): headers={"content-type": "application/json"}, content="{some broken json}", ) - assert response.status_code == 422, response.text + assert response.status_code == 400, response.text assert response.json() == IsDict( { "detail": [