Skip to content
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

Add customized document serialization support #2677

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
89c445d
Added ISwaggerDocumentSerializer
Jun 29, 2023
c9f6721
- Fix breaking binary changes
Apr 15, 2024
7522848
Removed dummy test
Apr 15, 2024
a02dfa8
Some more code review fixes
Apr 15, 2024
471e800
Removed whitespaces
Apr 15, 2024
e40a1ca
Added some tests
Apr 17, 2024
082a1c4
Removed debug code
Apr 17, 2024
df70359
Added tests on DocumentProvider
Apr 17, 2024
3bf2a65
removed unused configurations
Apr 17, 2024
f8b6128
More code review comments resolved
Apr 17, 2024
dd7e74f
Fixed dunglish
Apr 17, 2024
90c7c4d
Fixed merge issues
Apr 24, 2024
bc6bf47
Fix null reference
Apr 24, 2024
284476f
fixed call to SwaggerMiddleware
Apr 24, 2024
0b46d4c
Fixed SwaggerBuilderExtensions
Apr 25, 2024
82985cd
Simplified TemplateBinder usage
Apr 25, 2024
65f2d88
Switched to using SwaggerOptions to configure the document serializer
Apr 25, 2024
c771dc9
Merge pull request #2 from remcolam/feature/#2668-add-custom-document…
remcolam Apr 25, 2024
ca28721
Update src/Swashbuckle.AspNetCore.Swagger/SwaggerOptions.cs
remcolam Apr 25, 2024
c5147d8
Update src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/Swagger…
remcolam Apr 25, 2024
03ecdc7
review comments
Apr 25, 2024
144e41e
More code review items
Apr 25, 2024
e2048bf
Fixed comments
Apr 25, 2024
5c1193e
Some more tweaks
Apr 25, 2024
779b9d9
Update src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/Swagger…
remcolam Apr 25, 2024
90a1c32
Update src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/Swagger…
remcolam Apr 25, 2024
5be0578
Update src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/Swagger…
remcolam Apr 25, 2024
53eba1e
Update src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/Swagger…
remcolam Apr 25, 2024
04c3c32
Restored using that was needed
Apr 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 8 additions & 269 deletions Swashbuckle.AspNetCore.sln

Large diffs are not rendered by default.

35 changes: 28 additions & 7 deletions src/Swashbuckle.AspNetCore.Cli/Program.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
using System;
using System.Reflection;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.Loader;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Writers;
using Swashbuckle.AspNetCore.Swagger;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore;
using Microsoft.Extensions.Hosting;

namespace Swashbuckle.AspNetCore.Cli
{
Expand Down Expand Up @@ -87,6 +90,8 @@ public static int Main(string[] args)

// 3) Retrieve Swagger via configured provider
var swaggerProvider = serviceProvider.GetRequiredService<ISwaggerProvider>();
var swaggerOptions = serviceProvider.GetService<IOptions<SwaggerOptions>>();
var swaggerDocumentSerializer = swaggerOptions?.Value?.CustomDocumentSerializer;
var swagger = swaggerProvider.GetSwagger(
namedArgs["swaggerdoc"],
namedArgs.TryGetValue("--host", out var arg) ? arg : null,
Expand All @@ -97,7 +102,8 @@ public static int Main(string[] args)
? Path.Combine(Directory.GetCurrentDirectory(), arg1)
: null;

using (var streamWriter = (outputPath != null ? File.CreateText(outputPath) : Console.Out))
using (Stream stream = (outputPath != null ? File.OpenWrite(outputPath) : Console.OpenStandardOutput()))
using (var streamWriter = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture))
{
IOpenApiWriter writer;
if (namedArgs.ContainsKey("--yaml"))
Expand All @@ -106,9 +112,24 @@ public static int Main(string[] args)
writer = new OpenApiJsonWriter(streamWriter);

if (namedArgs.ContainsKey("--serializeasv2"))
swagger.SerializeAsV2(writer);
{
if (swaggerDocumentSerializer != null)
{
swaggerDocumentSerializer.SerializeDocument(swagger, writer, Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0);
}
else
{
swagger.SerializeAsV2(writer);
}
}
else if (swaggerDocumentSerializer != null)
{
swaggerDocumentSerializer.SerializeDocument(swagger, writer, Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0);
}
else
{
swagger.SerializeAsV3(writer);
}

if (outputPath != null)
Console.WriteLine($"Swagger JSON/YAML successfully written to {outputPath}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using Swashbuckle.AspNetCore.Swagger;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Extensions for helping with configuring instances of <see cref="SwaggerOptions"/>.
/// </summary>
public static class SwaggerOptionsExtensions
{
/// <summary>
/// Sets a custom Swagger document serializer to use.
/// </summary>
/// <remarks>For the CLI tool to be able to use this, this needs to be configured for use in the service collection of your application.</remarks>
/// <typeparam name="TDocumentSerializer">The type of the custom Swagger document serializer implementation.</typeparam>
/// <param name="swaggerOptions">The options to configure the serializer for.</param>
/// <param name="constructorParameters">The parameters to pass into the constructor of the custom Swagger document serializer implementation.</param>
public static void SetCustomDocumentSerializer<TDocumentSerializer>(
martincostello marked this conversation as resolved.
Show resolved Hide resolved
this SwaggerOptions swaggerOptions,
params object[] constructorParameters)
where TDocumentSerializer : ISwaggerDocumentSerializer
{
if (swaggerOptions == null)
{
throw new ArgumentNullException(nameof(swaggerOptions));
}
swaggerOptions.CustomDocumentSerializer = (TDocumentSerializer)Activator.CreateInstance(typeof(TDocumentSerializer), constructorParameters);
martincostello marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using Swashbuckle.AspNetCore.Swagger;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Extensions to configure dependencies for Swagger.
/// </summary>
public static class SwaggerServiceCollectionExtensions
{
/// <summary>
/// Configures Swagger options in the specified service collection.
/// </summary>
/// <param name="services">The service collection to configure the Swagger options for.</param>
/// <param name="setupAction">A delegate to a method to use to configure the Swagger options.</param>
public static void ConfigureSwagger(
martincostello marked this conversation as resolved.
Show resolved Hide resolved
this IServiceCollection services,
Action<SwaggerOptions> setupAction)
{
services.Configure(setupAction);
}
}
20 changes: 20 additions & 0 deletions src/Swashbuckle.AspNetCore.Swagger/ISwaggerDocumentSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.OpenApi;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Writers;

namespace Swashbuckle.AspNetCore.Swagger
{
/// <summary>
/// Provide an implementation for this interface if you wish to customize how the OpenAPI document is written.
/// </summary>
public interface ISwaggerDocumentSerializer
{
/// <summary>
/// Serializes an OpenAPI document.
/// </summary>
/// <param name="document">The OpenAPI document that should be serialized.</param>
/// <param name="writer">The writer to which the document needs to be written.</param>
/// <param name="specVersion">The OpenAPI specification version to serialize as.</param>
void SerializeDocument(OpenApiDocument document, IOpenApiWriter writer, OpenApiSpecVersion specVersion);
}
}
34 changes: 31 additions & 3 deletions src/Swashbuckle.AspNetCore.Swagger/SwaggerMiddleware.cs
martincostello marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
Expand All @@ -8,6 +9,7 @@
using Microsoft.AspNetCore.Routing.Patterns;
#endif
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Writers;

Expand Down Expand Up @@ -134,7 +136,20 @@ private async Task RespondWithSwaggerJson(HttpResponse response, OpenApiDocument
using (var textWriter = new StringWriter(CultureInfo.InvariantCulture))
{
var jsonWriter = new OpenApiJsonWriter(textWriter);
if (_options.SerializeAsV2) swagger.SerializeAsV2(jsonWriter); else swagger.SerializeAsV3(jsonWriter);
if (_options.SerializeAsV2)
{
if (_options.CustomDocumentSerializer != null)
_options.CustomDocumentSerializer.SerializeDocument(swagger, jsonWriter, Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0);
else
swagger.SerializeAsV2(jsonWriter);
}
else
{
if (_options.CustomDocumentSerializer != null)
_options.CustomDocumentSerializer.SerializeDocument(swagger, jsonWriter, Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0);
else
swagger.SerializeAsV3(jsonWriter);
}

await response.WriteAsync(textWriter.ToString(), new UTF8Encoding(false));
}
Expand All @@ -148,7 +163,20 @@ private async Task RespondWithSwaggerYaml(HttpResponse response, OpenApiDocument
using (var textWriter = new StringWriter(CultureInfo.InvariantCulture))
{
var yamlWriter = new OpenApiYamlWriter(textWriter);
if (_options.SerializeAsV2) swagger.SerializeAsV2(yamlWriter); else swagger.SerializeAsV3(yamlWriter);
if (_options.SerializeAsV2)
{
if (_options.CustomDocumentSerializer != null)
_options.CustomDocumentSerializer.SerializeDocument(swagger, yamlWriter, Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0);
else
swagger.SerializeAsV2(yamlWriter);
}
else
{
if (_options.CustomDocumentSerializer != null)
_options.CustomDocumentSerializer.SerializeDocument(swagger, yamlWriter, Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0);
else
swagger.SerializeAsV3(yamlWriter);
}

await response.WriteAsync(textWriter.ToString(), new UTF8Encoding(false));
}
Expand Down
8 changes: 7 additions & 1 deletion src/Swashbuckle.AspNetCore.Swagger/SwaggerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ public SwaggerOptions()
/// </summary>
public bool SerializeAsV2 { get; set; }

/// <summary>
/// Gets or sets an optional custom <see cref="ISwaggerDocumentSerializer"/> implementation to use to serialize Swagger documents.
/// </summary>
/// <remarks>For the CLI tool to be able to use this, this needs to be configured for use in the service collection of your application.</remarks>
public ISwaggerDocumentSerializer CustomDocumentSerializer { get; set; }

/// <summary>
/// Actions that can be applied to an OpenApiDocument before it's serialized.
/// Useful for setting metadata that's derived from the current request
/// Useful for setting metadata that's derived from the current request.
/// </summary>
public List<Action<OpenApiDocument, HttpRequest>> PreSerializeFilters { get; private set; }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Writers;
using Swashbuckle.AspNetCore.Swagger;
Expand Down Expand Up @@ -47,11 +49,17 @@ public async Task GenerateAsync(string documentName, TextWriter writer)
var jsonWriter = new OpenApiJsonWriter(writer);
if (_options.SerializeAsV2)
{
swagger.SerializeAsV2(jsonWriter);
if (_options.CustomDocumentSerializer != null)
_options.CustomDocumentSerializer.SerializeDocument(swagger, jsonWriter, OpenApi.OpenApiSpecVersion.OpenApi2_0);
else
swagger.SerializeAsV2(jsonWriter);
}
else
{
swagger.SerializeAsV3(jsonWriter);
if (_options.CustomDocumentSerializer != null)
_options.CustomDocumentSerializer.SerializeDocument(swagger, jsonWriter, OpenApi.OpenApiSpecVersion.OpenApi3_0);
else
swagger.SerializeAsV3(jsonWriter);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ public class FilterDescriptor

public object[] Arguments { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Swashbuckle.AspNetCore.TestSupport\Swashbuckle.AspNetCore.TestSupport.csproj" />
<ProjectReference Include="..\WebSites\Basic\Basic.csproj" />
<ProjectReference Include="..\WebSites\CustomDocumentSerializer\CustomDocumentSerializer.csproj" />
<ProjectReference Include="..\WebSites\MinimalApp\MinimalApp.csproj" />
<ProjectReference Include="..\..\src\Swashbuckle.AspNetCore.Cli\Swashbuckle.AspNetCore.Cli.csproj" />
</ItemGroup>
Expand Down
82 changes: 48 additions & 34 deletions test/Swashbuckle.AspNetCore.Cli.Test/ToolTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.IO;
using System.Text.Json;
using Swashbuckle.AspNetCore.TestSupport.Utilities;
using Xunit;

namespace Swashbuckle.AspNetCore.Cli.Test
Expand All @@ -16,46 +17,59 @@ public void Throws_When_Startup_Assembly_Does_Not_Exist()
[Fact]
public void Can_Generate_Swagger_Json()
{
var dir = Directory.CreateDirectory(Path.Join(Path.GetTempPath(), Path.GetRandomFileName()));
try
{
var args = new string[] { "tofile", "--output", $"{dir}/swagger.json", "--serializeasv2", Path.Combine(Directory.GetCurrentDirectory(), "Basic.dll"), "v1" };
Assert.Equal(0, Program.Main(args));

using var document = JsonDocument.Parse(File.ReadAllText(Path.Combine(dir.FullName, "swagger.json")));

// verify one of the endpoints
var paths = document.RootElement.GetProperty("paths");
var productsPath = paths.GetProperty("/products");
Assert.True(productsPath.TryGetProperty("post", out _));
}
finally
{
dir.Delete(true);
}
using var temporaryDirectory = new TemporaryDirectory();
var args = new string[] { "tofile", "--output", $"{temporaryDirectory.Path}/swagger.json", "--serializeasv2", Path.Combine(Directory.GetCurrentDirectory(), "Basic.dll"), "v1" };
Assert.Equal(0, Program.Main(args));
using var document = JsonDocument.Parse(File.ReadAllText(Path.Combine(temporaryDirectory.Path, "swagger.json")));

// verify one of the endpoints
var paths = document.RootElement.GetProperty("paths");
var productsPath = paths.GetProperty("/products");
Assert.True(productsPath.TryGetProperty("post", out _));
}

[Fact]
public void CustomDocumentSerializer_Writes_Custom_V2_Document()
{
using var temporaryDirectory = new TemporaryDirectory();
var args = new string[] { "tofile", "--output", $"{temporaryDirectory.Path}/swagger.json", "--serializeasv2", Path.Combine(Directory.GetCurrentDirectory(), "CustomDocumentSerializer.dll"), "v1" };
Assert.Equal(0, Program.Main(args));

using var document = JsonDocument.Parse(File.ReadAllText(Path.Combine(temporaryDirectory.Path, "swagger.json")));

// verify that the custom serializer wrote the swagger info
var swaggerInfo = document.RootElement.GetProperty("swagger").GetString();
Assert.Equal("DocumentSerializerTest2.0", swaggerInfo);
}

[Fact]
public void CustomDocumentSerializer_Writes_Custom_V3_Document()
{
using var temporaryDirectory = new TemporaryDirectory();
var args = new string[] { "tofile", "--output", $"{temporaryDirectory.Path}/swagger.json", Path.Combine(Directory.GetCurrentDirectory(), "CustomDocumentSerializer.dll"), "v1" };
Assert.Equal(0, Program.Main(args));

using var document = JsonDocument.Parse(File.ReadAllText(Path.Combine(temporaryDirectory.Path, "swagger.json")));

// verify that the custom serializer wrote the swagger info
var swaggerInfo = document.RootElement.GetProperty("swagger").GetString();
Assert.Equal("DocumentSerializerTest3.0", swaggerInfo);
}

#if NET6_0_OR_GREATER
[Fact]
public void Can_Generate_Swagger_Json_ForTopLevelApp()
{
var dir = Directory.CreateDirectory(Path.Join(Path.GetTempPath(), Path.GetRandomFileName()));
try
{
var args = new string[] { "tofile", "--output", $"{dir}/swagger.json", "--serializeasv2", Path.Combine(Directory.GetCurrentDirectory(), "MinimalApp.dll"), "v1" };
Assert.Equal(0, Program.Main(args));

using var document = JsonDocument.Parse(File.ReadAllText(Path.Combine(dir.FullName, "swagger.json")));

// verify one of the endpoints
var paths = document.RootElement.GetProperty("paths");
var path = paths.GetProperty("/WeatherForecast");
Assert.True(path.TryGetProperty("get", out _));
}
finally
{
dir.Delete(true);
}
using var temporaryDirectory = new TemporaryDirectory();
var args = new string[] { "tofile", "--output", $"{temporaryDirectory.Path}/swagger.json", "--serializeasv2", Path.Combine(Directory.GetCurrentDirectory(), "MinimalApp.dll"), "v1" };
Assert.Equal(0, Program.Main(args));

using var document = JsonDocument.Parse(File.ReadAllText(Path.Combine(temporaryDirectory.Path, "swagger.json")));

// verify one of the endpoints
var paths = document.RootElement.GetProperty("paths");
var path = paths.GetProperty("/WeatherForecast");
Assert.True(path.TryGetProperty("get", out _));
}
#endif
}
Expand Down
Loading