-
-
Notifications
You must be signed in to change notification settings - Fork 63
Extract RefitterRunner as shared generation workflow #1166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
12778c6
feat: add RefitterRunner, RunResult, RunnerDiagnostic, IValidator, an…
christianhelle 50b0e41
test: add unit tests for RefitterRunner covering all key scenarios
christianhelle a857c75
feat: add Exception property to RunResult for form-specific error han…
christianhelle 2328d1c
refactor: GenerationOrchestrator delegates to RefitterRunner
christianhelle fb39173
refactor: RefitterSourceGenerator delegates to RefitterRunner
christianhelle 93eb7ee
refactor: RefitterGenerateTask delegates to RefitterRunner
christianhelle c1f6dc6
merge: resolve conflict with origin/main (keep RefitterRunner delegat…
Copilot 5dc492c
fix: apply CodeRabbit auto-fixes
coderabbitai[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Refitter.Core.Validation; | ||
|
|
||
| namespace Refitter.Core; | ||
|
|
||
| /// <summary> | ||
| /// Validates an OpenAPI specification file and returns validation diagnostics and statistics. | ||
| /// Each distribution form (CLI, MSBuild, Source Generator) can provide its own adapter. | ||
| /// </summary> | ||
| public interface IValidator | ||
| { | ||
| /// <summary> | ||
| /// Validates the specified OpenAPI specification file. | ||
| /// </summary> | ||
| /// <param name="openApiPath">The path to the OpenAPI specification file.</param> | ||
| /// <param name="cancellationToken">A cancellation token.</param> | ||
| /// <returns>An <see cref="OpenApiValidationResult"/> containing diagnostics and element counts.</returns> | ||
| Task<OpenApiValidationResult> ValidateAsync(string openApiPath, CancellationToken cancellationToken = default); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Refitter.Core.Validation; | ||
|
|
||
| namespace Refitter.Core; | ||
|
|
||
| /// <summary> | ||
| /// Encapsulates the shared generation workflow across all distribution forms (CLI, MSBuild, Source Generator). | ||
| /// Handles generator creation, code generation, output planning, validation, warning detection, and file writing. | ||
| /// </summary> | ||
| public class RefitterRunner | ||
| { | ||
| /// <summary> | ||
| /// Runs the complete generation workflow. | ||
| /// </summary> | ||
| /// <param name="settings">The generator settings.</param> | ||
| /// <param name="writer">Optional file writer for writing output files. When null, files are not written.</param> | ||
| /// <param name="validator">Optional validator for OpenAPI spec validation. When null, validation is skipped.</param> | ||
| /// <param name="settingsFilePath">Optional path to the .refitter settings file, used for resolving relative output paths.</param> | ||
| /// <param name="outputPath">Optional CLI output path override.</param> | ||
| /// <param name="cancellationToken">A cancellation token.</param> | ||
| /// <returns>A <see cref="RunResult"/> containing generated files, warnings, diagnostics, and the exit code.</returns> | ||
| public async Task<RunResult> RunAsync( | ||
|
Check warning on line 26 in src/Refitter.Core/RefitterRunner.cs
|
||
| RefitGeneratorSettings settings, | ||
| IFileWriter? writer = null, | ||
| IValidator? validator = null, | ||
| string? settingsFilePath = null, | ||
| string? outputPath = null, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| var stopwatch = Stopwatch.StartNew(); | ||
| var warnings = new List<Warning>(); | ||
| var diagnostics = new List<RunnerDiagnostic>(); | ||
|
|
||
| try | ||
| { | ||
| var generator = await RefitGenerator.CreateAsync( | ||
| settings, | ||
| cancellationToken); | ||
|
|
||
| var output = GenerateCode(generator, settings); | ||
|
|
||
| var planner = new OutputPlannerAdapter(); | ||
| var plannedFiles = planner.Plan( | ||
| output, | ||
| settings, | ||
| settingsFilePath, | ||
| outputPath); | ||
|
|
||
| if (validator != null) | ||
| { | ||
| await ValidateOpenApiSpecsAsync( | ||
| settings, | ||
| validator, | ||
| diagnostics, | ||
| cancellationToken); | ||
| } | ||
|
|
||
| if (writer != null) | ||
| { | ||
| await WriteFilesAsync(plannedFiles, writer, cancellationToken); | ||
| } | ||
|
|
||
| DetectWarnings(settings, warnings); | ||
|
|
||
| DetectPathFilteringDiagnostics(settings, generator, diagnostics); | ||
|
|
||
| stopwatch.Stop(); | ||
|
|
||
| return new RunResult( | ||
| plannedFiles, | ||
| warnings.AsReadOnly(), | ||
| diagnostics.AsReadOnly(), | ||
| stopwatch.Elapsed, | ||
| ExitCode: 0); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| stopwatch.Stop(); | ||
|
|
||
| var errorDiagnostics = new List<RunnerDiagnostic>(diagnostics) | ||
| { | ||
| new RunnerDiagnostic(ex.Message, IsError: true) | ||
| }; | ||
|
|
||
| return new RunResult( | ||
| Array.Empty<PlannedFile>(), | ||
| warnings.AsReadOnly(), | ||
| errorDiagnostics.AsReadOnly(), | ||
| stopwatch.Elapsed, | ||
| ex.HResult, | ||
| ex); | ||
| } | ||
| } | ||
|
|
||
| private static GeneratorOutput GenerateCode(RefitGenerator generator, RefitGeneratorSettings settings) | ||
| { | ||
| if (settings.GenerateMultipleFiles) | ||
| { | ||
| return generator.GenerateMultipleFiles(); | ||
| } | ||
| else | ||
| { | ||
| var code = generator.Generate() | ||
| .Replace("\r\n", "\n") | ||
| .Replace("\n", "\r\n"); | ||
| return new GeneratorOutput( | ||
| new List<GeneratedCode> { new(string.Empty, code) }); | ||
| } | ||
| } | ||
|
|
||
| private static async Task ValidateOpenApiSpecsAsync( | ||
| RefitGeneratorSettings settings, | ||
| IValidator validator, | ||
| List<RunnerDiagnostic> diagnostics, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| var openApiPaths = GetOpenApiPaths(settings); | ||
| foreach (var specPath in openApiPaths) | ||
|
Check warning on line 122 in src/Refitter.Core/RefitterRunner.cs
|
||
| { | ||
| if (!string.IsNullOrWhiteSpace(specPath)) | ||
| { | ||
| var validationResult = await validator.ValidateAsync( | ||
| specPath, | ||
| cancellationToken); | ||
|
|
||
| foreach (var error in validationResult.Diagnostics.Errors) | ||
| diagnostics.Add(new RunnerDiagnostic(error.Message, IsError: true)); | ||
|
|
||
| foreach (var warning in validationResult.Diagnostics.Warnings) | ||
| diagnostics.Add(new RunnerDiagnostic(warning.Message, IsError: false)); | ||
|
|
||
| validationResult.ThrowIfInvalid(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static async Task WriteFilesAsync( | ||
| IReadOnlyList<PlannedFile> plannedFiles, | ||
| IFileWriter writer, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| foreach (var file in plannedFiles) | ||
| { | ||
| await writer.WriteAsync(file, cancellationToken); | ||
| } | ||
| } | ||
|
|
||
| private static void DetectPathFilteringDiagnostics( | ||
| RefitGeneratorSettings settings, | ||
| RefitGenerator generator, | ||
| List<RunnerDiagnostic> diagnostics) | ||
| { | ||
| if (settings.IncludePathMatches.Length > 0 && | ||
| generator.OpenApiDocument.Paths.Count == 0) | ||
| { | ||
| diagnostics.Add(new RunnerDiagnostic( | ||
| $"All paths were filtered out by include path patterns: [{string.Join(", ", settings.IncludePathMatches)}]", | ||
| IsError: false)); | ||
| } | ||
| } | ||
|
|
||
| private static string[] GetOpenApiPaths(RefitGeneratorSettings settings) | ||
| { | ||
| if (settings.OpenApiPaths is { Length: > 0 }) | ||
| return settings.OpenApiPaths; | ||
|
|
||
| if (settings.OpenApiPath is not null) | ||
| return new[] { settings.OpenApiPath }; | ||
|
|
||
| return Array.Empty<string>(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Detects configuration warnings in the settings and adds them to the provided list. | ||
| /// </summary> | ||
| internal static void DetectWarnings( | ||
| RefitGeneratorSettings settings, | ||
| List<Warning> warnings) | ||
| { | ||
| if (settings is { UseIsoDateFormat: true, CodeGeneratorSettings.DateFormat: not null }) | ||
| { | ||
| warnings.Add( | ||
| new Warning( | ||
| "Date Format Override", | ||
| "'codeGeneratorSettings.dateFormat' will be ignored due to 'useIsoDateFormat' set to true")); | ||
| } | ||
|
|
||
| #pragma warning disable CS0618 | ||
| if (settings.DependencyInjectionSettings?.UsePolly is true) | ||
| #pragma warning restore CS0618 | ||
| { | ||
| warnings.Add( | ||
| new Warning( | ||
| "Deprecated Setting", | ||
| "The 'usePolly' property is deprecated. Use 'transientErrorHandler: Polly' instead")); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace Refitter.Core; | ||
|
|
||
| /// <summary> | ||
| /// Represents the result of a <see cref="RefitterRunner.RunAsync"/> invocation. | ||
| /// </summary> | ||
| /// <param name="GeneratedFiles">The list of planned output files with their paths and content.</param> | ||
| /// <param name="Warnings">Configuration warnings detected during the run.</param> | ||
| /// <param name="Diagnostics">Validation and error diagnostics collected during the run.</param> | ||
| /// <param name="Elapsed">The elapsed time of the run.</param> | ||
| /// <param name="ExitCode">The exit code (0 for success, non-zero for failure).</param> | ||
| /// <param name="Exception">The original exception, if any, for form-specific error handling.</param> | ||
| public record RunResult( | ||
| IReadOnlyList<PlannedFile> GeneratedFiles, | ||
| IReadOnlyList<Warning> Warnings, | ||
| IReadOnlyList<RunnerDiagnostic> Diagnostics, | ||
| TimeSpan Elapsed, | ||
| int ExitCode, | ||
| Exception? Exception = null); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| namespace Refitter.Core; | ||
|
|
||
| /// <summary> | ||
| /// Represents a diagnostic message from a generation run, such as a validation error or warning. | ||
| /// </summary> | ||
| /// <param name="Message">The diagnostic message text.</param> | ||
| /// <param name="IsError">Whether this diagnostic represents an error (true) or a warning (false).</param> | ||
| public record RunnerDiagnostic(string Message, bool IsError); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Refitter.Core.Validation; | ||
|
|
||
| /// <summary> | ||
| /// Adapts the static <see cref="OpenApiValidator"/> to the <see cref="IValidator"/> interface. | ||
| /// </summary> | ||
| public sealed class OpenApiValidatorAdapter : IValidator | ||
| { | ||
| /// <inheritdoc /> | ||
| public async Task<OpenApiValidationResult> ValidateAsync(string openApiPath, CancellationToken cancellationToken = default) | ||
| { | ||
| return await OpenApiValidator.Validate(openApiPath, cancellationToken); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| namespace Refitter.Core; | ||
|
|
||
| /// <summary> | ||
| /// Represents a user-facing configuration warning message. | ||
| /// </summary> | ||
| /// <param name="Title">The short title of the warning.</param> | ||
| /// <param name="Description">The extended description of the warning.</param> | ||
| public record Warning(string Title, string Description); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.