Files
thrillwiki_django_no_react/apps/core/exceptions.py
pacnpal 75cc618c2b update
2025-09-21 20:04:42 -04:00

225 lines
6.1 KiB
Python

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