✨ Features: - Comprehensive Pydantic response models with examples - Enhanced FastAPI configuration with rich OpenAPI metadata - Centralized error handling with standardized error codes - Professional Swagger UI styling and branding - Health check endpoints with detailed component status - Type-safe request/response models for all endpoints 📊 Coverage: - Agent Management API fully documented - Standardized error responses across all endpoints - Interactive API documentation with try-it-now functionality - Custom OpenAPI schema with authentication schemes 🛠️ Technical Improvements: - Created app/models/responses.py with comprehensive models - Added app/core/error_handlers.py for centralized error handling - Enhanced app/api/agents.py with detailed documentation - Custom documentation configuration in app/docs_config.py - Global exception handlers for consistent error responses 🌐 Access Points: - Swagger UI: /docs - ReDoc: /redoc - OpenAPI JSON: /openapi.json This establishes professional-grade API documentation that matches Hive's technical excellence and provides developers with comprehensive, interactive documentation for efficient integration. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
296 lines
8.9 KiB
Python
296 lines
8.9 KiB
Python
"""
|
|
Centralized Error Handling for Hive API
|
|
|
|
This module provides standardized error handling, response formatting,
|
|
and HTTP status code management across all API endpoints.
|
|
|
|
Features:
|
|
- Consistent error response format
|
|
- Proper HTTP status code mapping
|
|
- Detailed error logging
|
|
- Security-aware error messages
|
|
- OpenAPI documentation integration
|
|
"""
|
|
|
|
from fastapi import HTTPException, Request, status
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.exceptions import RequestValidationError
|
|
from pydantic import ValidationError
|
|
from typing import Dict, Any, Optional
|
|
import logging
|
|
import traceback
|
|
from datetime import datetime
|
|
|
|
from ..models.responses import ErrorResponse
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class HiveAPIException(HTTPException):
|
|
"""
|
|
Custom exception class for Hive API with enhanced error details.
|
|
|
|
Extends FastAPI's HTTPException with additional context and
|
|
standardized error formatting.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
status_code: int,
|
|
detail: str,
|
|
error_code: Optional[str] = None,
|
|
details: Optional[Dict[str, Any]] = None,
|
|
headers: Optional[Dict[str, str]] = None
|
|
):
|
|
super().__init__(status_code=status_code, detail=detail, headers=headers)
|
|
self.error_code = error_code
|
|
self.details = details or {}
|
|
|
|
|
|
# Standard error codes
|
|
class ErrorCodes:
|
|
"""Standard error codes used across the Hive API"""
|
|
|
|
# Authentication & Authorization
|
|
INVALID_CREDENTIALS = "INVALID_CREDENTIALS"
|
|
TOKEN_EXPIRED = "TOKEN_EXPIRED"
|
|
INSUFFICIENT_PERMISSIONS = "INSUFFICIENT_PERMISSIONS"
|
|
|
|
# Agent Management
|
|
AGENT_NOT_FOUND = "AGENT_NOT_FOUND"
|
|
AGENT_ALREADY_EXISTS = "AGENT_ALREADY_EXISTS"
|
|
AGENT_UNREACHABLE = "AGENT_UNREACHABLE"
|
|
AGENT_BUSY = "AGENT_BUSY"
|
|
INVALID_AGENT_CONFIG = "INVALID_AGENT_CONFIG"
|
|
|
|
# Task Management
|
|
TASK_NOT_FOUND = "TASK_NOT_FOUND"
|
|
TASK_ALREADY_COMPLETED = "TASK_ALREADY_COMPLETED"
|
|
TASK_EXECUTION_FAILED = "TASK_EXECUTION_FAILED"
|
|
INVALID_TASK_CONFIG = "INVALID_TASK_CONFIG"
|
|
|
|
# Workflow Management
|
|
WORKFLOW_NOT_FOUND = "WORKFLOW_NOT_FOUND"
|
|
WORKFLOW_EXECUTION_FAILED = "WORKFLOW_EXECUTION_FAILED"
|
|
INVALID_WORKFLOW_CONFIG = "INVALID_WORKFLOW_CONFIG"
|
|
|
|
# System Errors
|
|
SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE"
|
|
DATABASE_ERROR = "DATABASE_ERROR"
|
|
COORDINATOR_ERROR = "COORDINATOR_ERROR"
|
|
VALIDATION_ERROR = "VALIDATION_ERROR"
|
|
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
|
|
|
|
# Common HTTP exceptions with proper error codes
|
|
def agent_not_found_error(agent_id: str) -> HiveAPIException:
|
|
"""Standard agent not found error"""
|
|
return HiveAPIException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Agent with ID '{agent_id}' not found",
|
|
error_code=ErrorCodes.AGENT_NOT_FOUND,
|
|
details={"agent_id": agent_id}
|
|
)
|
|
|
|
|
|
def agent_already_exists_error(agent_id: str) -> HiveAPIException:
|
|
"""Standard agent already exists error"""
|
|
return HiveAPIException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail=f"Agent with ID '{agent_id}' already exists",
|
|
error_code=ErrorCodes.AGENT_ALREADY_EXISTS,
|
|
details={"agent_id": agent_id}
|
|
)
|
|
|
|
|
|
def task_not_found_error(task_id: str) -> HiveAPIException:
|
|
"""Standard task not found error"""
|
|
return HiveAPIException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Task with ID '{task_id}' not found",
|
|
error_code=ErrorCodes.TASK_NOT_FOUND,
|
|
details={"task_id": task_id}
|
|
)
|
|
|
|
|
|
def coordinator_unavailable_error() -> HiveAPIException:
|
|
"""Standard coordinator unavailable error"""
|
|
return HiveAPIException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="Coordinator service is currently unavailable",
|
|
error_code=ErrorCodes.SERVICE_UNAVAILABLE,
|
|
details={"service": "coordinator"}
|
|
)
|
|
|
|
|
|
def database_error(operation: str, details: Optional[str] = None) -> HiveAPIException:
|
|
"""Standard database error"""
|
|
return HiveAPIException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Database operation failed: {operation}",
|
|
error_code=ErrorCodes.DATABASE_ERROR,
|
|
details={"operation": operation, "details": details}
|
|
)
|
|
|
|
|
|
def validation_error(field: str, message: str) -> HiveAPIException:
|
|
"""Standard validation error"""
|
|
return HiveAPIException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Validation failed for field '{field}': {message}",
|
|
error_code=ErrorCodes.VALIDATION_ERROR,
|
|
details={"field": field, "validation_message": message}
|
|
)
|
|
|
|
|
|
# Global exception handlers
|
|
async def hive_exception_handler(request: Request, exc: HiveAPIException) -> JSONResponse:
|
|
"""
|
|
Global exception handler for HiveAPIException.
|
|
|
|
Converts HiveAPIException to properly formatted JSON response
|
|
with standardized error structure.
|
|
"""
|
|
logger.error(
|
|
f"HiveAPIException: {exc.status_code} - {exc.detail}",
|
|
extra={
|
|
"error_code": exc.error_code,
|
|
"details": exc.details,
|
|
"path": request.url.path,
|
|
"method": request.method
|
|
}
|
|
)
|
|
|
|
error_response = ErrorResponse(
|
|
message=exc.detail,
|
|
error_code=exc.error_code,
|
|
details=exc.details
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content=error_response.dict(),
|
|
headers=exc.headers
|
|
)
|
|
|
|
|
|
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
|
"""
|
|
Global exception handler for Pydantic validation errors.
|
|
|
|
Converts validation errors to standardized error responses
|
|
with detailed field-level error information.
|
|
"""
|
|
logger.warning(
|
|
f"Validation error: {exc}",
|
|
extra={
|
|
"path": request.url.path,
|
|
"method": request.method,
|
|
"errors": exc.errors()
|
|
}
|
|
)
|
|
|
|
# Extract validation details
|
|
validation_details = []
|
|
for error in exc.errors():
|
|
validation_details.append({
|
|
"field": ".".join(str(x) for x in error["loc"]),
|
|
"message": error["msg"],
|
|
"type": error["type"]
|
|
})
|
|
|
|
error_response = ErrorResponse(
|
|
message="Request validation failed",
|
|
error_code=ErrorCodes.VALIDATION_ERROR,
|
|
details={
|
|
"validation_errors": validation_details,
|
|
"total_errors": len(validation_details)
|
|
}
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
content=error_response.dict()
|
|
)
|
|
|
|
|
|
async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
"""
|
|
Global exception handler for unexpected errors.
|
|
|
|
Provides safe error responses for unexpected exceptions
|
|
while logging full details for debugging.
|
|
"""
|
|
# Log full traceback for debugging
|
|
logger.error(
|
|
f"Unexpected error: {type(exc).__name__}: {str(exc)}",
|
|
extra={
|
|
"path": request.url.path,
|
|
"method": request.method,
|
|
"traceback": traceback.format_exc()
|
|
}
|
|
)
|
|
|
|
# Return generic error message to avoid information leakage
|
|
error_response = ErrorResponse(
|
|
message="An unexpected error occurred. Please try again or contact support.",
|
|
error_code=ErrorCodes.INTERNAL_ERROR,
|
|
details={"error_type": type(exc).__name__}
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
content=error_response.dict()
|
|
)
|
|
|
|
|
|
# Health check utilities
|
|
def create_health_response(
|
|
status: str = "healthy",
|
|
version: str = "1.1.0",
|
|
components: Optional[Dict[str, Any]] = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Create standardized health check response.
|
|
|
|
Args:
|
|
status: Overall system health status
|
|
version: API version
|
|
components: Optional component-specific health details
|
|
|
|
Returns:
|
|
Dict containing standardized health check response
|
|
"""
|
|
return {
|
|
"status": status,
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"version": version,
|
|
"components": components or {}
|
|
}
|
|
|
|
|
|
def check_component_health(component_name: str, check_function) -> Dict[str, Any]:
|
|
"""
|
|
Standardized component health check wrapper.
|
|
|
|
Args:
|
|
component_name: Name of the component being checked
|
|
check_function: Function that performs the health check
|
|
|
|
Returns:
|
|
Dict containing component health status
|
|
"""
|
|
try:
|
|
result = check_function()
|
|
return {
|
|
"status": "healthy",
|
|
"details": result,
|
|
"last_check": datetime.utcnow().isoformat()
|
|
}
|
|
except Exception as e:
|
|
logger.warning(f"Health check failed for {component_name}: {e}")
|
|
return {
|
|
"status": "unhealthy",
|
|
"error": str(e),
|
|
"last_check": datetime.utcnow().isoformat()
|
|
} |