Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- Create domain/errors.py as canonical error hierarchy: DomainError, InvalidStateTransition, PermissionViolation, BusinessRuleViolation, EntityNotFoundError, DuplicateEntityError - InvalidOperationError now inherits from BusinessRuleViolation for semantic consistency - Convert domain/exceptions.py to backward-compatible re-export shim with legacy aliases (DomainException, InvalidTransitionError, AuthorizationError) - Update error_handler.py to import from domain/errors.py and map all new error types - Update main.py to register DomainError (new base) as the exception handler root
46 lines
1.3 KiB
Python
46 lines
1.3 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``.
|
|
"""
|
|
|
|
from fastapi import Request
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.domain.errors import (
|
|
BusinessRuleViolation,
|
|
DomainError,
|
|
DuplicateEntityError,
|
|
EntityNotFoundError,
|
|
InvalidOperationError,
|
|
InvalidStateTransition,
|
|
PermissionViolation,
|
|
)
|
|
|
|
EXCEPTION_STATUS_MAP: dict[type[DomainError], int] = {
|
|
EntityNotFoundError: 404,
|
|
DuplicateEntityError: 409,
|
|
InvalidStateTransition: 400,
|
|
InvalidOperationError: 400,
|
|
BusinessRuleViolation: 400,
|
|
PermissionViolation: 403,
|
|
}
|
|
|
|
|
|
async def domain_exception_handler(
|
|
request: Request,
|
|
exc: DomainError,
|
|
) -> JSONResponse:
|
|
"""Convert a :class:`DomainError` into a JSON error response."""
|
|
status_code = EXCEPTION_STATUS_MAP.get(type(exc), 400)
|
|
|
|
content: dict = {"detail": exc.message, "code": exc.code}
|
|
|
|
if isinstance(exc, InvalidStateTransition):
|
|
content["current_state"] = exc.current_state
|
|
content["target_state"] = exc.target_state
|
|
content["valid_transitions"] = exc.valid_transitions
|
|
|
|
return JSONResponse(status_code=status_code, content=content)
|