Files
2025-11-30 08:51:46 +08:00

7.4 KiB

name, description
name description
structured-errors Automatically applies when writing error handling in APIs and tools. Ensures errors are returned as structured JSON with error, request_id, and timestamp (not plain strings).

Structured Error Response Enforcer

When writing error handling in API endpoints, tools, or services, always return structured JSON errors.

Correct Pattern

import uuid
from datetime import datetime
import json

def create_error_response(
    error_message: str,
    status_code: int = 500,
    details: dict = None
) -> dict:
    """Create structured error response."""
    response = {
        "error": error_message,
        "request_id": str(uuid.uuid4()),
        "timestamp": datetime.now().isoformat(),
        "status_code": status_code
    }
    if details:
        response["details"] = details
    return response

# In API endpoint
@app.get("/users/{user_id}")
async def get_user(user_id: str):
    if not user_id:
        raise HTTPException(
            status_code=400,
            detail=create_error_response(
                "User ID is required",
                status_code=400
            )
        )

    try:
        user = await fetch_user(user_id)
        return user
    except UserNotFoundError:
        raise HTTPException(
            status_code=404,
            detail=create_error_response(
                "User not found",
                status_code=404,
                details={"user_id": user_id}
            )
        )

# In tool function
def process_data(data: str) -> str:
    if not data:
        return json.dumps(create_error_response(
            "Data is required",
            status_code=400
        ))

    try:
        result = parse_data(data)
        return json.dumps({"result": result})
    except Exception as e:
        return json.dumps(create_error_response(
            "Failed to process data",
            status_code=500,
            details={"error_type": type(e).__name__}
        ))

Incorrect Pattern

# ❌ Plain string
if not user_id:
    return "Error: user ID is required"

# ❌ Exposing sensitive data
except httpx.HTTPStatusError as e:
    return f"Error: {e.response.text}"  # ❌ Leaks API response

# ❌ No request ID for tracing
return {"error": "Something went wrong"}

# ❌ Inconsistent error format
return "Error: failed"  # Sometimes string
return {"message": "failed"}  # Sometimes object

Required Fields

All error responses must include:

  1. error (string) - Human-readable error message
  2. request_id (uuid) - For tracing/debugging
  3. timestamp (ISO string) - When error occurred
  4. status_code (int) - HTTP status code

Optional but recommended: 5. details (object) - Additional context (safe data only) 6. error_code (string) - Application-specific error code 7. field (string) - Which field caused the error (validation)

Custom Exception Classes

class APIError(Exception):
    """Base exception for API errors."""

    def __init__(self, message: str, status_code: int = 500, details: dict = None):
        self.message = message
        self.status_code = status_code
        self.details = details or {}
        self.request_id = str(uuid.uuid4())
        self.timestamp = datetime.now().isoformat()
        super().__init__(self.message)

    def to_dict(self) -> dict:
        """Convert to structured error response."""
        return {
            "error": self.message,
            "status_code": self.status_code,
            "request_id": self.request_id,
            "timestamp": self.timestamp,
            "details": self.details
        }

class ValidationError(APIError):
    """Validation error."""
    def __init__(self, message: str, field: str = None):
        details = {"field": field} if field else {}
        super().__init__(message, status_code=400, details=details)

class NotFoundError(APIError):
    """Resource not found."""
    def __init__(self, resource: str, resource_id: str):
        super().__init__(
            f"{resource} not found",
            status_code=404,
            details={"resource": resource, "id": resource_id}
        )

# Usage
try:
    user = await get_user(user_id)
except UserNotFoundError:
    raise NotFoundError("User", user_id)

FastAPI Integration

from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(APIError)
async def api_error_handler(request: Request, exc: APIError):
    """Global exception handler for APIError."""
    return JSONResponse(
        status_code=exc.status_code,
        content=exc.to_dict()
    )

@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
    """Catch-all for unexpected errors."""
    error_response = create_error_response(
        "Internal server error",
        status_code=500,
        details={"type": type(exc).__name__}
    )
    # Log full error but don't expose to client
    logger.error(f"Unexpected error: {exc}", exc_info=True)
    return JSONResponse(status_code=500, content=error_response)

Security Note

Never include in errors:

  • Raw API response text
  • Stack traces (log them, don't return them)
  • Internal IDs or tokens
  • Database query details
  • Full exception messages from external APIs
  • Authentication credentials
  • File paths or system information

Safe to include:

  • User-facing error messages
  • Validation field names
  • Request IDs for support
  • Status codes
  • Generic error types

Logging Errors

import logging

logger = logging.getLogger(__name__)

def handle_error(error: Exception, context: dict = None) -> dict:
    """Handle error with proper logging and structured response."""
    request_id = str(uuid.uuid4())

    # Log with context
    logger.error(
        f"Error occurred | request_id={request_id} | "
        f"error={type(error).__name__}",
        extra=context,
        exc_info=True
    )

    # Return safe error response
    return create_error_response(
        "An error occurred processing your request",
        status_code=500,
        details={"request_id": request_id}
    )

Anti-Patterns

# ❌ Leaking internal errors
return {"error": str(exception)}

# ❌ Inconsistent format across endpoints
def endpoint1(): return "Error"
def endpoint2(): return {"error": "Error"}

# ❌ No context for debugging
return {"error": "Failed"}

# ❌ Returning 200 with error in body
return {"success": False, "error": "..."}  # Should use proper status code

# ❌ Too much information
return {
    "error": f"Database query failed: {full_sql_query}",
    "stacktrace": traceback.format_exc()
}

Best Practices Checklist

  • Use consistent error structure across application
  • Include request_id for tracing
  • Include timestamp for debugging
  • Use proper HTTP status codes
  • Create custom exception classes for common errors
  • Add global exception handlers
  • Log full error details (with context)
  • Return safe, user-friendly messages
  • Never expose sensitive data in errors
  • Document error responses in API docs

Auto-Apply

When you write error handling:

  1. Create helper create_error_response()
  2. Use it for all error returns
  3. Include request_id and timestamp
  4. Remove any sensitive data
  5. Use proper status codes
  • docstring-format - Document Raises section
  • pii-redaction - Redact PII in error logs
  • pydantic-models - For error response models