# Error Handling Comprehensive error handling system for the FastAPI multi-tenant e-commerce platform. ## Overview The application uses a structured exception hierarchy with custom exception classes and centralized error handlers. All exceptions are logged, formatted consistently, and return appropriate HTTP status codes. ## Exception Hierarchy ### Base Exceptions All custom exceptions inherit from base exception classes defined in `app.exceptions`: ```python from app.exceptions import ( InvalidTokenException, TokenExpiredException, InvalidCredentialsException, UserNotActiveException, AdminRequiredException, InsufficientPermissionsException, RateLimitException ) ``` ### Authentication Exceptions | Exception | Status Code | Description | |-----------|-------------|-------------| | `InvalidTokenException` | 401 | JWT token is invalid, malformed, or missing required claims | | `TokenExpiredException` | 401 | JWT token has expired | | `InvalidCredentialsException` | 401 | Username/password authentication failed | | `UserNotActiveException` | 403 | User account is inactive or disabled | ### Authorization Exceptions | Exception | Status Code | Description | |-----------|-------------|-------------| | `AdminRequiredException` | 403 | Endpoint requires admin role | | `InsufficientPermissionsException` | 403 | User lacks required permissions | ### Rate Limiting Exceptions | Exception | Status Code | Description | |-----------|-------------|-------------| | `RateLimitException` | 429 | Too many requests, rate limit exceeded | ### Shopping Cart Exceptions | Exception | Status Code | Description | |-----------|-------------|-------------| | `CartItemNotFoundException` | 404 | Cart item not found (product not in cart) | | `InsufficientInventoryForCartException` | 400 | Product doesn't have enough inventory for cart operation | | `InvalidCartQuantityException` | 422 | Cart quantity is invalid (e.g., less than min or greater than max) | | `CartValidationException` | 422 | Cart data validation failed | | `EmptyCartException` | 422 | Operation attempted on empty cart | | `ProductNotAvailableForCartException` | 400 | Product is not available for adding to cart | ### Product Exceptions | Exception | Status Code | Description | |-----------|-------------|-------------| | `ProductNotFoundException` | 404 | Product not found in store catalog | | `ProductNotActiveException` | 400 | Product is inactive and cannot be purchased | ### Inventory Exceptions | Exception | Status Code | Description | |-----------|-------------|-------------| | `InventoryNotFoundException` | 404 | Inventory record not found | | `InsufficientInventoryException` | 400 | Not enough inventory for operation | ## Error Response Format All custom exceptions (inheriting from `OrionException`) return a structured JSON format: ```json { "error_code": "PRODUCT_NOT_FOUND", "message": "Product with ID '123' not found in store 1 catalog", "status_code": 404, "details": { "resource_type": "Product", "identifier": "123", "product_id": 123, "store_id": 1 } } ``` **Standard Fields:** | Field | Type | Description | |-------|------|-------------| | `error_code` | string | Machine-readable error code (e.g., "CART_ITEM_NOT_FOUND") | | `message` | string | Human-readable error message | | `status_code` | integer | HTTP status code | | `details` | object | Additional context-specific error details | **Note:** Generic FastAPI/HTTP errors may still use the simpler format: ```json { "detail": "Error message", "status_code": 401 } ``` ### Rate Limit Error Response Rate limit errors include additional information: ```json { "detail": "Rate limit exceeded", "status_code": 429, "retry_after": 3600, "timestamp": "2024-11-16T13:00:00Z", "path": "/api/v1/resource" } ``` ## Usage Examples ### Raising Exceptions in Code ```python from app.exceptions import InvalidCredentialsException, AdminRequiredException # Authentication failure if not user: raise InvalidCredentialsException("User not found") # Authorization check if user.role != "admin": raise AdminRequiredException() ``` ### Catching Exceptions in Routes ```python from fastapi import HTTPException from app.exceptions import InvalidTokenException @app.post("/api/v1/auth/protected") async def protected_endpoint(token: str): try: user_data = auth_manager.verify_token(token) return {"user": user_data} except InvalidTokenException as e: # Exception will be caught by global handler raise except Exception as e: # Unexpected errors logger.error(f"Unexpected error: {e}") raise HTTPException(status_code=500, detail="Internal server error") ``` ## Context-Aware Error Handling The error handling system is context-aware and provides different error formats based on the request context: ### API Requests (`/api/*`) Returns JSON error responses suitable for API clients. ### Admin/Store Dashboard (`/admin/*`, `/store/*`) Returns JSON errors or redirects to error pages based on accept headers. ### Shop Requests (`/shop/*`) Returns themed error pages matching the store's shop design. ## Logging All errors are automatically logged with the following information: - Error type and message - Request path and method - User information (if authenticated) - Stack trace (for unexpected errors) - Timestamp Example log output: ``` 2024-11-16 13:00:00 [ERROR] middleware.auth: Token verification error: Token missing user identifier 2024-11-16 13:00:00 [ERROR] app.main: Request failed: POST /api/v1/auth/login - 401 ``` ## Best Practices ### 1. Use Specific Exceptions Always use the most specific exception class available: ```python # Good raise InvalidCredentialsException("Invalid email or password") # Avoid raise HTTPException(status_code=401, detail="Invalid credentials") ``` ### 2. Provide Meaningful Messages Include context in error messages: ```python # Good raise InvalidTokenException("Token missing user identifier") # Avoid raise InvalidTokenException("Invalid token") ``` ### 3. Don't Expose Sensitive Information Never include sensitive data in error messages: ```python # Good raise InvalidCredentialsException("Invalid email or password") # Avoid - reveals which field is wrong raise InvalidCredentialsException(f"User {email} not found") ``` ### 4. Log Before Raising Log errors before raising them for debugging: ```python try: result = risky_operation() except OperationFailed as e: logger.error(f"Operation failed: {e}", exc_info=True) raise InternalServerException("Operation failed") ``` ## Testing Error Handling ### Unit Tests ```python import pytest from app.exceptions import InvalidTokenException def test_invalid_token(): auth_manager = AuthManager() with pytest.raises(InvalidTokenException) as exc_info: auth_manager.verify_token("invalid-token") assert "Could not validate credentials" in str(exc_info.value.message) ``` ### Integration Tests ```python def test_authentication_error_response(client): response = client.post( "/api/v1/auth/login", json={"username": "wrong", "password": "wrong"} ) assert response.status_code == 401 assert "detail" in response.json() ``` ## Global Exception Handlers The application registers global exception handlers in `main.py`: ```python from fastapi import FastAPI from app.exceptions import InvalidTokenException, RateLimitException app = FastAPI() @app.exception_handler(InvalidTokenException) async def invalid_token_handler(request, exc): return JSONResponse( status_code=401, content={ "detail": exc.message, "status_code": 401, "timestamp": datetime.now(timezone.utc).isoformat(), "path": str(request.url.path) } ) ``` ## Related Documentation - [Authentication](authentication.md) - Authentication-related exceptions - [RBAC](rbac.md) - Authorization and permission exceptions - [Rate Limiting](rate-limiting.md) - Rate limit error handling - [Testing Guide](../testing/testing-guide.md) - Testing error scenarios