Files
orion/docs/development/error-rendering/error-rendering-developer-documentation.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

36 KiB

Error Handling System - Developer Documentation

Version: 1.0.0 Last Updated: 2025 Status: Phase 1 Complete (Admin), Phase 2-3 Pending (Store, Storefront)


Table of Contents

  1. Overview
  2. Architecture
  3. System Components
  4. Context Detection
  5. Error Response Types
  6. Template System
  7. Implementation Status
  8. Developer Guidelines
  9. Adding New Error Pages
  10. Testing Guide
  11. Troubleshooting
  12. API Reference

Overview

Purpose

The error handling system provides context-aware error responses throughout the application. It automatically determines whether to return JSON (for API calls) or HTML error pages (for browser requests), and renders appropriate error pages based on the request context (Admin, Store Dashboard, or Storefront).

Key Features

  • Context-Aware: Different error pages for Admin, Store, and Storefront areas
  • Automatic Detection: Distinguishes between API and HTML page requests
  • Consistent JSON API: API endpoints always return standardized JSON errors
  • Fallback Mechanism: Gracefully handles missing templates
  • Debug Mode: Shows technical details to admin users only
  • Theme Integration: Storefront error pages support store theming (Phase 3)
  • Security: 401 errors automatically redirect to appropriate login pages

Design Principles

  1. Separation of Concerns: HTML templates are separate from exception handler logic
  2. Fail-Safe: Multiple fallback levels ensure errors always render
  3. Developer-Friendly: Easy to add new error pages or contexts
  4. User-Centric: Professional error pages with clear messaging and actions
  5. Secure: Technical details hidden from non-admin users

Architecture

Request Flow

HTTP Request
    ↓
Store Context Middleware (detects store from domain/subdomain/path)
    ↓
Context Detection Middleware (detects API/Admin/Store/Storefront)
    ↓
Route Handler (processes request, may throw exception)
    ↓
Exception Handler (catches and processes exception)
    ↓
    ├─→ API Request? → Return JSON Response
    ├─→ HTML Request + 401? → Redirect to Login
    └─→ HTML Request? → Render Context-Aware Error Page

Components

middleware/
├── store_context.py      # Detects store from URL (existing)
└── context_middleware.py  # Detects request context type (NEW)

app/exceptions/
├── handler.py             # Exception handlers (refactored)
├── error_renderer.py      # Error page rendering logic (NEW)
└── base.py               # Base exception classes (existing)

app/templates/
├── admin/errors/         # Admin error pages (Phase 1 - COMPLETE)
├── store/errors/        # Store error pages (Phase 2 - PENDING)
├── storefront/errors/    # Storefront error pages (Phase 3 - PENDING)
└── shared/               # Shared fallback error pages (COMPLETE)

System Components

1. Context Detection Middleware

File: middleware/context_middleware.py

Purpose: Detects the request context type and injects it into request.state.context_type.

Context Types:

class RequestContext(str, Enum):
    API = "api"                    # API endpoints (/api/*)
    ADMIN = "admin"                # Admin portal (/admin/* or admin.*)
    STORE_DASHBOARD = "store"    # Store management (/store/*)
    SHOP = "shop"                  # Customer storefront (store subdomains)
    FALLBACK = "fallback"          # Unknown/generic context

Detection Logic:

  1. API - Path starts with /api/ (highest priority)
  2. ADMIN - Path starts with /admin or host starts with admin.
  3. STORE_DASHBOARD - Path starts with /store/
  4. SHOP - request.state.store exists (set by store_context_middleware)
  5. FALLBACK - None of the above match

Usage:

from middleware.frontend_type import get_frontend_type
from app.modules.enums import FrontendType

def my_handler(request: Request):
    frontend_type = get_frontend_type(request)

    if frontend_type == FrontendType.ADMIN:
        # Handle admin-specific logic
        pass

2. Error Page Renderer

File: app/exceptions/error_renderer.py

Purpose: Renders context-aware HTML error pages using Jinja2 templates.

Key Methods:

ErrorPageRenderer.render_error_page(
    request: Request,
    status_code: int,
    error_code: str,
    message: str,
    details: Optional[Dict[str, Any]] = None,
    show_debug: bool = False,
) -> HTMLResponse

Template Selection Priority:

  1. {context}/errors/{status_code}.html (e.g., admin/errors/404.html)
  2. {context}/errors/generic.html (e.g., admin/errors/generic.html)
  3. shared/{status_code}-fallback.html (e.g., shared/404-fallback.html)
  4. shared/generic-fallback.html (absolute fallback)

Template Variables Provided:

{
    "status_code": int,           # HTTP status code
    "status_name": str,           # Friendly name ("Not Found")
    "error_code": str,            # Application error code
    "message": str,               # User-friendly message
    "details": dict,              # Additional error details
    "show_debug": bool,           # Whether to show debug info
    "context_type": str,          # Request context type
    "path": str,                  # Request path
    "store": dict,               # Store info (storefront context only)
    "theme": dict,                # Theme data (storefront context only)
}

3. Exception Handler

File: app/exceptions/handler.py

Purpose: Central exception handling for all application exceptions.

Handlers:

  • OrionException - Custom application exceptions
  • HTTPException - FastAPI HTTP exceptions
  • RequestValidationError - Pydantic validation errors
  • Exception - Generic Python exceptions
  • 404 - Not Found errors

Response Logic:

if request.url.path.startswith("/api/"):
    return JSONResponse(...)  # Always JSON for API
elif _is_html_page_request(request):
    if status_code == 401:
        return RedirectResponse(...)  # Redirect to login
    else:
        return ErrorPageRenderer.render_error_page(...)  # HTML error page
else:
    return JSONResponse(...)  # Default to JSON

Context Detection

How Context is Determined

The system uses a priority-based approach to detect context:

# Priority 1: API Context
if path.startswith("/api/"):
    return RequestContext.API

# Priority 2: Admin Context
if path.startswith("/admin") or host.startswith("admin."):
    return RequestContext.ADMIN

# Priority 3: Store Dashboard Context
if path.startswith("/store/"):
    return RequestContext.STORE_DASHBOARD

# Priority 4: Storefront Context
if hasattr(request.state, 'store') and request.state.store:
    return RequestContext.SHOP

# Priority 5: Fallback
return RequestContext.FALLBACK

Context Examples

URL Host Context
/api/v1/admin/stores any API
/admin/dashboard any ADMIN
/store/products any STORE_DASHBOARD
/products store1.platform.com SHOP
/products customdomain.com SHOP (if store detected)
/about platform.com FALLBACK

Special Cases

Admin Access via Subdomain:

admin.platform.com/dashboard → ADMIN context

Store Dashboard Access:

/store/store1/dashboard → STORE_DASHBOARD context
store1.platform.com/store/dashboard → STORE_DASHBOARD context

Storefront Access:

store1.platform.com/ → SHOP context
customdomain.com/ → SHOP context (if store verified)

Error Response Types

1. JSON Responses (API Context)

When: Request path starts with /api/

Format:

{
  "error_code": "STORE_NOT_FOUND",
  "message": "Store with ID '999' not found",
  "status_code": 404,
  "details": {
    "store_id": "999"
  }
}

Always JSON, Regardless of Accept Header: API endpoints MUST always return JSON, even if the client sends Accept: text/html.

2. HTML Error Pages (HTML Page Requests)

When:

  • NOT an API request
  • GET request
  • Accept header includes text/html
  • NOT already on login page

Renders: Context-appropriate HTML error page

Example: Admin 404

<html>
  <head><title>404 - Page Not Found | Admin Portal</title></head>
  <body>
    <div class="error-container">
      <h1>404</h1>
      <p>The admin page you're looking for doesn't exist.</p>
      <a href="/admin/dashboard">Go to Dashboard</a>
    </div>
  </body>
</html>

3. Login Redirects (401 Unauthorized)

When:

  • HTML page request
  • 401 status code

Behavior: Redirect to appropriate login page based on context

Context Redirect To
ADMIN /admin/login
STORE_DASHBOARD /store/login
SHOP /storefront/login
FALLBACK /admin/login

Template System

Template Structure

All error templates follow this organization:

app/templates/
├── admin/
│   └── errors/
│       ├── base.html          # Base template (extends nothing)
│       ├── 400.html           # Bad Request
│       ├── 401.html           # Unauthorized
│       ├── 403.html           # Forbidden
│       ├── 404.html           # Not Found
│       ├── 422.html           # Validation Error
│       ├── 429.html           # Rate Limit
│       ├── 500.html           # Internal Server Error
│       ├── 502.html           # Bad Gateway
│       └── generic.html       # Catch-all
│
├── store/
│   └── errors/
│       └── (same structure as admin)
│
├── storefront/
│   └── errors/
│       └── (same structure as admin)
│
└── shared/
    ├── 404-fallback.html
    ├── 500-fallback.html
    ├── generic-fallback.html
    └── cdn-fallback.html

Base Template Pattern

Each context has its own base.html template:

<!-- app/templates/admin/errors/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}{{ status_code }} - {{ status_name }}{% endblock %}</title>
    <style>
        /* Admin-specific styling */
    </style>
</head>
<body>
    <div class="error-container">
        {% block content %}
        <!-- Default error content -->
        {% endblock %}
    </div>
</body>
</html>

Specific Error Template Pattern

<!-- app/templates/admin/errors/404.html -->
{% extends "admin/errors/base.html" %}

{% block icon %}🔍{% endblock %}
{% block title %}404 - Page Not Found{% endblock %}

{% block content %}
<div class="error-icon">🔍</div>
<div class="status-code">404</div>
<div class="status-name">Page Not Found</div>
<div class="error-message">
    The admin page you're looking for doesn't exist or has been moved.
</div>
<div class="error-code">Error Code: {{ error_code }}</div>

<div class="action-buttons">
    <a href="/admin/dashboard" class="btn btn-primary">Go to Dashboard</a>
    <a href="javascript:history.back()" class="btn btn-secondary">Go Back</a>
</div>

{% if show_debug %}
<div class="debug-info">
    <!-- Debug information for admins -->
</div>
{% endif %}
{% endblock %}

Context-Specific Considerations

Admin Error Pages

Purpose: Error pages for platform administrators

Characteristics:

  • Professional platform branding
  • Links to admin dashboard
  • Full debug information when appropriate
  • Support contact link

Action Buttons:

  • Primary: "Go to Dashboard" → /admin/dashboard
  • Secondary: "Go Back" → javascript:history.back()

Store Error Pages

Purpose: Error pages for store dashboard users

Characteristics:

  • Professional store management branding
  • Links to store dashboard
  • Debug information for store admins
  • Store support contact link

Action Buttons:

  • Primary: "Go to Dashboard" → /store/dashboard
  • Secondary: "Go Back" → javascript:history.back()

Storefront Error Pages

Purpose: Error pages for customers on store storefronts

Characteristics:

  • Uses store theme (colors, logo, fonts)
  • Customer-friendly language
  • No technical jargon
  • Links to storefront homepage
  • Customer support contact

Action Buttons:

  • Primary: "Continue Shopping" → Storefront homepage
  • Secondary: "Contact Support" → Store support page

Theme Integration:

<!-- Storefront error pages use store theme variables -->
<style>
    :root {
        --color-primary: {{ theme.colors.primary }};
        --color-background: {{ theme.colors.background }};
        --font-body: {{ theme.fonts.body }};
    }
</style>

Implementation Status

Phase 1: Admin Error Handling COMPLETE

Status: Fully implemented and ready for use

Components:

  • Context detection middleware
  • Error page renderer
  • Refactored exception handler
  • Admin error templates (11 files)
  • Fallback templates (3 files)

Error Codes Implemented:

  • 400 (Bad Request)
  • 401 (Unauthorized)
  • 403 (Forbidden)
  • 404 (Not Found)
  • 422 (Validation Error)
  • 429 (Rate Limit)
  • 500 (Internal Server Error)
  • 502 (Bad Gateway)
  • Generic (Catch-all)

Phase 2: Store Error Handling PENDING

Status: Not yet implemented

Required Tasks:

  1. Create /app/templates/store/errors/ directory
  2. Copy admin templates as starting point
  3. Customize messaging for store context:
    • Change "Admin Portal" to "Store Portal"
    • Update dashboard links to /store/dashboard
    • Adjust support links to store support
  4. Update action button destinations
  5. Test all error codes in store context

Estimated Effort: 1-2 hours

Priority: Medium (stores currently see fallback pages)

Phase 3: Storefront Error Handling PENDING

Status: Not yet implemented

Required Tasks:

  1. Create /app/templates/storefront/errors/ directory
  2. Create customer-facing error templates:
    • Use customer-friendly language
    • Integrate store theme variables
    • Add store logo/branding
  3. Update ErrorPageRenderer to pass theme data
  4. Implement theme integration:
    if context_type == RequestContext.SHOP:
        template_data["theme"] = request.state.theme
        template_data["store"] = request.state.store
    
  5. Test with multiple store themes
  6. Test on custom domains

Estimated Effort: 2-3 hours

Priority: High (customers currently see non-branded fallback pages)

Dependencies: Requires store theme system (already exists)


Developer Guidelines

When to Throw Exceptions

Use Custom Exceptions:

from app.exceptions import StoreNotFoundException

# Good - Specific exception
raise StoreNotFoundException(store_id="123")

# Avoid - Generic exception with less context
raise HTTPException(status_code=404, detail="Store not found")

Exception Selection Guide:

Scenario Exception Status Code
Resource not found {Resource}NotFoundException 404
Validation failed ValidationException 422
Unauthorized AuthenticationException 401
Forbidden AuthorizationException 403
Business rule violated BusinessLogicException 400
External service failed ExternalServiceException 502
Rate limit exceeded RateLimitException 429

Creating Custom Exceptions

Follow this pattern:

# app/exceptions/my_domain.py
from .base import ResourceNotFoundException

class MyResourceNotFoundException(ResourceNotFoundException):
    """Raised when MyResource is not found."""

    def __init__(self, resource_id: str):
        super().__init__(
            resource_type="MyResource",
            identifier=resource_id,
            message=f"MyResource with ID '{resource_id}' not found",
            error_code="MY_RESOURCE_NOT_FOUND",
        )

Then register in app/exceptions/__init__.py:

from .my_domain import MyResourceNotFoundException

__all__ = [
    # ... existing exports
    "MyResourceNotFoundException",
]

Error Messages Best Practices

User-Facing Messages:

  • Clear and concise
  • Avoid technical jargon
  • Suggest next actions
  • Never expose sensitive information
# Good
message="The product you're looking for is currently unavailable."

# Bad
message="SELECT * FROM products WHERE id=123 returned 0 rows"

Error Codes:

  • Use UPPER_SNAKE_CASE
  • Be descriptive but concise
  • Follow existing patterns
# Good
error_code="PRODUCT_OUT_OF_STOCK"
error_code="PAYMENT_PROCESSING_FAILED"

# Bad
error_code="error1"
error_code="ProductOutOfStockException"

Details Dictionary

Use the details dictionary for additional context:

raise StoreNotFoundException(
    store_id="123"
)
# Results in:
# details = {"store_id": "123", "resource_type": "Store"}

# For validation errors:
raise ValidationException(
    message="Invalid email format",
    field="email",
    details={
        "provided_value": user_input,
        "expected_format": "user@example.com"
    }
)

What to Include in Details:

  • Resource identifiers
  • Validation error specifics
  • Operation context
  • Non-sensitive debugging info
  • Passwords or secrets
  • Internal system paths
  • Database queries
  • Stack traces (use show_debug instead)

Adding New Error Pages

Step 1: Identify Context

Determine which context needs the new error page:

  • Admin Portal → admin/errors/
  • Store Dashboard → store/errors/
  • Customer Storefront → storefront/errors/

Step 2: Choose Template Type

Specific Error Code Template:

  • For common HTTP status codes (400, 403, 404, 500, etc.)
  • File name: {status_code}.html

Generic Catch-All Template:

  • For less common or custom error codes
  • File name: generic.html
  • Uses variables to display appropriate content

Step 3: Create Template

Option A: Extend Base Template (Recommended)

<!-- app/templates/admin/errors/503.html -->
{% extends "admin/errors/base.html" %}

{% block icon %}🔧{% endblock %}
{% block title %}503 - Service Unavailable{% endblock %}

{% block content %}
<div class="error-icon">🔧</div>
<div class="status-code">503</div>
<div class="status-name">Service Unavailable</div>
<div class="error-message">
    The service is temporarily unavailable. We're working to restore it.
</div>
<div class="error-code">Error Code: {{ error_code }}</div>

<div class="action-buttons">
    <a href="javascript:location.reload()" class="btn btn-primary">Retry</a>
    <a href="/admin/dashboard" class="btn btn-secondary">Dashboard</a>
</div>

{% if show_debug %}
<div class="debug-info">
    <h3>🔧 Debug Information (Admin Only)</h3>
    <div class="debug-item">
        <span class="debug-label">Path:</span>
        <span class="debug-value">{{ path }}</span>
    </div>
    {% if details %}
    <div class="debug-item">
        <span class="debug-label">Details:</span>
        <pre>{{ details | tojson(indent=2) }}</pre>
    </div>
    {% endif %}
</div>
{% endif %}
{% endblock %}

Option B: Standalone Template (For custom designs)

<!-- app/templates/storefront/errors/custom.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{ status_code }} - {{ status_name }}</title>
    <style>
        /* Custom styling that uses store theme */
        :root {
            --primary: {{ theme.colors.primary }};
        }
    </style>
</head>
<body>
    <!-- Custom error page design -->
</body>
</html>

Step 4: Add to ErrorPageRenderer (if needed)

If adding a new status code with a friendly name:

# app/exceptions/error_renderer.py

STATUS_CODE_NAMES = {
    # ... existing codes
    503: "Service Unavailable",  # Add new code
}

STATUS_CODE_MESSAGES = {
    # ... existing messages
    503: "The service is temporarily unavailable. Please try again later.",
}

Step 5: Test

# Test the new error page
curl -H "Accept: text/html" http://localhost:8000/admin/test-503

# Or trigger programmatically:
from app.exceptions import ServiceUnavailableException
raise ServiceUnavailableException("Maintenance in progress")

Testing Guide

Unit Tests

Test Context Detection:

# tests/test_frontend_detector.py
from app.core.frontend_detector import FrontendDetector
from app.modules.enums import FrontendType

def test_admin_detection():
    assert FrontendDetector.is_admin("localhost", "/admin/dashboard") is True

def test_storefront_detection():
    frontend_type = FrontendDetector.detect(
        host="localhost", path="/storefront/products", has_store_context=True
    )
    assert frontend_type == FrontendType.STOREFRONT

Test Error Renderer:

# tests/test_error_renderer.py
from app.exceptions.error_renderer import ErrorPageRenderer

def test_template_selection_admin():
    template = ErrorPageRenderer._find_template(
        context_type=RequestContext.ADMIN,
        status_code=404
    )
    assert template == "admin/errors/404.html"

def test_template_fallback():
    template = ErrorPageRenderer._find_template(
        context_type=RequestContext.ADMIN,
        status_code=999  # Non-existent error code
    )
    assert template == "admin/errors/generic.html"

Test Exception Handlers:

# tests/test_exception_handlers.py
from fastapi.testclient import TestClient

def test_api_returns_json(client: TestClient):
    response = client.get("/api/v1/nonexistent")
    assert response.status_code == 404
    assert response.headers["content-type"] == "application/json"
    data = response.json()
    assert "error_code" in data
    assert "message" in data

def test_html_page_returns_html(client: TestClient):
    response = client.get(
        "/admin/nonexistent",
        headers={"Accept": "text/html"}
    )
    assert response.status_code == 404
    assert "text/html" in response.headers["content-type"]
    assert "<html" in response.text

Integration Tests

Test Admin Error Pages:

def test_admin_404_error_page(client: TestClient):
    response = client.get(
        "/admin/nonexistent",
        headers={"Accept": "text/html"}
    )
    assert response.status_code == 404
    assert "Page Not Found" in response.text
    assert "/admin/dashboard" in response.text  # Dashboard link
    assert "Admin Portal" in response.text

def test_admin_403_error_page(client: TestClient):
    # Trigger 403 by accessing restricted resource
    response = client.get(
        "/admin/super-admin-only",
        headers={"Accept": "text/html", "Authorization": "Bearer regular_user_token"}
    )
    assert response.status_code == 403
    assert "Access Denied" in response.text

Test 401 Redirect:

def test_401_redirects_to_admin_login(client: TestClient):
    response = client.get(
        "/admin/dashboard",
        headers={"Accept": "text/html"},
        follow_redirects=False
    )
    assert response.status_code == 302
    assert response.headers["location"] == "/admin/login"

def test_401_api_returns_json(client: TestClient):
    response = client.get(
        "/api/v1/admin/dashboard",
        follow_redirects=False
    )
    assert response.status_code == 401
    assert response.headers["content-type"] == "application/json"

Manual Testing Checklist

Admin Context:

# Test 404
curl -H "Accept: text/html" http://localhost:8000/admin/nonexistent
# Expected: Admin 404 page with "Go to Dashboard" button

# Test API 404
curl http://localhost:8000/api/v1/admin/nonexistent
# Expected: JSON error response

# Test 401 redirect
curl -H "Accept: text/html" http://localhost:8000/admin/dashboard
# Expected: 302 redirect to /admin/login

# Test validation error
curl -X POST -H "Accept: text/html" http://localhost:8000/admin/stores \
  -d '{"invalid": "data"}'
# Expected: Admin 422 page with validation errors

Store Context (Phase 2):

# Test 404
curl -H "Accept: text/html" http://localhost:8000/store/nonexistent
# Expected: Store 404 page

# Test 401 redirect
curl -H "Accept: text/html" http://localhost:8000/store/dashboard
# Expected: 302 redirect to /store/login

Storefront Context (Phase 3):

# Test 404 on store subdomain
curl -H "Accept: text/html" http://store1.localhost:8000/nonexistent
# Expected: Storefront 404 page with store theme

# Test 500 error
# Trigger server error on storefront
# Expected: Storefront 500 page with store branding

Performance Testing

Load Test Error Handling:

# Test that error pages don't significantly impact performance
import time

def test_error_page_performance(client: TestClient):
    # Test 100 consecutive 404 requests
    start = time.time()
    for _ in range(100):
        response = client.get(
            "/admin/nonexistent",
            headers={"Accept": "text/html"}
        )
        assert response.status_code == 404
    duration = time.time() - start

    # Should complete in reasonable time (< 5 seconds)
    assert duration < 5.0
    print(f"100 error pages rendered in {duration:.2f}s")

Template Validation

Check Template Syntax:

# Validate all Jinja2 templates
python -c "
from jinja2 import Environment, FileSystemLoader
import os

env = Environment(loader=FileSystemLoader('app/templates'))
errors = []

for root, dirs, files in os.walk('app/templates'):
    for file in files:
        if file.endswith('.html'):
            path = os.path.join(root, file)
            rel_path = os.path.relpath(path, 'app/templates')
            try:
                env.get_template(rel_path)
                print(f'✓ {rel_path}')
            except Exception as e:
                errors.append((rel_path, str(e)))
                print(f'✗ {rel_path}: {e}')

if errors:
    print(f'\n{len(errors)} template(s) have errors')
    exit(1)
else:
    print(f'\nAll templates validated successfully')
"

Troubleshooting

Issue: API Returning HTML Instead of JSON

Symptoms:

curl http://localhost:8000/api/v1/admin/stores/999
# Returns HTML instead of JSON

Diagnosis: Check if _is_api_request() is working correctly:

# Add logging to handler.py
logger.debug(f"Path: {request.url.path}, is_api: {_is_api_request(request)}")

Solution: Ensure path check is correct:

def _is_api_request(request: Request) -> bool:
    return request.url.path.startswith("/api/")  # Must have leading slash

Issue: HTML Pages Returning JSON

Symptoms: Browser shows JSON instead of error page when visiting /admin/nonexistent

Diagnosis: Check Accept header:

# Add logging
logger.debug(f"Accept: {request.headers.get('accept', '')}")
logger.debug(f"is_html: {_is_html_page_request(request)}")

Common Causes:

  1. Browser not sending Accept: text/html
  2. Request is POST/PUT/DELETE (not GET)
  3. Path is /api/*
  4. Already on login page

Solution: Verify _is_html_page_request() logic:

def _is_html_page_request(request: Request) -> bool:
    if _is_api_request(request):
        return False
    if request.url.path.endswith("/login"):
        return False
    if request.method != "GET":
        return False
    accept_header = request.headers.get("accept", "")
    if "text/html" not in accept_header:
        return False
    return True

Issue: Template Not Found Error

Symptoms:

jinja2.exceptions.TemplateNotFound: admin/errors/404.html

Diagnosis:

  1. Check template file exists:
ls -la app/templates/admin/errors/404.html
  1. Check templates directory configuration in main.py:
# Should be:
TEMPLATES_DIR = BASE_DIR / "app" / "templates"
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))

Solution:

  • Ensure template files are in correct location
  • Verify templates directory path is correct
  • Check file permissions

Issue: Wrong Context Detected

Symptoms: Admin page shows storefront error page, or vice versa

Diagnosis: Add context logging:

# In context_middleware.py
logger.info(
    f"Context detection: path={request.url.path}, "
    f"host={request.headers.get('host')}, "
    f"context={context_type}"
)

Solution: Check middleware order in main.py:

# Correct order:
app.middleware("http")(store_context_middleware)  # FIRST
app.middleware("http")(context_middleware)         # SECOND

Issue: Debug Info Not Showing for Admin

Symptoms: Debug information not visible in admin error pages

Diagnosis: Check _is_admin_user() implementation:

# In error_renderer.py
@staticmethod
def _is_admin_user(request: Request) -> bool:
    context_type = get_request_context(request)
    logger.debug(f"Checking admin user: context={context_type}")
    return context_type == RequestContext.ADMIN

Solution:

  • Verify context is correctly detected as ADMIN
  • Check show_debug parameter is True in render call
  • Ensure template has {% if show_debug %} block

Issue: 401 Not Redirecting

Symptoms: 401 errors show JSON instead of redirecting to login

Diagnosis: Check if request is detected as HTML page request:

# Add logging before redirect check
logger.debug(
    f"401 handling: is_html={_is_html_page_request(request)}, "
    f"path={request.url.path}, method={request.method}, "
    f"accept={request.headers.get('accept')}"
)

Solution: Ensure all conditions for HTML page request are met:

  • NOT an API endpoint
  • GET request
  • Accept header includes text/html
  • NOT already on login page

Issue: Store Theme Not Applied to Storefront Errors

Symptoms: Storefront error pages don't use store colors/branding (Phase 3 issue)

Diagnosis:

  1. Check if theme is in request state:
logger.debug(f"Theme in state: {hasattr(request.state, 'theme')}")
logger.debug(f"Theme data: {getattr(request.state, 'theme', None)}")
  1. Check if theme is passed to template:
# In error_renderer.py _get_context_data()
if context_type == RequestContext.SHOP:
    theme = getattr(request.state, "theme", None)
    logger.debug(f"Passing theme to template: {theme}")

Solution:

  • Ensure theme_context_middleware ran before error
  • Verify theme data structure is correct
  • Check template uses theme variables correctly:
<style>
:root {
    --primary: {{ theme.colors.primary if theme else '#6366f1' }};
}
</style>

API Reference

Frontend Type Detection

get_frontend_type(request: Request) -> FrontendType

Gets the frontend type for the current request.

Parameters:

  • request (Request): FastAPI request object

Returns:

  • FrontendType enum value

Example:

from middleware.frontend_type import get_frontend_type
from app.modules.enums import FrontendType

def my_handler(request: Request):
    frontend_type = get_frontend_type(request)
    return {"frontend_type": frontend_type.value}

Error Rendering

ErrorPageRenderer.render_error_page(...) -> HTMLResponse

Renders context-aware HTML error page.

Parameters:

request: Request                          # FastAPI request object
status_code: int                          # HTTP status code
error_code: str                           # Application error code
message: str                              # User-friendly error message
details: Optional[Dict[str, Any]] = None  # Additional error details
show_debug: bool = False                  # Whether to show debug info

Returns:

  • HTMLResponse with rendered error page

Example:

from app.exceptions.error_renderer import ErrorPageRenderer

return ErrorPageRenderer.render_error_page(
    request=request,
    status_code=404,
    error_code="STORE_NOT_FOUND",
    message="The store you're looking for doesn't exist.",
    details={"store_id": "123"},
    show_debug=True,
)

ErrorPageRenderer.get_templates_dir() -> Path

Gets the templates directory path.

Returns:

  • Path object pointing to templates directory

ErrorPageRenderer._find_template(context_type, status_code) -> str

Finds appropriate error template based on context and status code.

Parameters:

  • context_type (RequestContext): Request context type
  • status_code (int): HTTP status code

Returns:

  • String path to template (e.g., "admin/errors/404.html")

Exception Utilities

raise_not_found(resource_type: str, identifier: str) -> None

Convenience function to raise ResourceNotFoundException.

Example:

from app.exceptions.handler import raise_not_found

raise_not_found("Store", "123")
# Raises: ResourceNotFoundException with appropriate message

raise_validation_error(message: str, field: str = None, details: dict = None) -> None

Convenience function to raise ValidationException.

Example:

from app.exceptions.handler import raise_validation_error

raise_validation_error(
    message="Invalid email format",
    field="email",
    details={"provided": user_input}
)

raise_auth_error(message: str = "Authentication failed") -> None

Convenience function to raise AuthenticationException.

raise_permission_error(message: str = "Access denied") -> None

Convenience function to raise AuthorizationException.


Configuration

Middleware Order

Critical: Middleware must be registered in this exact order in main.py:

# 1. CORS (if needed)
app.add_middleware(CORSMiddleware, ...)

# 2. Store Context Detection (MUST BE FIRST)
app.middleware("http")(store_context_middleware)

# 3. Context Detection (MUST BE AFTER STORE)
app.middleware("http")(context_middleware)

# 4. Theme Context (MUST BE AFTER CONTEXT)
app.middleware("http")(theme_context_middleware)

# 5. Logging (optional, should be last)
app.add_middleware(LoggingMiddleware)

Why This Order Matters:

  1. Store context must set request.state.store first
  2. Context detection needs store info to identify SHOP context
  3. Theme context needs store info to load theme
  4. Logging should be last to capture all middleware activity

Template Directory

Configure in main.py:

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent
TEMPLATES_DIR = BASE_DIR / "app" / "templates"

templates = Jinja2Templates(directory=str(TEMPLATES_DIR))

Error Code Registry

Update as needed in app/exceptions/error_renderer.py:

STATUS_CODE_NAMES = {
    400: "Bad Request",
    401: "Unauthorized",
    403: "Forbidden",
    404: "Not Found",
    422: "Validation Error",
    429: "Too Many Requests",
    500: "Internal Server Error",
    502: "Bad Gateway",
    503: "Service Unavailable",
    # Add new codes here
}

STATUS_CODE_MESSAGES = {
    400: "The request could not be processed due to invalid data.",
    # ... etc
}

Best Practices Summary

DO:

Use specific exception classes for different error scenarios Provide clear, user-friendly error messages Include relevant details in the details dictionary Use consistent error codes across the application Test both API and HTML responses Keep error templates simple and accessible Use debug mode responsibly (admin only) Follow the template inheritance pattern Document any new exception types Test error pages manually in browsers

DON'T:

Expose sensitive information in error messages Use generic exceptions for domain-specific errors Return HTML for API endpoints Skip the Accept header check Hardcode HTML in exception handlers Forget to add fallback templates Show technical details to customers Use complex JavaScript in error pages Forget to test 401 redirects Mix API and page response logic


Support and Questions

For questions or issues with the error handling system:

  1. Check Logs: Look for [CONTEXT] and [ERROR] log messages
  2. Review This Documentation: Most issues are covered in Troubleshooting
  3. Check Implementation Status: Verify which phase is complete
  4. Test Locally: Reproduce the issue with curl/browser
  5. Contact: [Your team contact information]

Document Version: 1.0.0 Last Updated: 2025 Maintained By: [Your team name] Next Review: After Phase 2 & 3 completion