using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; namespace MarketAlly.AIPlugin.Refactoring.Plugins { // Unified exception hierarchy public class RefactoringException : Exception { public string PluginName { get; } public string Operation { get; } public RefactoringErrorCode ErrorCode { get; } public Dictionary Context { get; } public RefactoringException(string pluginName, string operation, RefactoringErrorCode errorCode, string message, Exception innerException = null) : base($"[{pluginName}] {operation}: {message}", innerException) { PluginName = pluginName; Operation = operation; ErrorCode = errorCode; Context = new Dictionary(); } public RefactoringException AddContext(string key, object value) { Context[key] = value; return this; } } public enum RefactoringErrorCode { // General errors (1000-1999) Unknown = 1000, InvalidInput = 1001, InvalidConfiguration = 1002, OperationCancelled = 1003, TimeoutExceeded = 1004, // File system errors (2000-2999) FileNotFound = 2000, FileAccessDenied = 2001, FileInUse = 2002, DirectoryNotFound = 2003, InvalidFilePath = 2004, FileTooLarge = 2005, // Code analysis errors (3000-3999) ParseError = 3000, SyntaxError = 3001, SemanticError = 3002, UnsupportedLanguageFeature = 3003, ComplexityTooHigh = 3004, // Git operation errors (4000-4999) GitRepositoryNotFound = 4000, GitOperationFailed = 4001, GitConflict = 4002, GitUncommittedChanges = 4003, // Network/API errors (5000-5999) NetworkError = 5000, ApiKeyInvalid = 5001, ApiRateLimitExceeded = 5002, ApiResponseError = 5003, // Security errors (6000-6999) SecurityViolation = 6000, UnauthorizedAccess = 6001, CommandInjectionAttempt = 6002, PathTraversalAttempt = 6003 } // Error handling service interface public interface IErrorHandlingService { RefactoringException CreateException(string pluginName, string operation, RefactoringErrorCode errorCode, string message, Exception innerException = null); void LogError(RefactoringException exception, ILogger logger = null); void LogWarning(string pluginName, string operation, string message, ILogger logger = null); void LogInfo(string pluginName, string operation, string message, ILogger logger = null); AIPluginResult CreateErrorResult(RefactoringException exception); AIPluginResult CreateErrorResult(string pluginName, string operation, RefactoringErrorCode errorCode, string message, Exception innerException = null); } // Concrete error handling service public class ErrorHandlingService : IErrorHandlingService { private readonly ILogger _logger; public ErrorHandlingService(ILogger logger = null) { _logger = logger; } public RefactoringException CreateException(string pluginName, string operation, RefactoringErrorCode errorCode, string message, Exception innerException = null) { return new RefactoringException(pluginName, operation, errorCode, message, innerException); } public void LogError(RefactoringException exception, ILogger logger = null) { var loggerToUse = logger ?? _logger; if (loggerToUse != null) { using var scope = loggerToUse.BeginScope(new Dictionary { ["PluginName"] = exception.PluginName, ["Operation"] = exception.Operation, ["ErrorCode"] = exception.ErrorCode, ["Context"] = exception.Context }); loggerToUse.LogError(exception, "Refactoring operation failed: {Message}", exception.Message); } } public void LogWarning(string pluginName, string operation, string message, ILogger logger = null) { var loggerToUse = logger ?? _logger; if (loggerToUse != null) { using var scope = loggerToUse.BeginScope(new Dictionary { ["PluginName"] = pluginName, ["Operation"] = operation }); loggerToUse.LogWarning("Refactoring warning: {Message}", message); } } public void LogInfo(string pluginName, string operation, string message, ILogger logger = null) { var loggerToUse = logger ?? _logger; if (loggerToUse != null) { using var scope = loggerToUse.BeginScope(new Dictionary { ["PluginName"] = pluginName, ["Operation"] = operation }); loggerToUse.LogInformation("Refactoring info: {Message}", message); } } public AIPluginResult CreateErrorResult(RefactoringException exception) { LogError(exception); return new AIPluginResult(exception, JsonSerializer.Serialize(new { Error = true, ErrorCode = exception.ErrorCode, PluginName = exception.PluginName, Operation = exception.Operation, Message = exception.Message, Context = exception.Context, Timestamp = DateTime.UtcNow })); } public AIPluginResult CreateErrorResult(string pluginName, string operation, RefactoringErrorCode errorCode, string message, Exception innerException = null) { var exception = CreateException(pluginName, operation, errorCode, message, innerException); return CreateErrorResult(exception); } } // Extension methods for common exception scenarios public static class ErrorHandlingExtensions { public static RefactoringException FileNotFound(this IErrorHandlingService service, string pluginName, string operation, string filePath) { return service.CreateException(pluginName, operation, RefactoringErrorCode.FileNotFound, $"File not found: {filePath}") .AddContext("FilePath", filePath); } public static RefactoringException InvalidInput(this IErrorHandlingService service, string pluginName, string operation, string parameterName, string reason) { return service.CreateException(pluginName, operation, RefactoringErrorCode.InvalidInput, $"Invalid input for parameter '{parameterName}': {reason}") .AddContext("ParameterName", parameterName) .AddContext("Reason", reason); } public static RefactoringException ParseError(this IErrorHandlingService service, string pluginName, string operation, string filePath, Exception innerException) { return service.CreateException(pluginName, operation, RefactoringErrorCode.ParseError, $"Failed to parse file: {filePath}", innerException) .AddContext("FilePath", filePath); } public static RefactoringException GitOperationFailed(this IErrorHandlingService service, string pluginName, string operation, string gitCommand, Exception innerException) { return service.CreateException(pluginName, operation, RefactoringErrorCode.GitOperationFailed, $"Git operation failed: {gitCommand}", innerException) .AddContext("GitCommand", gitCommand); } public static RefactoringException SecurityViolation(this IErrorHandlingService service, string pluginName, string operation, string reason) { return service.CreateException(pluginName, operation, RefactoringErrorCode.SecurityViolation, $"Security violation: {reason}") .AddContext("SecurityReason", reason); } public static RefactoringException ApiError(this IErrorHandlingService service, string pluginName, string operation, RefactoringErrorCode errorCode, string apiName, Exception innerException) { return service.CreateException(pluginName, operation, errorCode, $"API operation failed: {apiName}", innerException) .AddContext("ApiName", apiName); } } // Error recovery strategies public interface IErrorRecoveryStrategy { bool CanRecover(RefactoringException exception); Task TryRecoverAsync(RefactoringException exception); } public class FileAccessRecoveryStrategy : IErrorRecoveryStrategy { private readonly int _maxRetries; private readonly int _retryDelayMs; public FileAccessRecoveryStrategy(int maxRetries = 3, int retryDelayMs = 1000) { _maxRetries = maxRetries; _retryDelayMs = retryDelayMs; } public bool CanRecover(RefactoringException exception) { return exception.ErrorCode == RefactoringErrorCode.FileInUse || exception.ErrorCode == RefactoringErrorCode.FileAccessDenied; } public async Task TryRecoverAsync(RefactoringException exception) { if (!CanRecover(exception)) return false; for (int attempt = 1; attempt <= _maxRetries; attempt++) { await Task.Delay(_retryDelayMs * attempt); // The actual recovery logic would depend on the specific operation // For now, just return false to indicate recovery failed // In practice, this would retry the file operation } return false; } } // Centralized error handler with recovery strategies public class CentralizedErrorHandler { private readonly IErrorHandlingService _errorService; private readonly List _recoveryStrategies; public CentralizedErrorHandler(IErrorHandlingService errorService) { _errorService = errorService; _recoveryStrategies = new List { new FileAccessRecoveryStrategy() }; } public void AddRecoveryStrategy(IErrorRecoveryStrategy strategy) { _recoveryStrategies.Add(strategy); } public async Task HandleErrorAsync(string pluginName, string operation, Exception exception) { RefactoringException refactoringException; if (exception is RefactoringException rex) { refactoringException = rex; } else { // Convert generic exception to RefactoringException var errorCode = DetermineErrorCode(exception); refactoringException = _errorService.CreateException(pluginName, operation, errorCode, exception.Message, exception); } // Try recovery strategies foreach (var strategy in _recoveryStrategies) { if (strategy.CanRecover(refactoringException)) { if (await strategy.TryRecoverAsync(refactoringException)) { _errorService.LogInfo(pluginName, operation, "Error recovered successfully"); return null; // Indicate recovery successful, operation can continue } } } // No recovery possible, return error result return _errorService.CreateErrorResult(refactoringException); } private RefactoringErrorCode DetermineErrorCode(Exception exception) { return exception switch { FileNotFoundException => RefactoringErrorCode.FileNotFound, DirectoryNotFoundException => RefactoringErrorCode.DirectoryNotFound, UnauthorizedAccessException => RefactoringErrorCode.FileAccessDenied, ArgumentException => RefactoringErrorCode.InvalidInput, TimeoutException => RefactoringErrorCode.TimeoutExceeded, OperationCanceledException => RefactoringErrorCode.OperationCancelled, _ => RefactoringErrorCode.Unknown }; } } // Global error handler instance public static class GlobalErrorHandler { private static readonly Lazy _instance = new Lazy( () => new CentralizedErrorHandler(new ErrorHandlingService())); public static CentralizedErrorHandler Instance => _instance.Value; } }