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

6.3 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

Error Response Format

All errors return a consistent JSON format:

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

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

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