Files
orion/docs/api/error-handling.md
Samir Boulahtit d648c921b7
Some checks failed
CI / ruff (push) Successful in 10s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
docs: add consolidated dev URL reference and migrate /shop to /storefront
- Add Development URL Quick Reference section to url-routing overview
  with all login URLs, entry points, and full examples
- Replace /shop/ path segments with /storefront/ across 50 docs files
- Update file references: shop_pages.py → storefront_pages.py,
  templates/shop/ → templates/storefront/, api/v1/shop/ → api/v1/storefront/
- Preserve domain references (orion.shop) and /store/ staff dashboard paths
- Archive docs left unchanged (historical)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 13:23:44 +01:00

293 lines
8.0 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 |
### 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.
### Storefront Requests (`/storefront/*`)
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