""" Custom exception classes for ThrillWiki. Provides domain-specific exceptions with proper error codes and messages. """ from typing import Optional, Dict, Any class ThrillWikiException(Exception): """Base exception for all ThrillWiki-specific errors.""" default_message = "An error occurred" error_code = "THRILLWIKI_ERROR" status_code = 500 def __init__( self, message: Optional[str] = None, error_code: Optional[str] = None, details: Optional[Dict[str, Any]] = None, ): self.message = message or self.default_message self.error_code = error_code or self.error_code self.details = details or {} super().__init__(self.message) def to_dict(self) -> Dict[str, Any]: """Convert exception to dictionary for API responses.""" return { "error_code": self.error_code, "message": self.message, "details": self.details, } class ValidationException(ThrillWikiException): """Raised when data validation fails.""" default_message = "Validation failed" error_code = "VALIDATION_ERROR" status_code = 400 class NotFoundError(ThrillWikiException): """Raised when a requested resource is not found.""" default_message = "Resource not found" error_code = "NOT_FOUND" status_code = 404 class PermissionDeniedError(ThrillWikiException): """Raised when user lacks permission for an operation.""" default_message = "Permission denied" error_code = "PERMISSION_DENIED" status_code = 403 class BusinessLogicError(ThrillWikiException): """Raised when business logic constraints are violated.""" default_message = "Business logic violation" error_code = "BUSINESS_LOGIC_ERROR" status_code = 400 class ExternalServiceError(ThrillWikiException): """Raised when external service calls fail.""" default_message = "External service error" error_code = "EXTERNAL_SERVICE_ERROR" status_code = 502 # Domain-specific exceptions class ParkError(ThrillWikiException): """Base exception for park-related errors.""" error_code = "PARK_ERROR" class ParkNotFoundError(NotFoundError): """Raised when a park is not found.""" default_message = "Park not found" error_code = "PARK_NOT_FOUND" def __init__(self, park_slug: Optional[str] = None, **kwargs): if park_slug: kwargs["details"] = {"park_slug": park_slug} kwargs["message"] = f"Park with slug '{park_slug}' not found" super().__init__(**kwargs) class ParkOperationError(BusinessLogicError): """Raised when park operation constraints are violated.""" default_message = "Invalid park operation" error_code = "PARK_OPERATION_ERROR" class RideError(ThrillWikiException): """Base exception for ride-related errors.""" error_code = "RIDE_ERROR" class RideNotFoundError(NotFoundError): """Raised when a ride is not found.""" default_message = "Ride not found" error_code = "RIDE_NOT_FOUND" def __init__(self, ride_slug: Optional[str] = None, **kwargs): if ride_slug: kwargs["details"] = {"ride_slug": ride_slug} kwargs["message"] = f"Ride with slug '{ride_slug}' not found" super().__init__(**kwargs) class RideOperationError(BusinessLogicError): """Raised when ride operation constraints are violated.""" default_message = "Invalid ride operation" error_code = "RIDE_OPERATION_ERROR" class LocationError(ThrillWikiException): """Base exception for location-related errors.""" error_code = "LOCATION_ERROR" class InvalidCoordinatesError(ValidationException): """Raised when geographic coordinates are invalid.""" default_message = "Invalid geographic coordinates" error_code = "INVALID_COORDINATES" def __init__( self, latitude: Optional[float] = None, longitude: Optional[float] = None, **kwargs, ): if latitude is not None or longitude is not None: kwargs["details"] = {"latitude": latitude, "longitude": longitude} super().__init__(**kwargs) class GeolocationError(ExternalServiceError): """Raised when geolocation services fail.""" default_message = "Geolocation service unavailable" error_code = "GEOLOCATION_ERROR" class ReviewError(ThrillWikiException): """Base exception for review-related errors.""" error_code = "REVIEW_ERROR" class ReviewModerationError(BusinessLogicError): """Raised when review moderation constraints are violated.""" default_message = "Review moderation error" error_code = "REVIEW_MODERATION_ERROR" class DuplicateReviewError(BusinessLogicError): """Raised when user tries to create duplicate reviews.""" default_message = "User has already reviewed this item" error_code = "DUPLICATE_REVIEW" class AccountError(ThrillWikiException): """Base exception for account-related errors.""" error_code = "ACCOUNT_ERROR" class InsufficientPermissionsError(PermissionDeniedError): """Raised when user lacks required permissions.""" default_message = "Insufficient permissions" error_code = "INSUFFICIENT_PERMISSIONS" def __init__(self, required_permission: Optional[str] = None, **kwargs): if required_permission: kwargs["details"] = {"required_permission": required_permission} kwargs["message"] = f"Permission '{required_permission}' required" super().__init__(**kwargs) class EmailError(ExternalServiceError): """Raised when email operations fail.""" default_message = "Email service error" error_code = "EMAIL_ERROR" class CacheError(ThrillWikiException): """Raised when cache operations fail.""" default_message = "Cache operation failed" error_code = "CACHE_ERROR" status_code = 500 class RoadTripError(ExternalServiceError): """Raised when road trip planning fails.""" default_message = "Road trip planning error" error_code = "ROADTRIP_ERROR" def __init__(self, service_name: Optional[str] = None, **kwargs): if service_name: kwargs["details"] = {"service": service_name} super().__init__(**kwargs)