| | 1 | | // Copyright DotNet API Diff Project Contributors - SPDX Identifier: MIT |
| | 2 | | using System.Reflection; |
| | 3 | | using System.Security; |
| | 4 | | using DotNetApiDiff.Interfaces; |
| | 5 | | using DotNetApiDiff.Models; |
| | 6 | | using Microsoft.Extensions.Logging; |
| | 7 | |
|
| | 8 | | namespace DotNetApiDiff.ExitCodes |
| | 9 | | { |
| | 10 | | /// <summary> |
| | 11 | | /// Manages application exit codes based on comparison results. |
| | 12 | | /// </summary> |
| | 13 | | public class ExitCodeManager : IExitCodeManager |
| | 14 | | { |
| | 15 | | private readonly ILogger<ExitCodeManager>? _logger; |
| | 16 | |
|
| | 17 | | /// <summary> |
| | 18 | | /// Exit code for successful comparison with no breaking changes. |
| | 19 | | /// </summary> |
| | 20 | | public const int Success = 0; |
| | 21 | |
|
| | 22 | | /// <summary> |
| | 23 | | /// Exit code for successful comparison with breaking changes detected. |
| | 24 | | /// </summary> |
| | 25 | | public const int BreakingChangesDetected = 1; |
| | 26 | |
|
| | 27 | | /// <summary> |
| | 28 | | /// Exit code for errors during comparison. |
| | 29 | | /// </summary> |
| | 30 | | public const int ComparisonError = 2; |
| | 31 | |
|
| | 32 | | /// <summary> |
| | 33 | | /// Exit code for assembly loading failures. |
| | 34 | | /// </summary> |
| | 35 | | public const int AssemblyLoadError = 3; |
| | 36 | |
|
| | 37 | | /// <summary> |
| | 38 | | /// Exit code for configuration errors. |
| | 39 | | /// </summary> |
| | 40 | | public const int ConfigurationError = 4; |
| | 41 | |
|
| | 42 | | /// <summary> |
| | 43 | | /// Exit code for invalid command line arguments. |
| | 44 | | /// </summary> |
| | 45 | | public const int InvalidArguments = 5; |
| | 46 | |
|
| | 47 | | /// <summary> |
| | 48 | | /// Exit code for file not found errors. |
| | 49 | | /// </summary> |
| | 50 | | public const int FileNotFound = 6; |
| | 51 | |
|
| | 52 | | /// <summary> |
| | 53 | | /// Exit code for unexpected/unhandled errors. |
| | 54 | | /// </summary> |
| | 55 | | public const int UnexpectedError = 99; |
| | 56 | |
|
| | 57 | | /// <summary> |
| | 58 | | /// Initializes a new instance of the <see cref="ExitCodeManager"/> class. |
| | 59 | | /// </summary> |
| 39 | 60 | | public ExitCodeManager() |
| 39 | 61 | | { |
| 39 | 62 | | } |
| | 63 | |
|
| | 64 | | /// <summary> |
| | 65 | | /// Initializes a new instance of the <see cref="ExitCodeManager"/> class with a logger. |
| | 66 | | /// </summary> |
| | 67 | | /// <param name="logger">The logger to use for logging exit code information.</param> |
| 54 | 68 | | public ExitCodeManager(ILogger<ExitCodeManager> logger) |
| 54 | 69 | | { |
| 54 | 70 | | _logger = logger; |
| 54 | 71 | | } |
| | 72 | |
|
| | 73 | | /// <summary> |
| | 74 | | /// Determines the appropriate exit code based on comparison results. |
| | 75 | | /// </summary> |
| | 76 | | /// <param name="hasBreakingChanges">Whether breaking changes were detected.</param> |
| | 77 | | /// <param name="hasErrors">Whether errors occurred during comparison.</param> |
| | 78 | | /// <returns>The appropriate exit code.</returns> |
| | 79 | | public int GetExitCode(bool hasBreakingChanges, bool hasErrors) |
| 5 | 80 | | { |
| 5 | 81 | | if (hasErrors) |
| 2 | 82 | | { |
| 2 | 83 | | _logger?.LogWarning("Errors occurred during comparison, returning ComparisonError exit code"); |
| 2 | 84 | | return ComparisonError; |
| | 85 | | } |
| | 86 | |
|
| 3 | 87 | | if (hasBreakingChanges) |
| 2 | 88 | | { |
| 2 | 89 | | _logger?.LogWarning("Breaking changes detected, returning BreakingChangesDetected exit code"); |
| 2 | 90 | | return BreakingChangesDetected; |
| | 91 | | } |
| | 92 | |
|
| 1 | 93 | | _logger?.LogInformation("No breaking changes or errors detected, returning Success exit code"); |
| 1 | 94 | | return Success; |
| 5 | 95 | | } |
| | 96 | |
|
| | 97 | | /// <summary> |
| | 98 | | /// Determines the appropriate exit code based on a comparison result. |
| | 99 | | /// </summary> |
| | 100 | | /// <param name="comparisonResult">The comparison result to evaluate.</param> |
| | 101 | | /// <returns>The appropriate exit code.</returns> |
| | 102 | | public int GetExitCode(ComparisonResult comparisonResult) |
| 4 | 103 | | { |
| 4 | 104 | | if (comparisonResult == null) |
| 1 | 105 | | { |
| 1 | 106 | | _logger?.LogWarning("Comparison result is null, returning ComparisonError exit code"); |
| 1 | 107 | | return ComparisonError; |
| | 108 | | } |
| | 109 | |
|
| | 110 | | // Check for breaking changes |
| 3 | 111 | | if (comparisonResult.HasBreakingChanges) |
| 1 | 112 | | { |
| 1 | 113 | | _logger?.LogWarning("Breaking changes detected in comparison result, returning BreakingChangesDetected e |
| 1 | 114 | | return BreakingChangesDetected; |
| | 115 | | } |
| | 116 | |
|
| 2 | 117 | | _logger?.LogInformation("No breaking changes detected in comparison result, returning Success exit code"); |
| 2 | 118 | | return Success; |
| 4 | 119 | | } |
| | 120 | |
|
| | 121 | | /// <summary> |
| | 122 | | /// Determines the appropriate exit code based on an API comparison. |
| | 123 | | /// </summary> |
| | 124 | | /// <param name="apiComparison">The API comparison to evaluate.</param> |
| | 125 | | /// <returns>The appropriate exit code.</returns> |
| | 126 | | public int GetExitCode(ApiComparison apiComparison) |
| 6 | 127 | | { |
| 6 | 128 | | if (apiComparison == null) |
| 0 | 129 | | { |
| 0 | 130 | | _logger?.LogWarning("API comparison is null, returning ComparisonError exit code"); |
| 0 | 131 | | return ComparisonError; |
| | 132 | | } |
| | 133 | |
|
| | 134 | | // Validate the comparison result |
| 6 | 135 | | if (!apiComparison.IsValid()) |
| 1 | 136 | | { |
| 1 | 137 | | _logger?.LogWarning("API comparison is invalid, returning ComparisonError exit code"); |
| 1 | 138 | | return ComparisonError; |
| | 139 | | } |
| | 140 | |
|
| | 141 | | // Check for breaking changes |
| 5 | 142 | | if (apiComparison.HasBreakingChanges) |
| 3 | 143 | | { |
| 3 | 144 | | int breakingChangesCount = apiComparison.BreakingChangesCount; |
| 3 | 145 | | _logger?.LogWarning("{Count} breaking changes detected in API comparison, returning BreakingChangesDetec |
| 3 | 146 | | return BreakingChangesDetected; |
| | 147 | | } |
| | 148 | |
|
| 2 | 149 | | _logger?.LogInformation("No breaking changes detected in API comparison, returning Success exit code"); |
| 2 | 150 | | return Success; |
| 6 | 151 | | } |
| | 152 | |
|
| | 153 | | /// <summary> |
| | 154 | | /// Determines the appropriate exit code for an exception scenario. |
| | 155 | | /// </summary> |
| | 156 | | /// <param name="exception">The exception that occurred.</param> |
| | 157 | | /// <returns>The appropriate exit code.</returns> |
| | 158 | | public int GetExitCodeForException(Exception exception) |
| 16 | 159 | | { |
| 16 | 160 | | if (exception == null) |
| 1 | 161 | | { |
| 1 | 162 | | _logger?.LogWarning("Exception is null, returning UnexpectedError exit code"); |
| 1 | 163 | | return UnexpectedError; |
| | 164 | | } |
| | 165 | |
|
| 15 | 166 | | int exitCode = exception switch |
| 15 | 167 | | { |
| 2 | 168 | | FileNotFoundException => FileNotFound, |
| 1 | 169 | | DirectoryNotFoundException => FileNotFound, |
| 1 | 170 | | ReflectionTypeLoadException => AssemblyLoadError, |
| 1 | 171 | | BadImageFormatException => AssemblyLoadError, |
| 1 | 172 | | SecurityException => AssemblyLoadError, |
| 1 | 173 | | ArgumentNullException => InvalidArguments, |
| 2 | 174 | | ArgumentException => InvalidArguments, |
| 1 | 175 | | InvalidOperationException => ConfigurationError, |
| 1 | 176 | | NotSupportedException => ConfigurationError, |
| 4 | 177 | | _ => UnexpectedError |
| 15 | 178 | | }; |
| | 179 | |
|
| 15 | 180 | | _logger?.LogWarning( |
| 15 | 181 | | "Exception of type {ExceptionType} occurred, returning {ExitCode} exit code", |
| 15 | 182 | | exception.GetType().Name, |
| 15 | 183 | | GetExitCodeDescription(exitCode)); |
| | 184 | |
|
| 15 | 185 | | return exitCode; |
| 16 | 186 | | } |
| | 187 | |
|
| | 188 | | /// <summary> |
| | 189 | | /// Determines the appropriate exit code based on a combination of API comparison and exception. |
| | 190 | | /// </summary> |
| | 191 | | /// <param name="apiComparison">The API comparison to evaluate, or null if not available.</param> |
| | 192 | | /// <param name="exception">The exception that occurred, or null if no exception.</param> |
| | 193 | | /// <returns>The appropriate exit code.</returns> |
| | 194 | | public int GetExitCode(ApiComparison? apiComparison, Exception? exception) |
| 4 | 195 | | { |
| | 196 | | // If there's an exception, it takes precedence |
| 4 | 197 | | if (exception != null) |
| 2 | 198 | | { |
| 2 | 199 | | _logger?.LogWarning("Exception present, determining exit code based on exception"); |
| 2 | 200 | | return GetExitCodeForException(exception); |
| | 201 | | } |
| | 202 | |
|
| | 203 | | // If there's no API comparison, return comparison error |
| 2 | 204 | | if (apiComparison == null) |
| 1 | 205 | | { |
| 1 | 206 | | _logger?.LogWarning("API comparison is null, returning ComparisonError exit code"); |
| 1 | 207 | | return ComparisonError; |
| | 208 | | } |
| | 209 | |
|
| | 210 | | // Otherwise, determine based on the API comparison |
| 1 | 211 | | _logger?.LogInformation("Determining exit code based on API comparison"); |
| 1 | 212 | | return GetExitCode(apiComparison); |
| 4 | 213 | | } |
| | 214 | |
|
| | 215 | | /// <summary> |
| | 216 | | /// Gets a human-readable description of the exit code. |
| | 217 | | /// </summary> |
| | 218 | | /// <param name="exitCode">The exit code to describe.</param> |
| | 219 | | /// <returns>A description of what the exit code means.</returns> |
| | 220 | | public string GetExitCodeDescription(int exitCode) |
| 15 | 221 | | { |
| 15 | 222 | | return exitCode switch |
| 15 | 223 | | { |
| 1 | 224 | | Success => "Comparison completed successfully with no breaking changes detected.", |
| 1 | 225 | | BreakingChangesDetected => "Comparison completed successfully but breaking changes were detected.", |
| 1 | 226 | | ComparisonError => "An error occurred during the comparison process.", |
| 1 | 227 | | AssemblyLoadError => "Failed to load one or more assemblies for comparison.", |
| 1 | 228 | | ConfigurationError => "Configuration error or invalid settings detected.", |
| 1 | 229 | | InvalidArguments => "Invalid command line arguments provided.", |
| 1 | 230 | | FileNotFound => "One or more required files could not be found.", |
| 7 | 231 | | UnexpectedError => "An unexpected error occurred during execution.", |
| 1 | 232 | | _ => $"Unknown exit code: {exitCode}" |
| 15 | 233 | | }; |
| 15 | 234 | | } |
| | 235 | | } |
| | 236 | | } |