diff --git a/fastapi/applications.py b/fastapi/applications.py index 4af1146b0d..e452b6a596 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -7,10 +7,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 ( @@ -999,6 +1004,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 475dd7bdd9..5cc0976af2 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 d7065c52fe..386ea1c469 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 36acb6b89d..320ebe846a 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -54,6 +54,7 @@ from fastapi.encoders import jsonable_encoder from fastapi.exceptions import ( EndpointContext, FastAPIError, + RequestMalformedError, RequestValidationError, ResponseValidationError, WebSocketRequestValidationError, @@ -425,7 +426,7 @@ def get_request_handler( else: body = body_bytes except json.JSONDecodeError as e: - validation_error = RequestValidationError( + raise RequestMalformedError( [ { "type": "json_invalid", @@ -437,8 +438,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 8c883708a3..a38a77eb35 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": [ {