# Error Handling Guidelines This document provides guidelines for handling errors consistently across the ThrillWiki backend. ## Exception Hierarchy ThrillWiki uses a structured exception hierarchy defined in `apps/core/exceptions.py`: ``` ThrillWikiException (base) ├── ValidationException (400) ├── NotFoundError (404) ├── PermissionDeniedError (403) ├── BusinessLogicError (400) ├── ServiceError (500) ├── ExternalServiceError (502) ├── CacheError (500) │ ├── Domain-specific exceptions: │ ├── ParkError │ │ ├── ParkNotFoundError │ │ └── ParkOperationError │ ├── RideError │ │ ├── RideNotFoundError │ │ └── RideOperationError │ ├── LocationError │ │ ├── InvalidCoordinatesError │ │ └── GeolocationError │ ├── ReviewError │ │ ├── ReviewModerationError │ │ └── DuplicateReviewError │ └── AccountError │ ├── InsufficientPermissionsError │ └── EmailError ``` ## Using ErrorHandler The `ErrorHandler` class in `apps/core/utils/error_handling.py` provides standardized error handling. ### Template Views ```python from apps.core.utils.error_handling import ErrorHandler from apps.core.exceptions import ServiceError def my_view(request): try: result = SomeService.do_operation(...) except ServiceError as e: ErrorHandler.handle_view_error( request, e, user_message="The operation failed. Please try again.", log_message=f"Service operation failed for user {request.user.id}" ) return redirect("some-fallback") except ValidationError as e: ErrorHandler.handle_view_error( request, e, user_message="Invalid data provided", level="warning" ) return redirect("form-view") ``` ### API Views ```python from apps.core.utils.error_handling import ErrorHandler from apps.core.exceptions import ServiceError from rest_framework import status class MyAPIView(APIView): def post(self, request): try: result = SomeService.do_operation(...) return ErrorHandler.api_success_response( data=result, message="Operation completed successfully" ) except ServiceError as e: return ErrorHandler.handle_api_error( e, user_message="Failed to complete operation", status_code=status.HTTP_400_BAD_REQUEST ) ``` ## Best Practices ### 1. Always Catch Specific Exceptions ```python # Good try: park = ParkService.create_park(...) except ParkOperationError as e: # Handle park-specific error pass except ValidationException as e: # Handle validation error pass # Bad try: park = ParkService.create_park(...) except Exception as e: # Too broad - loses error context pass ``` ### 2. Log with Appropriate Context ```python # Good logger.error( f"Park creation failed for user {user.id}: {error}", exc_info=True, extra={"user_id": user.id, "park_name": name} ) # Bad logger.error(f"Error: {error}") ``` ### 3. Provide Clear User Messages ```python # Good - User-friendly and actionable ErrorHandler.handle_view_error( request, error, user_message="Unable to save your changes. Please check your input and try again." ) # Bad - Technical details exposed to user ErrorHandler.handle_view_error( request, error, user_message=f"IntegrityError: UNIQUE constraint failed: parks_park.slug" ) ``` ### 4. Use Appropriate HTTP Status Codes | Error Type | Status Code | When to Use | |------------|-------------|-------------| | ValidationException | 400 | Invalid user input | | NotFoundError | 404 | Resource doesn't exist | | PermissionDeniedError | 403 | User lacks permission | | BusinessLogicError | 400 | Business rule violation | | ServiceError | 500 | Internal service failure | | ExternalServiceError | 502 | Third-party service failure | ### 5. Never Use Bare `except:` Clauses ```python # Never do this try: something() except: pass # Always specify exception type try: something() except SpecificException: handle_error() ``` ## Error Response Format ### API Error Response ```json { "error": "User-friendly error message", "detail": "Technical error details", "error_code": "SPECIFIC_ERROR_CODE", "details": { "field": "Additional context" } } ``` ### API Success Response ```json { "status": "success", "message": "Operation completed successfully", "data": { // Response data } } ``` ## Creating Custom Exceptions When creating domain-specific exceptions: ```python from apps.core.exceptions import BusinessLogicError class MyDomainError(BusinessLogicError): """Raised when my domain operation fails.""" default_message = "My domain operation failed" error_code = "MY_DOMAIN_ERROR" status_code = 400 def __init__(self, context_value: str = None, **kwargs): if context_value: kwargs["details"] = {"context": context_value} kwargs["message"] = f"Operation failed for: {context_value}" super().__init__(**kwargs) ```