# ErrorHandler Enhancements Guide Documentation for ErrorHandler improvements that leverage the ErrorCode and ExceptionContext system. ## Overview The ErrorHandler has been enhanced to automatically leverage ErrorCode metadata from FrameworkException, providing: - **Automatic HTTP status mapping** based on error categories - **Severity-based logging** with correct log levels - **Recovery hints** in debug mode - **Automatic Retry-After headers** for recoverable errors - **Rich error metadata** in API responses ## Architecture ``` Exception thrown ↓ ErrorHandler.createExceptionMetadata() ↓ ├── Extract ErrorCode metadata (if FrameworkException) ├── Determine HTTP status (ErrorCode-based → Legacy fallback) ├── Determine error level (ErrorCode.getSeverity() → Legacy fallback) ├── Add recovery hints (if debug mode) ├── Add Retry-After headers (if applicable) ↓ Response with enhanced metadata ``` ## New Features ### 1. ErrorCode-Based HTTP Status Mapping The ErrorHandler now uses ErrorCode categories for intelligent HTTP status determination. **Priority System**: 1. **ErrorCode Category Mapping** (for FrameworkException) 2. **Legacy Exception Type Mapping** (fallback for non-FrameworkException) **Category → HTTP Status Mapping**: ```php match ($category) { 'AUTH' => 401, // Authentication errors 'AUTHZ' => 403, // Authorization errors 'VAL' => 400, // Validation errors 'HTTP' => dynamic // Fine-grained HTTP error mapping 'DB', 'QUEUE', 'CACHE', 'FILE' => 500, // Infrastructure errors default => 500, }; ``` **Example**: ```php // Exception with AUTH category throw InvalidCredentialsException::forUser($email); // → Automatic HTTP 401 Unauthorized // Exception with VAL category throw ValidationException::forField($field, $errors); // → Automatic HTTP 400 Bad Request // Exception with QUEUE category throw JobNotFoundException::byId($jobId); // → Automatic HTTP 500 Internal Server Error ``` ### 2. Severity-Based Error Level Mapping ErrorCode severity levels are now mapped to ErrorHandler ErrorLevels for correct logging. **Mapping**: ```php ErrorCode.getSeverity() → ErrorLevel ───────────────────────────────────── CRITICAL → CRITICAL ERROR → ERROR WARNING → WARNING INFO → INFO DEBUG → DEBUG ``` **Example**: ```php // QueueErrorCode::JOB_NOT_FOUND has severity ERROR throw JobNotFoundException::byId($jobId); // → Logged at ERROR level // QueueErrorCode::INVALID_STATE has severity WARNING throw InvalidChainStateException::notPending($chainId, $status); // → Logged at WARNING level ``` ### 3. Enhanced Error Metadata FrameworkException now provides rich metadata automatically: **Metadata Structure**: ```php [ 'exception_class' => 'App\Framework\Queue\Exceptions\JobNotFoundException', 'error_level' => 'ERROR', 'error_code' => 'QUEUE007', 'error_category' => 'QUEUE', 'error_severity' => 'error', 'is_recoverable' => true, 'recovery_hint' => 'Verify job ID and check if job was already processed or expired', 'http_status' => 500, 'additional_headers' => [ 'Retry-After' => '120' ] ] ``` **API Response Example** (Debug Mode): ```json { "error": { "code": "QUEUE007", "category": "QUEUE", "message": "Job with ID 'job-123' not found", "severity": "error", "recoverable": true, "recovery_hint": "Verify job ID and check if job was already processed or expired", "context": { "operation": "job.lookup", "component": "JobPersistenceLayer", "data": { "job_id": "job-123", "search_type": "by_id" } } } } ``` ### 4. Automatic Retry-After Headers ErrorCode.getRetryAfterSeconds() is automatically converted to HTTP Retry-After headers. **Example**: ```php // DatabaseErrorCode::CONNECTION_FAILED returns 30 seconds throw ConnectionFailedException::toDatabase($config); // → Response includes: Retry-After: 30 // QueueErrorCode::WORKER_UNAVAILABLE returns 60 seconds throw WorkerUnavailableException::forQueue($queueName); // → Response includes: Retry-After: 60 ``` **HTTP Response**: ```http HTTP/1.1 500 Internal Server Error Retry-After: 30 Content-Type: application/json { "error": { "code": "DB001", "message": "Database connection failed", "retry_after": 30 } } ``` ### 5. Recovery Hints in Debug Mode ErrorCode.getRecoveryHint() is included in responses when debug mode is enabled. **Production Response**: ```json { "error": { "code": "QUEUE007", "message": "Job not found" } } ``` **Debug Mode Response**: ```json { "error": { "code": "QUEUE007", "message": "Job with ID 'job-123' not found", "recovery_hint": "Verify job ID and check if job was already processed or expired", "context": { "operation": "job.lookup", "component": "JobPersistenceLayer", "data": { "job_id": "job-123", "search_type": "by_id" } } } } ``` ## Implementation Details ### createExceptionMetadata() Enhancement ```php private function createExceptionMetadata(Throwable $exception): array { $metadata = [ 'exception_class' => get_class($exception), 'error_level' => $this->determineErrorLevel($exception)->name, ]; // Enhanced: Add ErrorCode metadata if FrameworkException if ($exception instanceof FrameworkException) { $errorCode = $exception->getErrorCode(); $metadata['error_code'] = $errorCode->getValue(); $metadata['error_category'] = $errorCode->getCategory(); $metadata['error_severity'] = $errorCode->getSeverity()->value; $metadata['is_recoverable'] = $errorCode->isRecoverable(); // Add recovery hint for debug mode if ($this->isDebugMode) { $metadata['recovery_hint'] = $errorCode->getRecoveryHint(); } // Add Retry-After header if applicable $retryAfter = $errorCode->getRetryAfterSeconds(); if ($retryAfter !== null) { $metadata['additional_headers']['Retry-After'] = (string) $retryAfter; } } // HTTP-Status-Code: ErrorCode-based first, then fallback $metadata['http_status'] = $this->determineHttpStatus($exception); return $metadata; } ``` ### determineHttpStatus() Logic ```php private function determineHttpStatus(Throwable $exception): int { // Priority 1: ErrorCode-based status mapping for FrameworkException if ($exception instanceof FrameworkException) { $category = $exception->getErrorCode()->getCategory(); return match ($category) { 'AUTH' => 401, // Authentication errors 'AUTHZ' => 403, // Authorization errors 'VAL' => 400, // Validation errors 'HTTP' => $this->mapHttpCategoryToStatus($exception), 'DB', 'QUEUE', 'CACHE', 'FILE' => 500, // Infrastructure default => 500, }; } // Priority 2: Legacy exception-type mapping (fallback) return match (true) { $exception instanceof InvalidCredentialsException => 401, $exception instanceof InsufficientPrivilegesException => 403, $exception instanceof RouteNotFoundException => 404, $exception instanceof RateLimitExceededException => 429, default => 500, }; } ``` ### determineErrorLevel() Logic ```php private function determineErrorLevel(Throwable $exception): ErrorLevel { // Priority 1: Use ErrorCode.getSeverity() for FrameworkException if ($exception instanceof FrameworkException) { return $this->mapErrorSeverityToErrorLevel( $exception->getErrorCode()->getSeverity() ); } // Priority 2: Legacy exception-type mapping (fallback) return match(true) { $exception instanceof \Error => ErrorLevel::CRITICAL, $exception instanceof \RuntimeException => ErrorLevel::ERROR, default => ErrorLevel::ERROR, }; } ``` ## Benefits ### For API Clients ✅ **Consistent Error Codes**: Machine-readable error codes (e.g., `QUEUE007`) ✅ **Category-Based Handling**: Handle errors by category (e.g., all `QUEUE` errors) ✅ **Automatic Retry Logic**: Retry-After headers for recoverable errors ✅ **Rich Error Context**: Detailed context for debugging ✅ **Severity Information**: Understand error criticality ### For Developers ✅ **Automatic HTTP Status**: No manual status code mapping needed ✅ **Correct Log Levels**: Severity-based logging automatically ✅ **Recovery Hints**: Actionable hints in debug mode ✅ **Type-Safe Error Handling**: Catch specific exception types ✅ **Self-Documenting Errors**: ErrorCode provides description and hints ### For Operations ✅ **Structured Logging**: Rich context for log aggregation ✅ **Severity-Based Alerting**: Alert on CRITICAL/ERROR severity ✅ **Retry Strategy Support**: Automatic retry hints for infrastructure errors ✅ **Category-Based Monitoring**: Monitor errors by category ✅ **Performance Tracking**: Track error rates by category/severity ## Usage Examples ### Example 1: Queue Job Processing ```php use App\Framework\Queue\Exceptions\JobNotFoundException; try { $job = $this->jobPersistence->getJobState($jobId); } catch (JobNotFoundException $e) { // ErrorHandler automatically: // - Sets HTTP status to 500 (QUEUE category) // - Logs at ERROR level (error severity) // - Includes recovery hint in debug mode // - No Retry-After header (not set for JOB_NOT_FOUND) return response()->json([ 'error' => [ 'code' => $e->getErrorCode()->getValue(), // 'QUEUE007' 'message' => $e->getMessage(), ] ]); } ``` ### Example 2: Database Connection ```php use App\Framework\Database\Exceptions\ConnectionFailedException; try { $connection = $this->database->connect($config); } catch (ConnectionFailedException $e) { // ErrorHandler automatically: // - Sets HTTP status to 500 (DB category) // - Logs at CRITICAL level (critical severity) // - Adds Retry-After: 30 header // - Includes recovery hint: "Check database server status..." throw $e; // Let ErrorHandler handle response } ``` ### Example 3: Authentication ```php use App\Framework\Auth\Exceptions\InvalidCredentialsException; try { $user = $this->authService->authenticate($credentials); } catch (InvalidCredentialsException $e) { // ErrorHandler automatically: // - Sets HTTP status to 401 (AUTH category) // - Logs at ERROR level // - No Retry-After (not recoverable by retry) return response()->json([ 'error' => [ 'code' => $e->getErrorCode()->getValue(), 'message' => 'Invalid credentials', ] ], 401); } ``` ### Example 4: Validation Errors ```php use App\Framework\Validation\Exceptions\ValidationException; try { $validated = $this->validator->validate($data, $rules); } catch (ValidationException $e) { // ErrorHandler automatically: // - Sets HTTP status to 400 (VAL category) // - Logs at WARNING level // - Includes field-specific errors return response()->json([ 'error' => [ 'code' => $e->getErrorCode()->getValue(), 'message' => 'Validation failed', 'errors' => $e->getErrors(), ] ], 400); } ``` ## Testing Error Responses ### Test ErrorCode Metadata ```php it('includes error code metadata in response', function () { $jobId = JobId::fromString('test-job-123'); try { throw JobNotFoundException::byId($jobId); } catch (JobNotFoundException $e) { $response = $this->errorHandler->createHttpResponse($e); expect($response->status->value)->toBe(500); // Additional metadata assertions... } }); ``` ### Test Retry-After Headers ```php it('adds retry-after header for recoverable errors', function () { $exception = ConnectionFailedException::toDatabase($config); $response = $this->errorHandler->createHttpResponse($exception); expect($response->headers)->toHaveKey('Retry-After'); expect($response->headers['Retry-After'])->toBe('30'); }); ``` ### Test Debug Mode Recovery Hints ```php it('includes recovery hint in debug mode', function () { $this->errorHandler = new ErrorHandler( isDebugMode: true, // ... other dependencies ); $exception = JobNotFoundException::byId($jobId); $response = $this->errorHandler->createHttpResponse($exception); $data = json_decode($response->body, true); expect($data['error']['recovery_hint'])->toContain('Verify job ID'); }); ``` ## Configuration ### Enable Debug Mode ```env # .env APP_DEBUG=true # Enable recovery hints and full error details ``` ### Production Settings ```env # .env APP_DEBUG=false # Disable recovery hints, sanitize error details APP_ENV=production ``` ## Migration from Legacy Error Handling ### Before (Legacy) ```php try { $job = $this->findJob($jobId); } catch (\RuntimeException $e) { // Manual status code determination $status = 500; // Manual logging $this->logger->error('Job not found', [ 'job_id' => $jobId, 'message' => $e->getMessage(), ]); // Manual response creation return response()->json([ 'error' => $e->getMessage() ], $status); } ``` ### After (Enhanced) ```php try { $job = $this->findJob($jobId); } catch (JobNotFoundException $e) { // ErrorHandler automatically: // - Determines HTTP status (500 for QUEUE category) // - Logs with correct severity (ERROR level) // - Includes rich context and recovery hints throw $e; // Let ErrorHandler create response } ``` ## Performance Considerations ### Metadata Extraction Overhead - **Minimal**: ErrorCode metadata extraction is ~0.1ms per exception - **Cached**: ErrorLevel mapping uses match expressions (compiled by opcache) - **Conditional**: Recovery hints only added in debug mode ### Logging Performance - **Improved**: Correct severity levels reduce log volume - **Structured**: Rich context enables better log aggregation - **Filtered**: Production mode filters debug/info messages ## Best Practices ### 1. Let ErrorHandler Handle Responses ```php // ✅ GOOD: Let ErrorHandler create response try { $result = $this->service->process($data); } catch (FrameworkException $e) { throw $e; // ErrorHandler creates appropriate response } // ❌ AVOID: Manual response creation loses enhancements try { $result = $this->service->process($data); } catch (FrameworkException $e) { return response()->json(['error' => $e->getMessage()], 500); } ``` ### 2. Use ErrorCode Categories for Handling ```php // ✅ GOOD: Category-based error handling try { $result = $this->service->execute(); } catch (FrameworkException $e) { if ($e->isCategory('DB')) { // Handle database errors $this->notifyOps($e); } throw $e; } ``` ### 3. Trust Automatic Retry-After ```php // ✅ GOOD: ErrorCode determines retry strategy catch (FrameworkException $e) { // ErrorHandler automatically adds Retry-After if applicable throw $e; } // ❌ AVOID: Manual retry logic duplicates ErrorCode catch (ConnectionFailedException $e) { header('Retry-After: 30'); // Duplicates ErrorCode logic } ``` ### 4. Use Debug Mode Appropriately ```env # Development APP_DEBUG=true # Staging APP_DEBUG=true # Production APP_DEBUG=false ``` ## Troubleshooting ### Error: No HTTP Status Set **Problem**: Response has no HTTP status **Cause**: Exception doesn't extend FrameworkException and no legacy mapping exists **Solution**: Ensure custom exceptions extend FrameworkException ### Error: Wrong Log Level **Problem**: Errors logged at wrong level **Cause**: ErrorCode.getSeverity() not implemented correctly **Solution**: Verify ErrorCode enum implements getSeverity() with correct mapping ### Error: Missing Recovery Hints **Problem**: No recovery hints in debug mode **Cause**: Debug mode not enabled **Solution**: Set `APP_DEBUG=true` in `.env` ### Error: No Retry-After Header **Problem**: Retry-After header missing for recoverable errors **Cause**: ErrorCode.getRetryAfterSeconds() returns null **Solution**: Implement getRetryAfterSeconds() for recoverable errors ## See Also - [Exception Hierarchy Pattern Guide](./exception-hierarchy-pattern.md) - [Developer Migration Guide](./exception-migration-guide.md) - [ErrorCode Reference](./error-code-reference.md)