From 8318a169bfb905ca204648d2b13508afb7485774 Mon Sep 17 00:00:00 2001
From: Christian Findlay <16697547+MelbourneDeveloper@users.noreply.github.com>
Date: Sat, 18 Oct 2025 08:05:44 +1100
Subject: [PATCH 01/35] Fix codegen
---
.../Program.cs | 3 +--
.../OpenApiCodeGenerator.cs | 27 +++++++------------
...ApiGenerator.Sample.JSONPlaceholder.csproj | 4 +--
3 files changed, 12 insertions(+), 22 deletions(-)
diff --git a/RestClient.Net.OpenApiGenerator.Cli/Program.cs b/RestClient.Net.OpenApiGenerator.Cli/Program.cs
index cca1c506..af3cf587 100644
--- a/RestClient.Net.OpenApiGenerator.Cli/Program.cs
+++ b/RestClient.Net.OpenApiGenerator.Cli/Program.cs
@@ -191,8 +191,7 @@ await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false),
@namespace: config.Namespace,
className: config.ClassName,
outputPath: config.OutputPath,
- baseUrlOverride: config.BaseUrl,
- versionOverride: config.Version
+ baseUrlOverride: config.BaseUrl
);
Console.WriteLine(
diff --git a/RestClient.Net.OpenApiGenerator/OpenApiCodeGenerator.cs b/RestClient.Net.OpenApiGenerator/OpenApiCodeGenerator.cs
index 01154d90..687b4359 100644
--- a/RestClient.Net.OpenApiGenerator/OpenApiCodeGenerator.cs
+++ b/RestClient.Net.OpenApiGenerator/OpenApiCodeGenerator.cs
@@ -1,4 +1,5 @@
using Microsoft.OpenApi.Readers;
+using Microsoft.OpenApi.Validations;
using ErrorUrl = Outcome.Result<(string, string), string>.Error<(string, string), string>;
using OkUrl = Outcome.Result<(string, string), string>.Ok<(string, string), string>;
@@ -13,7 +14,6 @@ public static class OpenApiCodeGenerator
/// The class name for extension methods.
/// The directory path where generated files will be saved.
/// Optional base URL override. Use this when the OpenAPI spec has a relative server URL.
- /// Optional OpenAPI version override (e.g., "3.0.2"). Use this when the spec declares the wrong version.
/// The generated code result.
#pragma warning disable CA1054
public static GeneratorResult Generate(
@@ -21,26 +21,17 @@ public static GeneratorResult Generate(
string @namespace,
string className,
string outputPath,
- string? baseUrlOverride = null,
- string? versionOverride = null
+ string? baseUrlOverride = null
)
#pragma warning restore CA1054
{
- // Apply version override if specified
- if (!string.IsNullOrEmpty(versionOverride))
- {
-#pragma warning disable SYSLIB1045
- openApiContent = System.Text.RegularExpressions.Regex.Replace(
- openApiContent,
- @"^openapi:\s*[\d\.]+",
- $"openapi: {versionOverride}",
- System.Text.RegularExpressions.RegexOptions.Multiline
- );
-#pragma warning restore SYSLIB1045
- }
-
- var reader = new OpenApiStringReader();
- var document = reader.Read(openApiContent, out var diagnostic);
+ var document = new OpenApiStringReader(
+ new OpenApiReaderSettings
+ {
+ ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences,
+ RuleSet = ValidationRuleSet.GetDefaultRuleSet(),
+ }
+ ).Read(openApiContent, out var diagnostic);
if (diagnostic.Errors.Count > 0)
{
diff --git a/Samples/RestClient.OpenApiGenerator.Sample.JSONPlaceholder/RestClient.OpenApiGenerator.Sample.JSONPlaceholder.csproj b/Samples/RestClient.OpenApiGenerator.Sample.JSONPlaceholder/RestClient.OpenApiGenerator.Sample.JSONPlaceholder.csproj
index d1ae74c7..99e1d1f4 100644
--- a/Samples/RestClient.OpenApiGenerator.Sample.JSONPlaceholder/RestClient.OpenApiGenerator.Sample.JSONPlaceholder.csproj
+++ b/Samples/RestClient.OpenApiGenerator.Sample.JSONPlaceholder/RestClient.OpenApiGenerator.Sample.JSONPlaceholder.csproj
@@ -9,7 +9,7 @@
-
-
+
+
From 16ca4d80fdf54f0072fc3bddc31059e9e1575d36 Mon Sep 17 00:00:00 2001
From: Christian Findlay <16697547+MelbourneDeveloper@users.noreply.github.com>
Date: Sat, 18 Oct 2025 08:20:29 +1100
Subject: [PATCH 02/35] Refactor to use Outcome
---
.editorconfig | 8 +-
.../Program.cs | 41 ++--
.../OpenApiCodeGeneratorTests.cs | 203 ++++++++++++------
.../OpenApiCodeGenerator.cs | 83 ++++---
.../GlobalUsings.cs | 60 ++++--
5 files changed, 263 insertions(+), 132 deletions(-)
diff --git a/.editorconfig b/.editorconfig
index 86fae7a4..e78483a6 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -12,7 +12,6 @@ dotnet_analyzer_diagnostic.category-Globalization.severity = error
dotnet_analyzer_diagnostic.category-Documentation.severity = error
dotnet_analyzer_diagnostic.category-Readability.severity = error
dotnet_analyzer_diagnostic.category-Ordering.severity = error
-dotnet_analyzer_diagnostic.category-StyleCop.CSharp.OrderingRules.severity = none
# Nullability
# CS8602: Dereference of a possibly null reference.
@@ -41,7 +40,6 @@ dotnet_diagnostic.CA2000.severity = error
dotnet_diagnostic.CA2201.severity = error
dotnet_diagnostic.CS1591.severity = error
dotnet_diagnostic.IDE0022.severity = error
-dotnet_diagnostic.CA1054.severity = error
dotnet_diagnostic.CS8600.severity = error
dotnet_diagnostic.CS8601.severity = error
dotnet_diagnostic.CS8603.severity = error
@@ -373,9 +371,15 @@ dotnet_diagnostic.SA1400.severity = none
dotnet_diagnostic.SA1114.severity = none
dotnet_diagnostic.SA1118.severity = none
dotnet_diagnostic.SA1649.severity = none
+dotnet_diagnostic.CA1054.severity = none
+dotnet_diagnostic.SA1200.severity = none
+# TODO: these are nice. Put back
+dotnet_diagnostic.SA1201.severity = none
+dotnet_diagnostic.SA1202.severity = none
+dotnet_diagnostic.SA1204.severity = none
diff --git a/RestClient.Net.OpenApiGenerator.Cli/Program.cs b/RestClient.Net.OpenApiGenerator.Cli/Program.cs
index af3cf587..1a59ebb4 100644
--- a/RestClient.Net.OpenApiGenerator.Cli/Program.cs
+++ b/RestClient.Net.OpenApiGenerator.Cli/Program.cs
@@ -6,6 +6,14 @@
Outcome.HttpError
>;
using ExceptionErrorString = Outcome.HttpError.ExceptionError;
+using GeneratorError = Outcome.Result<
+ RestClient.Net.OpenApiGenerator.GeneratorResult,
+ string
+>.Error;
+using GeneratorOk = Outcome.Result.Ok<
+ RestClient.Net.OpenApiGenerator.GeneratorResult,
+ string
+>;
using OkString = Outcome.Result>.Ok<
string,
Outcome.HttpError
@@ -186,7 +194,7 @@ await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false),
Console.WriteLine($"Downloaded {openApiSpec.Length} characters\n");
Console.WriteLine("Generating C# code from OpenAPI spec...");
- var generatorResult = OpenApiCodeGenerator.Generate(
+ var result = OpenApiCodeGenerator.Generate(
openApiSpec,
@namespace: config.Namespace,
className: config.ClassName,
@@ -194,21 +202,26 @@ await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false),
baseUrlOverride: config.BaseUrl
);
- Console.WriteLine(
- $"Generated {generatorResult.ExtensionMethodsCode.Length} "
- + "characters of extension methods"
- );
- Console.WriteLine($"Generated {generatorResult.ModelsCode.Length} " + "characters of models");
-
- if (generatorResult.ExtensionMethodsCode.StartsWith("//", StringComparison.Ordinal))
+#pragma warning disable IDE0010
+ switch (result)
+#pragma warning restore IDE0010
{
- Console.WriteLine("\nError in generated code:");
- Console.WriteLine(generatorResult.ExtensionMethodsCode);
- return;
+ case GeneratorOk(var generatorResult):
+ Console.WriteLine(
+ $"Generated {generatorResult.ExtensionMethodsCode.Length} "
+ + "characters of extension methods"
+ );
+ Console.WriteLine(
+ $"Generated {generatorResult.ModelsCode.Length} " + "characters of models"
+ );
+ Console.WriteLine($"\nSaved files to: {config.OutputPath}");
+ Console.WriteLine("\nGeneration completed successfully!");
+ break;
+ case GeneratorError(var error):
+ Console.WriteLine("\nCode generation failed:");
+ Console.WriteLine(error);
+ break;
}
-
- Console.WriteLine($"\nSaved files to: {config.OutputPath}");
- Console.WriteLine("\nGeneration completed successfully!");
}
static string HandleDownloadError(string message)
diff --git a/RestClient.Net.OpenApiGenerator.Tests/OpenApiCodeGeneratorTests.cs b/RestClient.Net.OpenApiGenerator.Tests/OpenApiCodeGeneratorTests.cs
index 1749b2fd..47b99d1b 100644
--- a/RestClient.Net.OpenApiGenerator.Tests/OpenApiCodeGeneratorTests.cs
+++ b/RestClient.Net.OpenApiGenerator.Tests/OpenApiCodeGeneratorTests.cs
@@ -1,10 +1,31 @@
using RestClient.Net.OpenApiGenerator;
+using GeneratorError = Outcome.Result<
+ RestClient.Net.OpenApiGenerator.GeneratorResult,
+ string
+>.Error;
+using GeneratorOk = Outcome.Result.Ok<
+ RestClient.Net.OpenApiGenerator.GeneratorResult,
+ string
+>;
namespace RestClient.Net.OpenApiGenerator.Tests;
[TestClass]
public class OpenApiCodeGeneratorTests
{
+ private static GeneratorResult GetSuccessResult(
+ Outcome.Result result
+ ) =>
+#pragma warning disable CS8509
+ result switch
+ {
+ GeneratorOk(var r) => r,
+ GeneratorError(var error) => throw new AssertFailedException(
+ $"Generation failed: {error}"
+ ),
+ };
+#pragma warning restore CS8509
+
private const string SimpleOpenApiSpec = """
{
"openapi": "3.0.0",
@@ -152,11 +173,13 @@ public class OpenApiCodeGeneratorTests
[TestMethod]
public void Generate_WithValidSpec_ProducesNonEmptyCode()
{
- var result = OpenApiCodeGenerator.Generate(
- SimpleOpenApiSpec,
- "TestApi",
- "TestApiExtensions",
- Path.GetTempPath()
+ var result = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ SimpleOpenApiSpec,
+ "TestApi",
+ "TestApiExtensions",
+ Path.GetTempPath()
+ )
);
// Debug: Output actual result if empty
@@ -182,11 +205,13 @@ public void Generate_WithValidSpec_ProducesNonEmptyCode()
[TestMethod]
public void Generate_CreatesCorrectBaseUrl()
{
- var result = OpenApiCodeGenerator.Generate(
- SimpleOpenApiSpec,
- "TestApi",
- "TestApiExtensions",
- Path.GetTempPath()
+ var result = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ SimpleOpenApiSpec,
+ "TestApi",
+ "TestApiExtensions",
+ Path.GetTempPath()
+ )
);
Assert.IsTrue(result.ExtensionMethodsCode.Contains("\"https://api.test.com\""));
@@ -196,11 +221,13 @@ public void Generate_CreatesCorrectBaseUrl()
[TestMethod]
public void Generate_CreatesCorrectRelativeUrls()
{
- var result = OpenApiCodeGenerator.Generate(
- SimpleOpenApiSpec,
- "TestApi",
- "TestApiExtensions",
- Path.GetTempPath()
+ var result = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ SimpleOpenApiSpec,
+ "TestApi",
+ "TestApiExtensions",
+ Path.GetTempPath()
+ )
);
Assert.IsTrue(result.ExtensionMethodsCode.Contains("\"/v1/pets\""));
@@ -210,11 +237,13 @@ public void Generate_CreatesCorrectRelativeUrls()
[TestMethod]
public void Generate_IncludesQueryParameters()
{
- var result = OpenApiCodeGenerator.Generate(
- SimpleOpenApiSpec,
- "TestApi",
- "TestApiExtensions",
- Path.GetTempPath()
+ var result = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ SimpleOpenApiSpec,
+ "TestApi",
+ "TestApiExtensions",
+ Path.GetTempPath()
+ )
);
Assert.IsTrue(result.ExtensionMethodsCode.Contains("int limit"));
@@ -224,11 +253,13 @@ public void Generate_IncludesQueryParameters()
[TestMethod]
public void Generate_HandlesPathAndQueryParametersTogether()
{
- var result = OpenApiCodeGenerator.Generate(
- SimpleOpenApiSpec,
- "TestApi",
- "TestApiExtensions",
- Path.GetTempPath()
+ var result = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ SimpleOpenApiSpec,
+ "TestApi",
+ "TestApiExtensions",
+ Path.GetTempPath()
+ )
);
Assert.IsTrue(
@@ -241,11 +272,13 @@ public void Generate_HandlesPathAndQueryParametersTogether()
[TestMethod]
public void Generate_ResolvesSchemaReferences()
{
- var result = OpenApiCodeGenerator.Generate(
- SimpleOpenApiSpec,
- "TestApi",
- "TestApiExtensions",
- Path.GetTempPath()
+ var result = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ SimpleOpenApiSpec,
+ "TestApi",
+ "TestApiExtensions",
+ Path.GetTempPath()
+ )
);
Assert.IsTrue(result.ExtensionMethodsCode.Contains("Result e,
+ GeneratorOk => throw new AssertFailedException("Expected error but got success"),
+ };
+#pragma warning restore CS8509
+
+ Assert.IsTrue(error.Contains("must specify at least one server"));
}
[TestMethod]
@@ -336,9 +382,16 @@ public void Generate_WithRelativeServerUrl_RequiresOverride()
Path.GetTempPath()
);
- Assert.IsTrue(result.ExtensionMethodsCode.StartsWith("// Error", StringComparison.Ordinal));
- Assert.IsTrue(result.ExtensionMethodsCode.Contains("relative"));
- Assert.IsTrue(result.ExtensionMethodsCode.Contains("baseUrlOverride"));
+#pragma warning disable CS8509
+ var error = result switch
+ {
+ GeneratorError(var e) => e,
+ GeneratorOk => throw new AssertFailedException("Expected error but got success"),
+ };
+#pragma warning restore CS8509
+
+ Assert.IsTrue(error.Contains("relative"));
+ Assert.IsTrue(error.Contains("baseUrlOverride"));
}
[TestMethod]
@@ -363,12 +416,14 @@ public void Generate_WithRelativeServerUrlAndOverride_Succeeds()
}
""";
- var result = OpenApiCodeGenerator.Generate(
- specWithRelativeUrl,
- "TestApi",
- "TestApiExtensions",
- Path.GetTempPath(),
- "https://example.com"
+ var result = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ specWithRelativeUrl,
+ "TestApi",
+ "TestApiExtensions",
+ Path.GetTempPath(),
+ "https://example.com"
+ )
);
Assert.IsTrue(result.ExtensionMethodsCode.Contains("https://example.com"));
@@ -378,11 +433,13 @@ public void Generate_WithRelativeServerUrlAndOverride_Succeeds()
[TestMethod]
public void Generate_CreatesModelWithCorrectProperties()
{
- var result = OpenApiCodeGenerator.Generate(
- SimpleOpenApiSpec,
- "TestApi",
- "TestApiExtensions",
- Path.GetTempPath()
+ var result = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ SimpleOpenApiSpec,
+ "TestApi",
+ "TestApiExtensions",
+ Path.GetTempPath()
+ )
);
Assert.IsTrue(result.ModelsCode.Contains("public class Pet"));
@@ -398,11 +455,13 @@ public void Generate_WritesFilesToOutputPath()
try
{
- _ = OpenApiCodeGenerator.Generate(
- SimpleOpenApiSpec,
- "TestApi",
- "TestApiExtensions",
- tempDir
+ _ = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ SimpleOpenApiSpec,
+ "TestApi",
+ "TestApiExtensions",
+ tempDir
+ )
);
var extensionsFile = Path.Combine(tempDir, "TestApiExtensions.g.cs");
@@ -425,11 +484,13 @@ public void Generate_WritesFilesToOutputPath()
[TestMethod]
public void Generate_CreatesPrivateStaticFuncFields()
{
- var result = OpenApiCodeGenerator.Generate(
- SimpleOpenApiSpec,
- "TestApi",
- "TestApiExtensions",
- Path.GetTempPath()
+ var result = GetSuccessResult(
+ OpenApiCodeGenerator.Generate(
+ SimpleOpenApiSpec,
+ "TestApi",
+ "TestApiExtensions",
+ Path.GetTempPath()
+ )
);
// Verify that private static properties with delegates are generated
diff --git a/RestClient.Net.OpenApiGenerator/OpenApiCodeGenerator.cs b/RestClient.Net.OpenApiGenerator/OpenApiCodeGenerator.cs
index 687b4359..8f20bcf7 100644
--- a/RestClient.Net.OpenApiGenerator/OpenApiCodeGenerator.cs
+++ b/RestClient.Net.OpenApiGenerator/OpenApiCodeGenerator.cs
@@ -1,8 +1,18 @@
using Microsoft.OpenApi.Readers;
using Microsoft.OpenApi.Validations;
using ErrorUrl = Outcome.Result<(string, string), string>.Error<(string, string), string>;
+using GeneratorError = Outcome.Result<
+ RestClient.Net.OpenApiGenerator.GeneratorResult,
+ string
+>.Error;
+using GeneratorOk = Outcome.Result.Ok<
+ RestClient.Net.OpenApiGenerator.GeneratorResult,
+ string
+>;
using OkUrl = Outcome.Result<(string, string), string>.Ok<(string, string), string>;
+#pragma warning disable CS8509
+
namespace RestClient.Net.OpenApiGenerator;
/// Generates C# extension methods from OpenAPI specifications.
@@ -14,9 +24,9 @@ public static class OpenApiCodeGenerator
/// The class name for extension methods.
/// The directory path where generated files will be saved.
/// Optional base URL override. Use this when the OpenAPI spec has a relative server URL.
- /// The generated code result.
+ /// A Result containing either the generated code or an error message with exception details.
#pragma warning disable CA1054
- public static GeneratorResult Generate(
+ public static Outcome.Result Generate(
string openApiContent,
string @namespace,
string className,
@@ -25,44 +35,51 @@ public static GeneratorResult Generate(
)
#pragma warning restore CA1054
{
- var document = new OpenApiStringReader(
- new OpenApiReaderSettings
+ try
+ {
+ var document = new OpenApiStringReader(
+ new OpenApiReaderSettings
+ {
+ ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences,
+ RuleSet = ValidationRuleSet.GetDefaultRuleSet(),
+ }
+ ).Read(openApiContent, out var diagnostic);
+
+ if (diagnostic.Errors.Count > 0)
{
- ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences,
- RuleSet = ValidationRuleSet.GetDefaultRuleSet(),
+ var errors = string.Join(", ", diagnostic.Errors.Select(e => e.Message));
+ return new GeneratorError($"Error parsing OpenAPI: {errors}");
}
- ).Read(openApiContent, out var diagnostic);
- if (diagnostic.Errors.Count > 0)
- {
- var errors = string.Join(", ", diagnostic.Errors.Select(e => e.Message));
- return new GeneratorResult($"// Error parsing OpenAPI: {errors}", string.Empty);
- }
-
- if (document == null)
- {
- return new GeneratorResult("// Error parsing OpenAPI: Document is null", string.Empty);
- }
+ if (document == null)
+ {
+ return new GeneratorError("Error parsing OpenAPI: Document is null");
+ }
- var urlResult = UrlParser.GetBaseUrlAndPath(document, baseUrlOverride);
+ var urlResult = UrlParser.GetBaseUrlAndPath(document, baseUrlOverride);
-#pragma warning disable CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive).
- return urlResult switch
+ return urlResult switch
+ {
+ OkUrl(var (baseUrl, basePath)) => GenerateCodeFiles(
+ document,
+ @namespace,
+ className,
+ outputPath,
+ baseUrl,
+ basePath
+ ),
+ ErrorUrl(var error) => new GeneratorError($"Error: {error}"),
+ };
+ }
+ catch (Exception ex)
{
- OkUrl(var (baseUrl, basePath)) => GenerateCodeFiles(
- document,
- @namespace,
- className,
- outputPath,
- baseUrl,
- basePath
- ),
- ErrorUrl(var error) => new GeneratorResult($"// Error: {error}", string.Empty),
- };
-#pragma warning restore CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ return new GeneratorError(
+ $"Exception during code generation: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}"
+ );
+ }
}
- private static GeneratorResult GenerateCodeFiles(
+ private static GeneratorOk GenerateCodeFiles(
Microsoft.OpenApi.Models.OpenApiDocument document,
string @namespace,
string className,
@@ -93,6 +110,6 @@ string basePath
File.WriteAllText(modelsFile, models);
File.WriteAllText(typeAliasesFile, typeAliases);
- return new GeneratorResult(extensionMethods, models);
+ return new GeneratorOk(new GeneratorResult(extensionMethods, models));
}
}
diff --git a/Samples/RestClient.OpenApiGenerator.Sample.Tests/GlobalUsings.cs b/Samples/RestClient.OpenApiGenerator.Sample.Tests/GlobalUsings.cs
index 86d58876..bd2292eb 100644
--- a/Samples/RestClient.OpenApiGenerator.Sample.Tests/GlobalUsings.cs
+++ b/Samples/RestClient.OpenApiGenerator.Sample.Tests/GlobalUsings.cs
@@ -1,17 +1,53 @@
#pragma warning disable IDE0005 // Using directive is unnecessary.
global using JSONPlaceholder.Generated;
+global using ErrorPost = Outcome.Result<
+ JSONPlaceholder.Generated.Post,
+ Outcome.HttpError
+>.Error>;
+global using ErrorPosts = Outcome.Result<
+ System.Collections.Generic.List,
+ Outcome.HttpError
+>.Error, Outcome.HttpError>;
+global using ErrorTodo = Outcome.Result<
+ JSONPlaceholder.Generated.Todo,
+ Outcome.HttpError
+>.Error>;
+global using ErrorTodos = Outcome.Result<
+ System.Collections.Generic.List,
+ Outcome.HttpError
+>.Error, Outcome.HttpError>;
+global using ErrorUnit = Outcome.Result>.Error<
+ Outcome.Unit,
+ Outcome.HttpError
+>;
+global using ErrorUser = Outcome.Result<
+ JSONPlaceholder.Generated.User,
+ Outcome.HttpError
+>.Error>;
global using ExceptionErrorString = Outcome.HttpError.ExceptionError;
+global using OkPost = Outcome.Result>.Ok<
+ JSONPlaceholder.Generated.Post,
+ Outcome.HttpError
+>;
+global using OkPosts = Outcome.Result<
+ System.Collections.Generic.List,
+ Outcome.HttpError
+>.Ok, Outcome.HttpError>;
+global using OkTodo = Outcome.Result>.Ok<
+ JSONPlaceholder.Generated.Todo,
+ Outcome.HttpError
+>;
+global using OkTodos = Outcome.Result<
+ System.Collections.Generic.List,
+ Outcome.HttpError
+>.Ok, Outcome.HttpError>;
+global using OkUnit = Outcome.Result>.Ok<
+ Outcome.Unit,
+ Outcome.HttpError
+>;
+global using OkUser = Outcome.Result>.Ok<
+ JSONPlaceholder.Generated.User,
+ Outcome.HttpError
+>;
global using ResponseErrorString = Outcome.HttpError.ErrorResponseError;
-global using OkPosts = Outcome.Result, Outcome.HttpError>.Ok, Outcome.HttpError>;
-global using ErrorPosts = Outcome.Result, Outcome.HttpError>.Error, Outcome.HttpError>;
-global using OkTodos = Outcome.Result, Outcome.HttpError>.Ok, Outcome.HttpError>;
-global using ErrorTodos = Outcome.Result, Outcome.HttpError>.Error, Outcome.HttpError>;
-global using OkPost = Outcome.Result>.Ok>;
-global using ErrorPost = Outcome.Result>.Error>;
-global using OkTodo = Outcome.Result>.Ok>;
-global using ErrorTodo = Outcome.Result>.Error>;
-global using OkUnit = Outcome.Result>.Ok>;
-global using ErrorUnit = Outcome.Result>.Error>;
-global using OkUser = Outcome.Result>.Ok>;
-global using ErrorUser = Outcome.Result>.Error>;
From 643da516b06a3124c33b293ebd26fe3dc0c9718e Mon Sep 17 00:00:00 2001
From: Christian Findlay <16697547+MelbourneDeveloper@users.noreply.github.com>
Date: Sat, 18 Oct 2025 10:21:41 +1100
Subject: [PATCH 03/35] Use the proper generator
---
.vscode/launch.json | 2 +-
...stClient.Net.OpenApiGenerator.Tests.csproj | 1 -
.../ExtensionMethodGenerator.cs | 89 +-
.../ModelGenerator.cs | 59 +-
.../OpenApiCodeGenerator.cs | 30 +-
RestClient.Net.OpenApiGenerator/README.md | 2 +-
.../RestClient.Net.OpenApiGenerator.csproj | 3 +-
RestClient.Net.OpenApiGenerator/UrlParser.cs | 4 +-
.../Generated/GlobalUsings.g.cs | 63 +
.../Generated/NucliaDBApiExtensions.g.cs | 1696 ++++++
.../Generated/NucliaDBApiModels.g.cs | 5181 +++++++++++++++++
Samples/NucliaDbClient/NucliaDbClient.csproj | 15 +
12 files changed, 7074 insertions(+), 71 deletions(-)
create mode 100644 Samples/NucliaDbClient/Generated/GlobalUsings.g.cs
create mode 100644 Samples/NucliaDbClient/Generated/NucliaDBApiExtensions.g.cs
create mode 100644 Samples/NucliaDbClient/Generated/NucliaDBApiModels.g.cs
create mode 100644 Samples/NucliaDbClient/NucliaDbClient.csproj
diff --git a/.vscode/launch.json b/.vscode/launch.json
index cd5d787f..7ab44cbd 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -33,7 +33,7 @@
"-u",
"${workspaceFolder}/Samples/NucliaDbClient/api.yaml",
"-o",
- "${workspaceFolder}/Samples/RestClient.OpenApiGenerator.Sample.NucliaDB/Generated",
+ "${workspaceFolder}/Samples/NucliaDbClient/Generated",
"-n",
"NucliaDB.Generated",
"-c",
diff --git a/RestClient.Net.OpenApiGenerator.Tests/RestClient.Net.OpenApiGenerator.Tests.csproj b/RestClient.Net.OpenApiGenerator.Tests/RestClient.Net.OpenApiGenerator.Tests.csproj
index 72c7ed1b..4a963d6d 100644
--- a/RestClient.Net.OpenApiGenerator.Tests/RestClient.Net.OpenApiGenerator.Tests.csproj
+++ b/RestClient.Net.OpenApiGenerator.Tests/RestClient.Net.OpenApiGenerator.Tests.csproj
@@ -9,7 +9,6 @@
-
diff --git a/RestClient.Net.OpenApiGenerator/ExtensionMethodGenerator.cs b/RestClient.Net.OpenApiGenerator/ExtensionMethodGenerator.cs
index f10dafc2..5240f21e 100644
--- a/RestClient.Net.OpenApiGenerator/ExtensionMethodGenerator.cs
+++ b/RestClient.Net.OpenApiGenerator/ExtensionMethodGenerator.cs
@@ -1,4 +1,4 @@
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
namespace RestClient.Net.OpenApiGenerator;
@@ -26,10 +26,15 @@ string basePath
foreach (var path in document.Paths)
{
+ if (path.Value?.Operations == null)
+ {
+ continue;
+ }
+
foreach (var operation in path.Value.Operations)
{
var responseType = GetResponseType(operation.Value);
- var isDelete = operation.Key == OperationType.Delete;
+ var isDelete = operation.Key == HttpMethod.Delete;
var resultResponseType = isDelete ? "Unit" : responseType;
_ = responseTypes.Add(resultResponseType);
@@ -123,7 +128,7 @@ private static async Task DeserializeError(
private static (string Method, List PrivateFunctions) GenerateMethod(
string path,
- OperationType operationType,
+ HttpMethod operationType,
OpenApiOperation operation,
string basePath
)
@@ -148,7 +153,7 @@ string basePath
#pragma warning disable CA1502 // Avoid excessive complexity - code generator method is inherently complex
private static (string Method, List PrivateFunctions) GenerateHttpMethod(
- OperationType operationType,
+ HttpMethod operationType,
string methodName,
string path,
List<(string Name, string Type, bool IsPath)> parameters,
@@ -159,8 +164,10 @@ string summary
#pragma warning restore CA1502
{
var hasBody =
- operationType is OperationType.Post or OperationType.Put or OperationType.Patch;
- var isDelete = operationType == OperationType.Delete;
+ operationType == HttpMethod.Post
+ || operationType == HttpMethod.Put
+ || operationType == HttpMethod.Patch;
+ var isDelete = operationType == HttpMethod.Delete;
var hasPathParams = parameters.Any(p => p.IsPath);
var queryParams = parameters.Where(p => !p.IsPath).ToList();
var hasQueryParams = queryParams.Count > 0;
@@ -175,7 +182,12 @@ string summary
? "?" + string.Join("&", queryParams.Select(q => $"{q.Name}={{{q.Name}}}"))
: string.Empty;
- var verb = operationType.ToString();
+ var verb = operationType == HttpMethod.Get ? "Get"
+ : operationType == HttpMethod.Post ? "Post"
+ : operationType == HttpMethod.Put ? "Put"
+ : operationType == HttpMethod.Delete ? "Delete"
+ : operationType == HttpMethod.Patch ? "Patch"
+ : operationType.Method;
var createMethod = $"Create{verb}";
var delegateType = $"{verb}Async";
@@ -369,7 +381,7 @@ string summary
private static string GetMethodName(
OpenApiOperation operation,
- OperationType operationType,
+ HttpMethod operationType,
string path
)
{
@@ -382,15 +394,15 @@ string path
path.Split('/').LastOrDefault(p => !string.IsNullOrEmpty(p) && !p.StartsWith('{'))
?? "Resource";
- return operationType switch
- {
- OperationType.Get => $"Get{CodeGenerationHelpers.ToPascalCase(pathPart)}",
- OperationType.Post => $"Create{CodeGenerationHelpers.ToPascalCase(pathPart)}",
- OperationType.Put => $"Update{CodeGenerationHelpers.ToPascalCase(pathPart)}",
- OperationType.Delete => $"Delete{CodeGenerationHelpers.ToPascalCase(pathPart)}",
- OperationType.Patch => $"Patch{CodeGenerationHelpers.ToPascalCase(pathPart)}",
- _ => $"{operationType}{CodeGenerationHelpers.ToPascalCase(pathPart)}",
- };
+ var methodName =
+ operationType == HttpMethod.Get ? "Get"
+ : operationType == HttpMethod.Post ? "Create"
+ : operationType == HttpMethod.Put ? "Update"
+ : operationType == HttpMethod.Delete ? "Delete"
+ : operationType == HttpMethod.Patch ? "Patch"
+ : operationType.Method;
+
+ return $"{methodName}{CodeGenerationHelpers.ToPascalCase(pathPart)}";
}
private static List<(string Name, string Type, bool IsPath)> GetParameters(
@@ -399,8 +411,18 @@ OpenApiOperation operation
{
var parameters = new List<(string Name, string Type, bool IsPath)>();
+ if (operation.Parameters == null)
+ {
+ return parameters;
+ }
+
foreach (var param in operation.Parameters)
{
+ if (param.Name == null)
+ {
+ continue;
+ }
+
var isPath = param.In == ParameterLocation.Path;
var type = ModelGenerator.MapOpenApiType(param.Schema);
parameters.Add((param.Name, type, isPath));
@@ -409,11 +431,18 @@ OpenApiOperation operation
return parameters;
}
- private static string? GetRequestBodyType(OpenApiOperation operation) =>
- operation.RequestBody == null ? null
- : operation.RequestBody.Content.FirstOrDefault().Value?.Schema?.Reference != null
- ? operation.RequestBody.Content.FirstOrDefault().Value.Schema.Reference.Id
- : "object";
+ private static string? GetRequestBodyType(OpenApiOperation operation)
+ {
+ if (operation.RequestBody?.Content == null)
+ {
+ return null;
+ }
+
+ var firstContent = operation.RequestBody.Content.FirstOrDefault();
+ return firstContent.Value?.Schema is OpenApiSchemaReference schemaRef
+ ? schemaRef.Reference.Id ?? "object"
+ : "object";
+ }
private static string GenerateTypeAliasesFile(HashSet responseTypes, string @namespace)
{
@@ -488,20 +517,22 @@ _ when responseType.StartsWith("List<", StringComparison.Ordinal) =>
private static string GetResponseType(OpenApiOperation operation)
{
- var successResponse = operation.Responses.FirstOrDefault(r =>
+ var successResponse = operation.Responses?.FirstOrDefault(r =>
r.Key.StartsWith('2') || r.Key == "default"
);
- if (successResponse.Value == null)
+ if (successResponse?.Value?.Content == null)
{
return "object";
}
- var content = successResponse.Value.Content.FirstOrDefault();
- return content.Value?.Schema?.Reference != null ? content.Value.Schema.Reference.Id
- : content.Value?.Schema?.Type == "array"
- && content.Value.Schema.Items?.Reference != null
- ? $"List<{content.Value.Schema.Items.Reference.Id}>"
+ var content = successResponse.Value.Value.Content.FirstOrDefault();
+ return content.Value?.Schema is OpenApiSchemaReference schemaRef
+ ? schemaRef.Reference.Id ?? "object"
+ : content.Value?.Schema is OpenApiSchema schema
+ && schema.Type == JsonSchemaType.Array
+ && schema.Items is OpenApiSchemaReference itemsRef
+ ? $"List<{itemsRef.Reference.Id ?? "object"}>"
: ModelGenerator.MapOpenApiType(content.Value?.Schema);
}
}
diff --git a/RestClient.Net.OpenApiGenerator/ModelGenerator.cs b/RestClient.Net.OpenApiGenerator/ModelGenerator.cs
index 3c96e44e..7b471e62 100644
--- a/RestClient.Net.OpenApiGenerator/ModelGenerator.cs
+++ b/RestClient.Net.OpenApiGenerator/ModelGenerator.cs
@@ -1,4 +1,4 @@
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
namespace RestClient.Net.OpenApiGenerator;
@@ -13,12 +13,16 @@ public static string GenerateModels(OpenApiDocument document, string @namespace)
{
var models = new List();
- foreach (
- var schema in document.Components?.Schemas ?? new Dictionary()
- )
+ if (document.Components?.Schemas != null)
{
- var model = GenerateModel(schema.Key, schema.Value);
- models.Add(model);
+ foreach (var schema in document.Components.Schemas)
+ {
+ if (schema.Value is OpenApiSchema schemaObj)
+ {
+ var model = GenerateModel(schema.Key, schemaObj);
+ models.Add(model);
+ }
+ }
}
var modelsCode = string.Join("\n\n", models);
@@ -36,12 +40,12 @@ namespace {{@namespace}};
/// The generated model code.
private static string GenerateModel(string name, OpenApiSchema schema)
{
- var properties = schema
- .Properties.Select(p =>
+ var properties = (schema.Properties ?? new Dictionary())
+ .Select(p =>
{
var propName = CodeGenerationHelpers.ToPascalCase(p.Key);
var propType = MapOpenApiType(p.Value);
- var propDesc = p.Value.Description ?? propName;
+ var propDesc = (p.Value as OpenApiSchema)?.Description ?? propName;
return $" /// {propDesc}\n public {propType} {propName} {{ get; set; }}";
})
.ToList();
@@ -60,7 +64,7 @@ public class {{name}}
/// Maps an OpenAPI schema to a C# type.
/// The OpenAPI schema.
/// The C# type name.
- public static string MapOpenApiType(OpenApiSchema? schema)
+ public static string MapOpenApiType(IOpenApiSchema? schema)
{
if (schema == null)
{
@@ -68,28 +72,39 @@ public static string MapOpenApiType(OpenApiSchema? schema)
}
// Check for schema reference first
- if (schema.Reference != null)
+ if (schema is OpenApiSchemaReference schemaRef)
+ {
+ return schemaRef.Reference.Id ?? "object";
+ }
+
+ if (schema is not OpenApiSchema schemaObj)
{
- return schema.Reference.Id;
+ return "object";
}
// Handle arrays
- if (schema.Type == "array")
+ if (schemaObj.Type == JsonSchemaType.Array)
{
- return schema.Items?.Reference != null ? $"List<{schema.Items.Reference.Id}>"
- : schema.Items?.Type == "string" ? "List"
- : schema.Items?.Type == "integer" ? "List"
- : schema.Items?.Type == "number" ? "List"
+ return schemaObj.Items is OpenApiSchemaReference itemsRef
+ ? $"List<{itemsRef.Reference.Id ?? "object"}>"
+ : schemaObj.Items is OpenApiSchema items
+ ? items.Type switch
+ {
+ JsonSchemaType.String => "List",
+ JsonSchemaType.Integer => "List",
+ JsonSchemaType.Number => "List",
+ _ => "List