Files
orion/app/exceptions/base.py

207 lines
5.3 KiB
Python

# app/exceptions/base.py
"""
Base exception classes for the LetzShop API.
Provides consistent error structure for frontend consumption with:
- Error codes for programmatic handling
- User-friendly messages
- HTTP status code mapping
- Additional context for debugging
"""
from typing import Any, Dict, Optional
class LetzShopException(Exception):
"""
Base exception for all LetzShop API errors.
Provides consistent structure for frontend error handling.
"""
def __init__(
self,
message: str,
error_code: str,
status_code: int = 500,
details: Optional[Dict[str, Any]] = None,
field: Optional[str] = None,
):
self.message = message
self.error_code = error_code
self.status_code = status_code
self.details = details or {}
self.field = field
super().__init__(self.message)
def to_dict(self) -> Dict[str, Any]:
"""Convert exception to dictionary for JSON response."""
result = {
"error_code": self.error_code,
"message": self.message,
"status_code": self.status_code,
}
if self.field:
result["field"] = self.field
if self.details:
result["details"] = self.details
return result
class ValidationException(LetzShopException):
"""Raised when input validation fails."""
def __init__(
self,
message: str = "Validation failed",
field: Optional[str] = None,
details: Optional[Dict[str, Any]] = None,
):
super().__init__(
message=message,
error_code="VALIDATION_ERROR",
status_code=400,
details=details,
field=field,
)
class AuthenticationException(LetzShopException):
"""Raised when authentication fails."""
def __init__(
self,
message: str = "Authentication failed",
error_code: str = "AUTHENTICATION_FAILED",
details: Optional[Dict[str, Any]] = None,
):
super().__init__(
message=message,
error_code=error_code,
status_code=401,
details=details,
)
class AuthorizationException(LetzShopException):
"""Raised when user lacks required permissions."""
def __init__(
self,
message: str = "Access denied",
error_code: str = "ACCESS_DENIED",
details: Optional[Dict[str, Any]] = None,
):
super().__init__(
message=message,
error_code=error_code,
status_code=403,
details=details,
)
class ResourceNotFoundException(LetzShopException):
"""Raised when a requested resource is not found."""
def __init__(
self,
resource_type: str,
identifier: str,
message: Optional[str] = None,
error_code: Optional[str] = None,
):
if not message:
message = f"{resource_type} not found"
if not error_code:
error_code = f"{resource_type.upper()}_NOT_FOUND"
super().__init__(
message=message,
error_code=error_code,
status_code=404,
details={"resource_type": resource_type, "identifier": identifier},
)
class ConflictException(LetzShopException):
"""Raised when a resource conflict occurs (e.g., duplicate)."""
def __init__(
self,
message: str,
error_code: str,
details: Optional[Dict[str, Any]] = None,
):
super().__init__(
message=message,
error_code=error_code,
status_code=409,
details=details,
)
class BusinessLogicException(LetzShopException):
"""Raised when business logic validation fails."""
def __init__(
self,
message: str,
error_code: str,
details: Optional[Dict[str, Any]] = None,
):
super().__init__(
message=message,
error_code=error_code,
status_code=422,
details=details,
)
class ExternalServiceException(LetzShopException):
"""Raised when external service calls fail."""
def __init__(
self,
service: str,
message: str = "External service unavailable",
error_code: str = "EXTERNAL_SERVICE_ERROR",
details: Optional[Dict[str, Any]] = None,
):
if not details:
details = {}
details["service"] = service
super().__init__(
message=message,
error_code=error_code,
status_code=503,
details=details,
)
class RateLimitException(LetzShopException):
"""Raised when rate limit is exceeded."""
def __init__(
self,
message: str = "Rate limit exceeded",
retry_after: Optional[int] = None,
details: Optional[Dict[str, Any]] = None,
):
if not details:
details = {}
if retry_after:
details["retry_after"] = retry_after
super().__init__(
message=message,
error_code="RATE_LIMIT_EXCEEDED",
status_code=429,
details=details,
)