"""
Exception handler registration for :mod:`hubvault.server`.
This module translates public ``hubvault`` exceptions into stable HTTP error
payloads for the embedded FastAPI app.
The module contains:
* :func:`register_exception_handlers` - Attach ``hubvault`` error handlers
"""
from ..errors import (
ConflictError,
EntryNotFoundError,
HubVaultError,
HubVaultValidationError,
RepositoryAlreadyExistsError,
RepositoryNotFoundError,
RevisionNotFoundError,
)
from .schemas import build_error_payload
def _status_for_error(err: HubVaultError) -> int:
"""
Map one public ``hubvault`` exception to an HTTP status code.
:param err: Repository-layer exception
:type err: hubvault.errors.HubVaultError
:return: HTTP status code aligned with the public error category
:rtype: int
"""
if isinstance(err, (RepositoryNotFoundError, RevisionNotFoundError, EntryNotFoundError)):
return 404
if isinstance(err, (ConflictError, RepositoryAlreadyExistsError)):
return 409
if isinstance(err, HubVaultValidationError):
return 400
return 500
[docs]
def register_exception_handlers(app) -> None:
"""
Attach ``hubvault``-specific exception handlers to one FastAPI app.
:param app: FastAPI application receiving the handlers
:type app: fastapi.FastAPI
:return: ``None``.
:rtype: None
:raises hubvault.optional.MissingOptionalDependencyError: Raised when the
API extra is not installed.
"""
from ..optional import import_optional_dependency
fastapi = import_optional_dependency(
"fastapi",
extra="api",
feature="server exception handlers",
missing_names={"starlette", "pydantic"},
)
fastapi_responses = import_optional_dependency(
"fastapi.responses",
extra="api",
feature="server exception handlers",
missing_names={"fastapi", "starlette", "pydantic"},
)
HTTPException = fastapi.HTTPException
JSONResponse = fastapi_responses.JSONResponse
@app.exception_handler(HubVaultError)
async def _hubvault_error_handler(_, err: HubVaultError):
"""
Translate one public ``hubvault`` exception into JSON.
:param _: Request object ignored by the shared handler
:type _: object
:param err: Repository-layer exception
:type err: hubvault.errors.HubVaultError
:return: JSON error response
:rtype: fastapi.responses.JSONResponse
"""
return JSONResponse(
status_code=_status_for_error(err),
content=build_error_payload(type(err).__name__, str(err)),
)
@app.exception_handler(HTTPException)
async def _http_exception_handler(_, err: HTTPException):
"""
Translate framework-level HTTP exceptions into the stable error shape.
:param _: Request object ignored by the shared handler
:type _: object
:param err: Framework-level HTTP exception
:type err: fastapi.HTTPException
:return: JSON error response
:rtype: fastapi.responses.JSONResponse
"""
detail = err.detail
if not isinstance(detail, str):
detail = str(detail)
return JSONResponse(
status_code=int(err.status_code),
content=build_error_payload("HTTPException", detail),
headers=err.headers,
)