245 lines
6.3 KiB
Markdown
245 lines
6.3 KiB
Markdown
# 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 |
|
|
|
|
## Error Response Format
|
|
|
|
All errors return a consistent JSON format:
|
|
|
|
```json
|
|
{
|
|
"detail": "Error message describing what went wrong",
|
|
"status_code": 401,
|
|
"timestamp": "2024-11-16T13:00:00Z",
|
|
"path": "/api/v1/auth/login"
|
|
}
|
|
```
|
|
|
|
### 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/Vendor Dashboard (`/admin/*`, `/vendor/*`)
|
|
Returns JSON errors or redirects to error pages based on accept headers.
|
|
|
|
### Shop Requests (`/shop/*`)
|
|
Returns themed error pages matching the vendor'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
|