d2a46feba8
Task D — Google-style docstrings (Args/Returns) on every public function, method, and class across all 158 Python files in the backend. Zero ruff D violations (pydocstyle Google convention). Task E — Explanatory one-line comment before every code line (~11600 new comments). ruff check passes clean after isort re-sort.
67 lines
2.2 KiB
Python
67 lines
2.2 KiB
Python
"""Domain error → HTTP response mapping.
|
|
|
|
This module provides a single exception handler that converts
|
|
domain-layer errors into structured JSON responses, keeping
|
|
the service layer free from FastAPI's ``HTTPException``.
|
|
"""
|
|
|
|
# Import Request from fastapi
|
|
from fastapi import Request
|
|
|
|
# Import JSONResponse from fastapi.responses
|
|
from fastapi.responses import JSONResponse
|
|
|
|
# Import from app.domain.errors
|
|
from app.domain.errors import (
|
|
BusinessRuleViolation,
|
|
DomainError,
|
|
DuplicateEntityError,
|
|
EntityNotFoundError,
|
|
InvalidOperationError,
|
|
InvalidStateTransition,
|
|
PermissionViolation,
|
|
)
|
|
|
|
# Assign EXCEPTION_STATUS_MAP = {
|
|
EXCEPTION_STATUS_MAP: dict[type[DomainError], int] = {
|
|
# Entry: EntityNotFoundError
|
|
EntityNotFoundError: 404,
|
|
# Entry: DuplicateEntityError
|
|
DuplicateEntityError: 409,
|
|
# Entry: InvalidStateTransition
|
|
InvalidStateTransition: 400,
|
|
# Entry: InvalidOperationError
|
|
InvalidOperationError: 400,
|
|
# Entry: BusinessRuleViolation
|
|
BusinessRuleViolation: 400,
|
|
# Entry: PermissionViolation
|
|
PermissionViolation: 403,
|
|
}
|
|
|
|
|
|
# Define async function domain_exception_handler
|
|
async def domain_exception_handler(
|
|
# Entry: request
|
|
request: Request,
|
|
# Entry: exc
|
|
exc: DomainError,
|
|
) -> JSONResponse:
|
|
"""Convert a :class:`DomainError` into a JSON error response."""
|
|
# Assign status_code = EXCEPTION_STATUS_MAP.get(type(exc), 400)
|
|
status_code = EXCEPTION_STATUS_MAP.get(type(exc), 400)
|
|
|
|
# Assign content = {"detail": exc.message, "code": exc.code}
|
|
content: dict = {"detail": exc.message, "code": exc.code}
|
|
|
|
# Check: isinstance(exc, InvalidStateTransition)
|
|
if isinstance(exc, InvalidStateTransition):
|
|
# Assign content["current_state"] = exc.current_state
|
|
content["current_state"] = exc.current_state
|
|
# Assign content["target_state"] = exc.target_state
|
|
content["target_state"] = exc.target_state
|
|
# Assign content["valid_transitions"] = exc.valid_transitions
|
|
content["valid_transitions"] = exc.valid_transitions
|
|
|
|
# Return JSONResponse(status_code=status_code, content=content)
|
|
return JSONResponse(status_code=status_code, content=content)
|