103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
"""
|
|
Exception handling for AI Service.
|
|
[AC-AISVC-03, AC-AISVC-04, AC-AISVC-05] Structured error responses.
|
|
"""
|
|
|
|
from fastapi import HTTPException, Request, status
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.models import ErrorCode, ErrorResponse
|
|
|
|
|
|
class AIServiceException(Exception):
|
|
def __init__(
|
|
self,
|
|
code: ErrorCode,
|
|
message: str,
|
|
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
details: list[dict] | None = None,
|
|
):
|
|
self.code = code
|
|
self.message = message
|
|
self.status_code = status_code
|
|
self.details = details
|
|
super().__init__(message)
|
|
|
|
|
|
class MissingTenantIdException(AIServiceException):
|
|
def __init__(self, message: str = "Missing required header: X-Tenant-Id"):
|
|
super().__init__(
|
|
code=ErrorCode.MISSING_TENANT_ID,
|
|
message=message,
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
|
|
class InvalidRequestException(AIServiceException):
|
|
def __init__(self, message: str, details: list[dict] | None = None):
|
|
super().__init__(
|
|
code=ErrorCode.INVALID_REQUEST,
|
|
message=message,
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
details=details,
|
|
)
|
|
|
|
|
|
class ServiceUnavailableException(AIServiceException):
|
|
def __init__(self, message: str = "Service temporarily unavailable"):
|
|
super().__init__(
|
|
code=ErrorCode.SERVICE_UNAVAILABLE,
|
|
message=message,
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
)
|
|
|
|
|
|
class TimeoutException(AIServiceException):
|
|
def __init__(self, message: str = "Request timeout"):
|
|
super().__init__(
|
|
code=ErrorCode.TIMEOUT,
|
|
message=message,
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
)
|
|
|
|
|
|
async def ai_service_exception_handler(request: Request, exc: AIServiceException) -> JSONResponse:
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content=ErrorResponse(
|
|
code=exc.code.value,
|
|
message=exc.message,
|
|
details=exc.details,
|
|
).model_dump(exclude_none=True),
|
|
)
|
|
|
|
|
|
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
|
if exc.status_code == status.HTTP_400_BAD_REQUEST:
|
|
code = ErrorCode.INVALID_REQUEST
|
|
elif exc.status_code == status.HTTP_503_SERVICE_UNAVAILABLE:
|
|
code = ErrorCode.SERVICE_UNAVAILABLE
|
|
else:
|
|
code = ErrorCode.INTERNAL_ERROR
|
|
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content=ErrorResponse(
|
|
code=code.value,
|
|
message=exc.detail or "An error occurred",
|
|
).model_dump(exclude_none=True),
|
|
)
|
|
|
|
|
|
async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.error(f"Unhandled exception: {type(exc).__name__}: {exc}", exc_info=True)
|
|
return JSONResponse(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
content=ErrorResponse(
|
|
code=ErrorCode.INTERNAL_ERROR.value,
|
|
message="An unexpected error occurred",
|
|
).model_dump(exclude_none=True),
|
|
)
|