Files
orion/docs/api/error-handling.md

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