diff --git a/fastapi/applications.py b/fastapi/applications.py index 41d86143e..892f9112b 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -11,10 +11,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 ( @@ -979,6 +984,9 @@ class FastAPI(Starlette): Any, Callable[[Request, Any], 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 d7065c52f..386ea1c46 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -209,6 +209,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 ea82ab14a..f38135c46 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -49,6 +49,7 @@ from fastapi.encoders import jsonable_encoder from fastapi.exceptions import ( EndpointContext, FastAPIError, + RequestMalformedError, RequestValidationError, ResponseValidationError, WebSocketRequestValidationError, @@ -382,7 +383,7 @@ def get_request_handler( else: body = body_bytes except json.JSONDecodeError as e: - validation_error = RequestValidationError( + raise RequestMalformedError( [ { "type": "json_invalid", @@ -394,8 +395,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 bdabf8d68..475994c1e 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -142,7 +142,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() == { "detail": [ {