Skip to content

Commit 643da51

Browse files
Use the proper generator
1 parent 16ca4d8 commit 643da51

File tree

12 files changed

+7074
-71
lines changed

12 files changed

+7074
-71
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"-u",
3434
"${workspaceFolder}/Samples/NucliaDbClient/api.yaml",
3535
"-o",
36-
"${workspaceFolder}/Samples/RestClient.OpenApiGenerator.Sample.NucliaDB/Generated",
36+
"${workspaceFolder}/Samples/NucliaDbClient/Generated",
3737
"-n",
3838
"NucliaDB.Generated",
3939
"-c",

RestClient.Net.OpenApiGenerator.Tests/RestClient.Net.OpenApiGenerator.Tests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
12-
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.28" />
1312
<PackageReference Include="MSTest" Version="3.6.4" />
1413
</ItemGroup>
1514

RestClient.Net.OpenApiGenerator/ExtensionMethodGenerator.cs

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.OpenApi.Models;
1+
using Microsoft.OpenApi;
22

33
namespace RestClient.Net.OpenApiGenerator;
44

@@ -26,10 +26,15 @@ string basePath
2626

2727
foreach (var path in document.Paths)
2828
{
29+
if (path.Value?.Operations == null)
30+
{
31+
continue;
32+
}
33+
2934
foreach (var operation in path.Value.Operations)
3035
{
3136
var responseType = GetResponseType(operation.Value);
32-
var isDelete = operation.Key == OperationType.Delete;
37+
var isDelete = operation.Key == HttpMethod.Delete;
3338
var resultResponseType = isDelete ? "Unit" : responseType;
3439
_ = responseTypes.Add(resultResponseType);
3540

@@ -123,7 +128,7 @@ private static async Task<string> DeserializeError(
123128

124129
private static (string Method, List<string> PrivateFunctions) GenerateMethod(
125130
string path,
126-
OperationType operationType,
131+
HttpMethod operationType,
127132
OpenApiOperation operation,
128133
string basePath
129134
)
@@ -148,7 +153,7 @@ string basePath
148153

149154
#pragma warning disable CA1502 // Avoid excessive complexity - code generator method is inherently complex
150155
private static (string Method, List<string> PrivateFunctions) GenerateHttpMethod(
151-
OperationType operationType,
156+
HttpMethod operationType,
152157
string methodName,
153158
string path,
154159
List<(string Name, string Type, bool IsPath)> parameters,
@@ -159,8 +164,10 @@ string summary
159164
#pragma warning restore CA1502
160165
{
161166
var hasBody =
162-
operationType is OperationType.Post or OperationType.Put or OperationType.Patch;
163-
var isDelete = operationType == OperationType.Delete;
167+
operationType == HttpMethod.Post
168+
|| operationType == HttpMethod.Put
169+
|| operationType == HttpMethod.Patch;
170+
var isDelete = operationType == HttpMethod.Delete;
164171
var hasPathParams = parameters.Any(p => p.IsPath);
165172
var queryParams = parameters.Where(p => !p.IsPath).ToList();
166173
var hasQueryParams = queryParams.Count > 0;
@@ -175,7 +182,12 @@ string summary
175182
? "?" + string.Join("&", queryParams.Select(q => $"{q.Name}={{{q.Name}}}"))
176183
: string.Empty;
177184

178-
var verb = operationType.ToString();
185+
var verb = operationType == HttpMethod.Get ? "Get"
186+
: operationType == HttpMethod.Post ? "Post"
187+
: operationType == HttpMethod.Put ? "Put"
188+
: operationType == HttpMethod.Delete ? "Delete"
189+
: operationType == HttpMethod.Patch ? "Patch"
190+
: operationType.Method;
179191
var createMethod = $"Create{verb}";
180192
var delegateType = $"{verb}Async";
181193

@@ -369,7 +381,7 @@ string summary
369381

370382
private static string GetMethodName(
371383
OpenApiOperation operation,
372-
OperationType operationType,
384+
HttpMethod operationType,
373385
string path
374386
)
375387
{
@@ -382,15 +394,15 @@ string path
382394
path.Split('/').LastOrDefault(p => !string.IsNullOrEmpty(p) && !p.StartsWith('{'))
383395
?? "Resource";
384396

385-
return operationType switch
386-
{
387-
OperationType.Get => $"Get{CodeGenerationHelpers.ToPascalCase(pathPart)}",
388-
OperationType.Post => $"Create{CodeGenerationHelpers.ToPascalCase(pathPart)}",
389-
OperationType.Put => $"Update{CodeGenerationHelpers.ToPascalCase(pathPart)}",
390-
OperationType.Delete => $"Delete{CodeGenerationHelpers.ToPascalCase(pathPart)}",
391-
OperationType.Patch => $"Patch{CodeGenerationHelpers.ToPascalCase(pathPart)}",
392-
_ => $"{operationType}{CodeGenerationHelpers.ToPascalCase(pathPart)}",
393-
};
397+
var methodName =
398+
operationType == HttpMethod.Get ? "Get"
399+
: operationType == HttpMethod.Post ? "Create"
400+
: operationType == HttpMethod.Put ? "Update"
401+
: operationType == HttpMethod.Delete ? "Delete"
402+
: operationType == HttpMethod.Patch ? "Patch"
403+
: operationType.Method;
404+
405+
return $"{methodName}{CodeGenerationHelpers.ToPascalCase(pathPart)}";
394406
}
395407

396408
private static List<(string Name, string Type, bool IsPath)> GetParameters(
@@ -399,8 +411,18 @@ OpenApiOperation operation
399411
{
400412
var parameters = new List<(string Name, string Type, bool IsPath)>();
401413

414+
if (operation.Parameters == null)
415+
{
416+
return parameters;
417+
}
418+
402419
foreach (var param in operation.Parameters)
403420
{
421+
if (param.Name == null)
422+
{
423+
continue;
424+
}
425+
404426
var isPath = param.In == ParameterLocation.Path;
405427
var type = ModelGenerator.MapOpenApiType(param.Schema);
406428
parameters.Add((param.Name, type, isPath));
@@ -409,11 +431,18 @@ OpenApiOperation operation
409431
return parameters;
410432
}
411433

412-
private static string? GetRequestBodyType(OpenApiOperation operation) =>
413-
operation.RequestBody == null ? null
414-
: operation.RequestBody.Content.FirstOrDefault().Value?.Schema?.Reference != null
415-
? operation.RequestBody.Content.FirstOrDefault().Value.Schema.Reference.Id
416-
: "object";
434+
private static string? GetRequestBodyType(OpenApiOperation operation)
435+
{
436+
if (operation.RequestBody?.Content == null)
437+
{
438+
return null;
439+
}
440+
441+
var firstContent = operation.RequestBody.Content.FirstOrDefault();
442+
return firstContent.Value?.Schema is OpenApiSchemaReference schemaRef
443+
? schemaRef.Reference.Id ?? "object"
444+
: "object";
445+
}
417446

418447
private static string GenerateTypeAliasesFile(HashSet<string> responseTypes, string @namespace)
419448
{
@@ -488,20 +517,22 @@ _ when responseType.StartsWith("List<", StringComparison.Ordinal) =>
488517

489518
private static string GetResponseType(OpenApiOperation operation)
490519
{
491-
var successResponse = operation.Responses.FirstOrDefault(r =>
520+
var successResponse = operation.Responses?.FirstOrDefault(r =>
492521
r.Key.StartsWith('2') || r.Key == "default"
493522
);
494523

495-
if (successResponse.Value == null)
524+
if (successResponse?.Value?.Content == null)
496525
{
497526
return "object";
498527
}
499528

500-
var content = successResponse.Value.Content.FirstOrDefault();
501-
return content.Value?.Schema?.Reference != null ? content.Value.Schema.Reference.Id
502-
: content.Value?.Schema?.Type == "array"
503-
&& content.Value.Schema.Items?.Reference != null
504-
? $"List<{content.Value.Schema.Items.Reference.Id}>"
529+
var content = successResponse.Value.Value.Content.FirstOrDefault();
530+
return content.Value?.Schema is OpenApiSchemaReference schemaRef
531+
? schemaRef.Reference.Id ?? "object"
532+
: content.Value?.Schema is OpenApiSchema schema
533+
&& schema.Type == JsonSchemaType.Array
534+
&& schema.Items is OpenApiSchemaReference itemsRef
535+
? $"List<{itemsRef.Reference.Id ?? "object"}>"
505536
: ModelGenerator.MapOpenApiType(content.Value?.Schema);
506537
}
507538
}

RestClient.Net.OpenApiGenerator/ModelGenerator.cs

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.OpenApi.Models;
1+
using Microsoft.OpenApi;
22

33
namespace RestClient.Net.OpenApiGenerator;
44

@@ -13,12 +13,16 @@ public static string GenerateModels(OpenApiDocument document, string @namespace)
1313
{
1414
var models = new List<string>();
1515

16-
foreach (
17-
var schema in document.Components?.Schemas ?? new Dictionary<string, OpenApiSchema>()
18-
)
16+
if (document.Components?.Schemas != null)
1917
{
20-
var model = GenerateModel(schema.Key, schema.Value);
21-
models.Add(model);
18+
foreach (var schema in document.Components.Schemas)
19+
{
20+
if (schema.Value is OpenApiSchema schemaObj)
21+
{
22+
var model = GenerateModel(schema.Key, schemaObj);
23+
models.Add(model);
24+
}
25+
}
2226
}
2327

2428
var modelsCode = string.Join("\n\n", models);
@@ -36,12 +40,12 @@ namespace {{@namespace}};
3640
/// <returns>The generated model code.</returns>
3741
private static string GenerateModel(string name, OpenApiSchema schema)
3842
{
39-
var properties = schema
40-
.Properties.Select(p =>
43+
var properties = (schema.Properties ?? new Dictionary<string, IOpenApiSchema>())
44+
.Select(p =>
4145
{
4246
var propName = CodeGenerationHelpers.ToPascalCase(p.Key);
4347
var propType = MapOpenApiType(p.Value);
44-
var propDesc = p.Value.Description ?? propName;
48+
var propDesc = (p.Value as OpenApiSchema)?.Description ?? propName;
4549
return $" /// <summary>{propDesc}</summary>\n public {propType} {propName} {{ get; set; }}";
4650
})
4751
.ToList();
@@ -60,36 +64,47 @@ public class {{name}}
6064
/// <summary>Maps an OpenAPI schema to a C# type.</summary>
6165
/// <param name="schema">The OpenAPI schema.</param>
6266
/// <returns>The C# type name.</returns>
63-
public static string MapOpenApiType(OpenApiSchema? schema)
67+
public static string MapOpenApiType(IOpenApiSchema? schema)
6468
{
6569
if (schema == null)
6670
{
6771
return "object";
6872
}
6973

7074
// Check for schema reference first
71-
if (schema.Reference != null)
75+
if (schema is OpenApiSchemaReference schemaRef)
76+
{
77+
return schemaRef.Reference.Id ?? "object";
78+
}
79+
80+
if (schema is not OpenApiSchema schemaObj)
7281
{
73-
return schema.Reference.Id;
82+
return "object";
7483
}
7584

7685
// Handle arrays
77-
if (schema.Type == "array")
86+
if (schemaObj.Type == JsonSchemaType.Array)
7887
{
79-
return schema.Items?.Reference != null ? $"List<{schema.Items.Reference.Id}>"
80-
: schema.Items?.Type == "string" ? "List<string>"
81-
: schema.Items?.Type == "integer" ? "List<int>"
82-
: schema.Items?.Type == "number" ? "List<double>"
88+
return schemaObj.Items is OpenApiSchemaReference itemsRef
89+
? $"List<{itemsRef.Reference.Id ?? "object"}>"
90+
: schemaObj.Items is OpenApiSchema items
91+
? items.Type switch
92+
{
93+
JsonSchemaType.String => "List<string>",
94+
JsonSchemaType.Integer => "List<int>",
95+
JsonSchemaType.Number => "List<double>",
96+
_ => "List<object>",
97+
}
8398
: "List<object>";
8499
}
85100

86101
// Handle primitive types
87-
return schema.Type switch
102+
return schemaObj.Type switch
88103
{
89-
"integer" => schema.Format == "int64" ? "long" : "int",
90-
"number" => schema.Format == "double" ? "double" : "float",
91-
"string" => "string",
92-
"boolean" => "bool",
104+
JsonSchemaType.Integer => schemaObj.Format == "int64" ? "long" : "int",
105+
JsonSchemaType.Number => schemaObj.Format == "double" ? "double" : "float",
106+
JsonSchemaType.String => "string",
107+
JsonSchemaType.Boolean => "bool",
93108
_ => "object",
94109
};
95110
}

RestClient.Net.OpenApiGenerator/OpenApiCodeGenerator.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using Microsoft.OpenApi.Readers;
2-
using Microsoft.OpenApi.Validations;
1+
using Microsoft.OpenApi;
2+
using Microsoft.OpenApi.Reader;
33
using ErrorUrl = Outcome.Result<(string, string), string>.Error<(string, string), string>;
44
using GeneratorError = Outcome.Result<
55
RestClient.Net.OpenApiGenerator.GeneratorResult,
@@ -15,7 +15,10 @@
1515

1616
namespace RestClient.Net.OpenApiGenerator;
1717

18-
/// <summary>Generates C# extension methods from OpenAPI specifications.</summary>
18+
/// <summary>Generates C# extension methods from OpenAPI specifications.
19+
/// This uses the Microsoft.OpenApi library to parse OpenAPI documents and generate code https://github.com/microsoft/OpenAPI.NET.
20+
/// See the tests here https://github.com/microsoft/OpenAPI.NET/tree/main/test/Microsoft.OpenApi.Tests to see how the API works.
21+
/// </summary>
1922
public static class OpenApiCodeGenerator
2023
{
2124
/// <summary>Generates code from an OpenAPI document.</summary>
@@ -37,25 +40,24 @@ public static Outcome.Result<GeneratorResult, string> Generate(
3740
{
3841
try
3942
{
40-
var document = new OpenApiStringReader(
41-
new OpenApiReaderSettings
42-
{
43-
ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences,
44-
RuleSet = ValidationRuleSet.GetDefaultRuleSet(),
45-
}
46-
).Read(openApiContent, out var diagnostic);
43+
var settings = new OpenApiReaderSettings();
44+
settings.AddYamlReader();
4745

48-
if (diagnostic.Errors.Count > 0)
46+
var readResult = OpenApiDocument.Parse(openApiContent, settings: settings);
47+
48+
if (readResult.Diagnostic?.Errors.Count > 0)
4949
{
50-
var errors = string.Join(", ", diagnostic.Errors.Select(e => e.Message));
50+
var errors = string.Join(", ", readResult.Diagnostic.Errors.Select(e => e.Message));
5151
return new GeneratorError($"Error parsing OpenAPI: {errors}");
5252
}
5353

54-
if (document == null)
54+
if (readResult.Document == null)
5555
{
5656
return new GeneratorError("Error parsing OpenAPI: Document is null");
5757
}
5858

59+
var document = readResult.Document;
60+
5961
var urlResult = UrlParser.GetBaseUrlAndPath(document, baseUrlOverride);
6062

6163
return urlResult switch
@@ -80,7 +82,7 @@ public static Outcome.Result<GeneratorResult, string> Generate(
8082
}
8183

8284
private static GeneratorOk GenerateCodeFiles(
83-
Microsoft.OpenApi.Models.OpenApiDocument document,
85+
OpenApiDocument document,
8486
string @namespace,
8587
string className,
8688
string outputPath,

RestClient.Net.OpenApiGenerator/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,8 @@ Test coverage includes:
340340

341341
## Dependencies
342342

343-
- Microsoft.OpenApi.Readers (1.6.22) - OpenAPI parsing
344343
- Microsoft.CodeAnalysis.CSharp (4.11.0) - Code generation
344+
- https://github.com/microsoft/OpenAPI.NET
345345

346346
## License
347347

RestClient.Net.OpenApiGenerator/RestClient.Net.OpenApiGenerator.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
</PropertyGroup>
66
<ItemGroup>
77
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
8-
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.28" />
8+
<PackageReference Include="Microsoft.OpenApi" Version="2.3.5" />
9+
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.5" />
910
</ItemGroup>
1011
<ItemGroup>
1112
<ProjectReference Include="..\RestClient.Net\RestClient.Net.csproj" />

RestClient.Net.OpenApiGenerator/UrlParser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.OpenApi.Models;
1+
using Microsoft.OpenApi;
22
using Outcome;
33

44
namespace RestClient.Net.OpenApiGenerator;
@@ -15,7 +15,7 @@ internal static class UrlParser
1515
string? baseUrlOverride
1616
)
1717
{
18-
var server = document.Servers.FirstOrDefault();
18+
var server = document.Servers?.FirstOrDefault();
1919

2020
if (server == null || string.IsNullOrWhiteSpace(server.Url))
2121
{

0 commit comments

Comments
 (0)