diff --git a/fastapi/applications.py b/fastapi/applications.py index 05c7bd2be..d0885162f 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -27,6 +27,7 @@ from fastapi.openapi.docs import ( get_swagger_ui_html, get_swagger_ui_oauth2_redirect_html, ) +from fastapi.openapi.models import DefaultErrorSchema from fastapi.openapi.utils import get_openapi from fastapi.params import Depends from fastapi.types import DecoratedCallable, IncEx @@ -695,6 +696,7 @@ class FastAPI(Starlette): """ ), ] = None, + default_error_schema: Optional[DefaultErrorSchema] = None, callbacks: Annotated[ Optional[List[BaseRoute]], Doc( @@ -871,6 +873,7 @@ class FastAPI(Starlette): ), ] = "3.1.0" self.openapi_schema: Optional[Dict[str, Any]] = None + self.default_error_schema = default_error_schema if self.openapi_url: assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'" assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'" @@ -987,6 +990,7 @@ class FastAPI(Starlette): terms_of_service=self.terms_of_service, contact=self.contact, license_info=self.license_info, + default_error_schema=self.default_error_schema, routes=self.routes, webhooks=self.webhooks.routes, tags=self.openapi_tags, diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index ed07b40f5..40b26086c 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -228,6 +228,12 @@ class Example(TypedDict, total=False): extra = "allow" +class DefaultErrorSchema(BaseModel): + status: int + description: Optional[str] = None + model: BaseModel + + class ParameterInType(Enum): query = "query" header = "header" diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 808646cc2..2e555e336 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -23,7 +23,7 @@ from fastapi.dependencies.utils import ( ) from fastapi.encoders import jsonable_encoder from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX, REF_TEMPLATE -from fastapi.openapi.models import OpenAPI +from fastapi.openapi.models import DefaultErrorSchema, OpenAPI from fastapi.params import Body, ParamTypes from fastapi.responses import Response from fastapi.types import ModelNameMap @@ -261,6 +261,7 @@ def get_openapi_path( Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], separate_input_output_schemas: bool = True, + default_error_schema: Optional[DefaultErrorSchema] = None, ) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]: path = {} security_schemes: Dict[str, Any] = {} @@ -418,7 +419,28 @@ def get_openapi_path( openapi_response["description"] = description http422 = str(HTTP_422_UNPROCESSABLE_ENTITY) all_route_params = get_flat_params(route.dependant) - if (all_route_params or route.body_field) and not any( + if default_error_schema is not None: + operation["responses"][default_error_schema["status"]] = { + "description": default_error_schema["description"], + "content": { + "application/json": { + "schema": { + "$ref": REF_PREFIX + default_error_schema["name"] + } + }, + }, + } + definitions.update( + { + default_error_schema["name"]: { + "title": default_error_schema["name"], + "type": "object", + "properties": default_error_schema["properties"], + "required": default_error_schema["required"], + } + } + ) + elif (all_route_params or route.body_field) and not any( status in operation["responses"] for status in [http422, "4XX", "default"] ): @@ -482,6 +504,7 @@ def get_openapi( summary: Optional[str] = None, description: Optional[str] = None, routes: Sequence[BaseRoute], + default_error_schema: Optional[DefaultErrorSchema] = None, webhooks: Optional[Sequence[BaseRoute]] = None, tags: Optional[List[Dict[str, Any]]] = None, servers: Optional[List[Dict[str, Union[str, Any]]]] = None, @@ -526,6 +549,7 @@ def get_openapi( model_name_map=model_name_map, field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, + default_error_schema=default_error_schema, ) if result: path, security_schemes, path_definitions = result