Files
orion/docs/api/error-handling.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:46:56 +01:00

8.0 KiB

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:

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:

{
  "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:

{
  "detail": "Error message",
  "status_code": 401
}

Rate Limit Error Response

Rate limit errors include additional information:

{
  "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

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

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:

# 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:

# 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:

# 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:

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

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

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:

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)
        }
    )