Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:51:46 +08:00
commit 00486a9b97
66 changed files with 29954 additions and 0 deletions

View File

@@ -0,0 +1,278 @@
---
name: structured-errors
description: 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
```python
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
```python
# ❌ 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
```python
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
```python
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
```python
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
```python
# ❌ 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
## Related Skills
- docstring-format - Document Raises section
- pii-redaction - Redact PII in error logs
- pydantic-models - For error response models