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