This commit is contained in:
Javier Sánchez Castro 2025-12-16 21:09:33 +00:00 committed by GitHub
commit 848902ee4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 39 additions and 6 deletions

View File

@ -18,10 +18,15 @@ from fastapi import routing
from fastapi.datastructures import Default, DefaultPlaceholder from fastapi.datastructures import Default, DefaultPlaceholder
from fastapi.exception_handlers import ( from fastapi.exception_handlers import (
http_exception_handler, http_exception_handler,
request_malformed_exception_handler,
request_validation_exception_handler, request_validation_exception_handler,
websocket_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.logger import logger
from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
from fastapi.openapi.docs import ( from fastapi.openapi.docs import (
@ -984,6 +989,9 @@ class FastAPI(Starlette):
Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]] Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]]
] = {} if exception_handlers is None else dict(exception_handlers) ] = {} if exception_handlers is None else dict(exception_handlers)
self.exception_handlers.setdefault(HTTPException, http_exception_handler) self.exception_handlers.setdefault(HTTPException, http_exception_handler)
self.exception_handlers.setdefault(
RequestMalformedError, request_malformed_exception_handler
)
self.exception_handlers.setdefault( self.exception_handlers.setdefault(
RequestValidationError, request_validation_exception_handler RequestValidationError, request_validation_exception_handler
) )

View File

@ -1,5 +1,9 @@
from fastapi.encoders import jsonable_encoder 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.utils import is_body_allowed_for_status_code
from fastapi.websockets import WebSocket from fastapi.websockets import WebSocket
from starlette.exceptions import HTTPException 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( async def request_validation_exception_handler(
request: Request, exc: RequestValidationError request: Request, exc: RequestValidationError
) -> JSONResponse: ) -> JSONResponse:

View File

@ -199,6 +199,18 @@ class ValidationException(Exception):
return message.rstrip() 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): class RequestValidationError(ValidationException):
def __init__( def __init__(
self, self,

View File

@ -48,6 +48,7 @@ from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import ( from fastapi.exceptions import (
EndpointContext, EndpointContext,
FastAPIError, FastAPIError,
RequestMalformedError,
RequestValidationError, RequestValidationError,
ResponseValidationError, ResponseValidationError,
WebSocketRequestValidationError, WebSocketRequestValidationError,
@ -388,7 +389,7 @@ def get_request_handler(
else: else:
body = body_bytes body = body_bytes
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
validation_error = RequestValidationError( raise RequestMalformedError(
[ [
{ {
"type": "json_invalid", "type": "json_invalid",
@ -400,8 +401,7 @@ def get_request_handler(
], ],
body=e.doc, body=e.doc,
endpoint_ctx=endpoint_ctx, endpoint_ctx=endpoint_ctx,
) ) from e
raise validation_error from e
except HTTPException: except HTTPException:
# If a middleware raises an HTTPException, it should be raised again # If a middleware raises an HTTPException, it should be raised again
raise raise

View File

@ -200,7 +200,7 @@ def test_post_broken_body(client: TestClient):
headers={"content-type": "application/json"}, headers={"content-type": "application/json"},
content="{some broken json}", content="{some broken json}",
) )
assert response.status_code == 422, response.text assert response.status_code == 400, response.text
assert response.json() == IsDict( assert response.json() == IsDict(
{ {
"detail": [ "detail": [