Skip to content

Commit

Permalink
Merge pull request #267 from christianhelle/multi-file-oas
Browse files Browse the repository at this point in the history
  • Loading branch information
christianhelle authored Jan 7, 2024
2 parents ababa83 + c224f6b commit a2a89aa
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 445 deletions.
18 changes: 18 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Default target
all: build

# Build target
build:
dotnet build --configuration Debug Refitter.sln

# Optional release build target
release:
dotnet build --configuration Release Refitter.sln

# Test target
test:
dotnet test --configuration Debug Refitter.sln

# Clean target
clean:
dotnet clean Refitter.sln
4 changes: 3 additions & 1 deletion src/Refitter.Core/DependencyInjectionGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ public static partial class IServiceCollectionExtensions
code.AppendLine();
}

code.Remove(code.Length - 2, 2);
#pragma warning disable RS1035
code.Remove(code.Length - Environment.NewLine.Length, Environment.NewLine.Length);
#pragma warning restore RS1035
code.AppendLine();
code.AppendLine(" return services;");
code.AppendLine(" }");
Expand Down
104 changes: 70 additions & 34 deletions src/Refitter.Core/OpenApiDocumentFactory.cs
Original file line number Diff line number Diff line change
@@ -1,73 +1,109 @@
using System.Net;

using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Readers;

using NSwag;

using OpenApiDocument = NSwag.OpenApiDocument;

namespace Refitter.Core;

/// <summary>
/// Creates an <see cref="OpenApiDocument"/> from a specified path or URL.
/// Creates an <see cref="NSwag.OpenApiDocument"/> from a specified path or URL.
/// </summary>
public static class OpenApiDocumentFactory
{
/// <summary>
/// Creates a new instance of the <see cref="OpenApiDocument"/> class asynchronously.
/// Creates a new instance of the <see cref="NSwag.OpenApiDocument"/> class asynchronously.
/// </summary>
/// <param name="settings">The settings used to configure the generator.</param>
/// <returns>A new instance of the <see cref="OpenApiDocument"/> class.</returns>
public static async Task<OpenApiDocument> CreateAsync(RefitGeneratorSettings settings)
/// <param name="openApiPath">The path or URL to the OpenAPI specification.</param>
/// <returns>A new instance of the <see cref="NSwag.OpenApiDocument"/> class.</returns>
public static async Task<OpenApiDocument> CreateAsync(string openApiPath)
{
OpenApiDocument document;
if (IsHttp(settings.OpenApiPath))
try
{
var content = await GetHttpContent(settings);
var readResult = await OpenApiMultiFileReader.Read(openApiPath);
if (!readResult.ContainedExternalReferences)
return await CreateUsingNSwagAsync(openApiPath);

if (IsYaml(settings.OpenApiPath))
{
document = await OpenApiYamlDocument.FromYamlAsync(content);
}
else
var specificationVersion = readResult.OpenApiDiagnostic.SpecificationVersion;
PopulateMissingRequiredFields(openApiPath, readResult);

if (IsYaml(openApiPath))
{
document = await OpenApiDocument.FromJsonAsync(content);
var yaml = readResult.OpenApiDocument.SerializeAsYaml(specificationVersion);
return await OpenApiYamlDocument.FromYamlAsync(yaml);
}

var json = readResult.OpenApiDocument.SerializeAsJson(specificationVersion);
return await OpenApiDocument.FromJsonAsync(json);
}
catch
{
return await CreateUsingNSwagAsync(openApiPath);
}
}

private static async Task<OpenApiDocument> CreateUsingNSwagAsync(string openApiPath)
{
if (IsHttp(openApiPath))
{
var content = await GetHttpContent(openApiPath);
return IsYaml(openApiPath)
? await OpenApiYamlDocument.FromYamlAsync(content)
: await OpenApiDocument.FromJsonAsync(content);
}
else

return IsYaml(openApiPath)
? await OpenApiYamlDocument.FromFileAsync(openApiPath)
: await OpenApiDocument.FromFileAsync(openApiPath);
}

private static void PopulateMissingRequiredFields(
string openApiPath,
Result readResult)
{
var document = readResult.OpenApiDocument;
if (document.Info is null)
{
if (IsYaml(settings.OpenApiPath))
document.Info = new Microsoft.OpenApi.Models.OpenApiInfo
{
document = await OpenApiYamlDocument.FromFileAsync(settings.OpenApiPath);
}
else
{
document = await OpenApiDocument.FromFileAsync(settings.OpenApiPath);
}
Title = Path.GetFileNameWithoutExtension(openApiPath),
Version = readResult.OpenApiDiagnostic.SpecificationVersion.GetDisplayName()
};
}
else
{
document.Info.Title ??= Path.GetFileNameWithoutExtension(openApiPath);
document.Info.Version ??= readResult.OpenApiDiagnostic.SpecificationVersion.GetDisplayName();
}
}

return document;
/// <summary>
/// Determines whether the specified path is an HTTP URL.
/// </summary>
/// <param name="path">The path to check.</param>
/// <returns>True if the path is an HTTP URL, otherwise false.</returns>
private static bool IsHttp(string path)
{
return path.StartsWith("http://") || path.StartsWith("https://");
}

/// <summary>
/// Gets the content of the URI as a string and decompresses it if necessary.
/// </summary>
/// <param name="settings">The settings used to configure the generator.</param>
/// <returns>The content of the HTTP request.</returns>
private static async Task<string> GetHttpContent(RefitGeneratorSettings settings)
private static async Task<string> GetHttpContent(string openApiPath)
{
var httpMessageHandler = new HttpClientHandler();
httpMessageHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
using var http = new HttpClient(httpMessageHandler);
var content = await http.GetStringAsync(settings.OpenApiPath);
var content = await http.GetStringAsync(openApiPath);
return content;
}

/// <summary>
/// Determines whether the specified path is an HTTP URL.
/// </summary>
/// <param name="path">The path to check.</param>
/// <returns>True if the path is an HTTP URL, otherwise false.</returns>
private static bool IsHttp(string path)
{
return path.StartsWith("http://") || path.StartsWith("https://");
}

/// <summary>
/// Determines whether the specified path is a YAML file.
Expand Down
21 changes: 17 additions & 4 deletions src/Refitter.Core/ParameterExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ public static IEnumerable<string> GetParameters(

var queryParameters = operationModel.Parameters
.Where(p => p.Kind == OpenApiParameterKind.Query)
.Select(p => $"{JoinAttributes(GetQueryAttribute(p, settings), GetAliasAsAttribute(p))}{GetBodyParameterType(p, settings)} {p.VariableName}")
.Select(p => $"{JoinAttributes(GetQueryAttribute(p, settings), GetAliasAsAttribute(p))}{GetQueryParameterType(p, settings)} {p.VariableName}")
.ToList();

var bodyParameters = operationModel.Parameters
.Where(p => p.Kind == OpenApiParameterKind.Body && !p.IsBinaryBodyParameter)
.Select(p => $"{JoinAttributes("Body", GetAliasAsAttribute(p))}{GetBodyParameterType(p, settings)} {p.VariableName}")
.Select(p => $"{JoinAttributes("Body", GetAliasAsAttribute(p))}{GetParameterType(p, settings)} {p.VariableName}")
.ToList();

var headerParameters = new List<string>();
Expand All @@ -32,7 +32,7 @@ public static IEnumerable<string> GetParameters(
{
headerParameters = operationModel.Parameters
.Where(p => p.Kind == OpenApiParameterKind.Header && p.IsHeader)
.Select(p => $"{JoinAttributes($"Header(\"{p.Name}\")")}{GetBodyParameterType(p, settings)} {p.VariableName}")
.Select(p => $"{JoinAttributes($"Header(\"{p.Name}\")")}{GetParameterType(p, settings)} {p.VariableName}")
.ToList();
}

Expand Down Expand Up @@ -98,7 +98,7 @@ private static string JoinAttributes(params string[] attributes)
return "[" + string.Join(", ", filteredAttributes) + "] ";
}

private static string GetBodyParameterType(
private static string GetParameterType(
ParameterModelBase parameterModel,
RefitGeneratorSettings settings)
{
Expand All @@ -115,6 +115,19 @@ private static string GetBodyParameterType(
return type;
}

private static string GetQueryParameterType(
ParameterModelBase parameterModel,
RefitGeneratorSettings settings)
{
var type = GetParameterType(parameterModel, settings);

if (parameterModel.IsQuery &&
parameterModel.Type.Equals("object", StringComparison.OrdinalIgnoreCase))
type = "string";

return type;
}

private static string FindSupportedType(string typeName) =>
typeName == "FileResponse" ? "StreamPart" : typeName;
}
9 changes: 6 additions & 3 deletions src/Refitter.Core/RefitGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
using System.Text;
using System.Text.RegularExpressions;

using Microsoft.OpenApi;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Readers;

namespace Refitter.Core;

/// <summary>
Expand All @@ -21,7 +25,6 @@ public static async Task<RefitGenerator> CreateAsync(RefitGeneratorSettings sett

ProcessTagFilters(openApiDocument, settings.IncludeTags);
ProcessPathFilters(openApiDocument, settings.IncludePathMatches);

ProcessContractFilter(openApiDocument, settings.TrimUnusedSchema, settings.KeepSchemaPatterns);

return new RefitGenerator(settings, openApiDocument);
Expand All @@ -33,9 +36,9 @@ private static async Task<OpenApiDocument> GetOpenApiDocument(RefitGeneratorSett
{
":"
};

return specialCharacters.Aggregate(
await OpenApiDocumentFactory.CreateAsync(settings),
await OpenApiDocumentFactory.CreateAsync(settings.OpenApiPath),
SanitizePath);
}

Expand Down
1 change: 1 addition & 0 deletions src/Refitter.Core/Refitter.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="13.20.0" />
<PackageReference Include="NSwag.Core.Yaml" Version="13.20.0" />
<PackageReference Include="OasReader" Version="1.6.11.12" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<PackageReference Include="H.Generators.Extensions" Version="1.22.0" PrivateAssets="all" />
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="13.20.0" PrivateAssets="all" />
<PackageReference Include="NSwag.Core.Yaml" Version="13.20.0" PrivateAssets="all" />
<PackageReference Include="OasReader" Version="1.6.11.12" />
<PackageReference Include="Refit" Version="7.0.0" />
</ItemGroup>

Expand Down
4 changes: 2 additions & 2 deletions src/Refitter.Tests/OpenApiDocumentFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class OpenApiDocumentFactoryTests
public async Task Create_From_Uri_Returns_NotNull(string url)
{
var settings = new RefitGeneratorSettings { OpenApiPath = url };
(await OpenApiDocumentFactory.CreateAsync(settings))
(await OpenApiDocumentFactory.CreateAsync(settings.OpenApiPath))
.Should()
.NotBeNull();
}
Expand All @@ -29,7 +29,7 @@ public async Task Create_From_File_Returns_NotNull(SampleOpenSpecifications vers
{
var swaggerFile = await TestFile.CreateSwaggerFile(EmbeddedResources.GetSwaggerPetstore(version), filename);
var settings = new RefitGeneratorSettings { OpenApiPath = swaggerFile };
(await OpenApiDocumentFactory.CreateAsync(settings))
(await OpenApiDocumentFactory.CreateAsync(settings.OpenApiPath))
.Should()
.NotBeNull();
}
Expand Down
28 changes: 1 addition & 27 deletions src/Refitter/GenerateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
using Microsoft.OpenApi.Models;

using Refitter.Core;
using Refitter.Merger;
using Refitter.OAS;
using Refitter.Validation;

using Spectre.Console;
Expand Down Expand Up @@ -67,7 +65,7 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
refitGeneratorSettings.OpenApiPath = settings.OpenApiPath!;
}

var generator = await CreateGenerator(refitGeneratorSettings);
var generator = await RefitGenerator.CreateAsync(refitGeneratorSettings);
if (!settings.SkipValidation)
await ValidateOpenApiSpec(refitGeneratorSettings.OpenApiPath);

Expand Down Expand Up @@ -111,30 +109,6 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
}
}

private static async Task<RefitGenerator> CreateGenerator(RefitGeneratorSettings refitGeneratorSettings)
{
var result = await OpenApiReader.ParseOpenApi(refitGeneratorSettings.OpenApiPath);
var document = result.OpenApiDocument;

if (!document.Paths.Any(pair => pair.Value.Parameters.Any(parameter => parameter.Reference?.IsExternal == true)))
{
return await RefitGenerator.CreateAsync(refitGeneratorSettings);
}

AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[yellow]OpenAPI specifications contains external references.[/]");
AnsiConsole.MarkupLine("[yellow]Attempting to merge of all external references into single document...[/]");

var contents = await OpenApiMerger.Merge(refitGeneratorSettings.OpenApiPath!);
var mergedPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
mergedPath = Path.ChangeExtension(mergedPath, Path.GetExtension(refitGeneratorSettings.OpenApiPath));
await File.WriteAllTextAsync(mergedPath, contents);

refitGeneratorSettings.OpenApiPath = mergedPath;

return await RefitGenerator.CreateAsync(refitGeneratorSettings);
}

private static void DonationBanner()
{
AnsiConsole.MarkupLine("[dim]###################################################################[/]");
Expand Down
47 changes: 0 additions & 47 deletions src/Refitter/Merger/OpenApiMerger.cs

This file was deleted.

Loading

0 comments on commit a2a89aa

Please sign in to comment.