ai-robot-core/ai-service/app/core/exceptions.py

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),
)