MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Refacto.../ErrorHandling.cs

333 lines
11 KiB
C#
Executable File

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<string, object> 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<string, object>();
}
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<ErrorHandlingService> _logger;
public ErrorHandlingService(ILogger<ErrorHandlingService> 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<string, object>
{
["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<string, object>
{
["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<string, object>
{
["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<bool> 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<bool> 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<IErrorRecoveryStrategy> _recoveryStrategies;
public CentralizedErrorHandler(IErrorHandlingService errorService)
{
_errorService = errorService;
_recoveryStrategies = new List<IErrorRecoveryStrategy>
{
new FileAccessRecoveryStrategy()
};
}
public void AddRecoveryStrategy(IErrorRecoveryStrategy strategy)
{
_recoveryStrategies.Add(strategy);
}
public async Task<AIPluginResult> 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<CentralizedErrorHandler> _instance = new Lazy<CentralizedErrorHandler>(
() => new CentralizedErrorHandler(new ErrorHandlingService()));
public static CentralizedErrorHandler Instance => _instance.Value;
}
}