Skip to content

Commit b022857

Browse files
committed
Support returning OpenAPI document in YAML from MapOpenApi (#58616)
* Support returning OpenAPI document in YAML from MapOpenApi * Update tests and YAML content type
1 parent f19e167 commit b022857

File tree

3 files changed

+43
-11
lines changed

3 files changed

+43
-11
lines changed

src/OpenApi/sample/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
var app = builder.Build();
3838

3939
app.MapOpenApi();
40+
app.MapOpenApi("/openapi/{documentName}.yaml");
4041
if (app.Environment.IsDevelopment())
4142
{
4243
app.MapSwaggerUi();

src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,16 @@ public static IEndpointConventionBuilder MapOpenApi(this IEndpointRouteBuilder e
4949
using var writer = Utf8BufferTextWriter.Get(output);
5050
try
5151
{
52-
document.Serialize(new OpenApiJsonWriter(writer), documentOptions.OpenApiVersion);
53-
context.Response.ContentType = "application/json;charset=utf-8";
52+
if (UseYaml(pattern))
53+
{
54+
document.Serialize(new OpenApiYamlWriter(writer), documentOptions.OpenApiVersion);
55+
context.Response.ContentType = "text/plain+yaml;charset=utf-8";
56+
}
57+
else
58+
{
59+
document.Serialize(new OpenApiJsonWriter(writer), documentOptions.OpenApiVersion);
60+
context.Response.ContentType = "application/json;charset=utf-8";
61+
}
5462
await context.Response.BodyWriter.WriteAsync(output.ToArray(), context.RequestAborted);
5563
await context.Response.BodyWriter.FlushAsync(context.RequestAborted);
5664
}
@@ -63,4 +71,8 @@ public static IEndpointConventionBuilder MapOpenApi(this IEndpointRouteBuilder e
6371
}
6472
}).ExcludeFromDescription();
6573
}
74+
75+
private static bool UseYaml(string pattern) =>
76+
pattern.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) ||
77+
pattern.EndsWith(".yml", StringComparison.OrdinalIgnoreCase);
6678
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiEndpointRouteBuilderExtensionsTests.cs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ public void MapOpenApi_ReturnsEndpointConventionBuilder()
2929
Assert.IsAssignableFrom<IEndpointConventionBuilder>(returnedBuilder);
3030
}
3131

32-
[Fact]
33-
public void MapOpenApi_SupportsCustomizingPath()
32+
[Theory]
33+
[InlineData("/custom/{documentName}/openapi.json")]
34+
[InlineData("/custom/{documentName}/openapi.yaml")]
35+
[InlineData("/custom/{documentName}/openapi.yml")]
36+
public void MapOpenApi_SupportsCustomizingPath(string expectedPath)
3437
{
3538
// Arrange
36-
var expectedPath = "/custom/{documentName}/openapi.json";
3739
var serviceProvider = CreateServiceProvider();
3840
var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider));
3941

@@ -72,13 +74,17 @@ public async Task MapOpenApi_ReturnsRenderedDocument()
7274
});
7375
}
7476

75-
[Fact]
76-
public async Task MapOpenApi_ReturnsDefaultDocumentIfNoNameProvided()
77+
[Theory]
78+
[InlineData("/openapi.json", "application/json;charset=utf-8", false)]
79+
[InlineData("/openapi.toml", "application/json;charset=utf-8", false)]
80+
[InlineData("/openapi.yaml", "text/plain+yaml;charset=utf-8", true)]
81+
[InlineData("/openapi.yml", "text/plain+yaml;charset=utf-8", true)]
82+
public async Task MapOpenApi_ReturnsDefaultDocumentIfNoNameProvided(string expectedPath, string expectedContentType, bool isYaml)
7783
{
7884
// Arrange
7985
var serviceProvider = CreateServiceProvider();
8086
var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider));
81-
builder.MapOpenApi("/openapi.json");
87+
builder.MapOpenApi(expectedPath);
8288
var context = new DefaultHttpContext();
8389
var responseBodyStream = new MemoryStream();
8490
context.Response.Body = responseBodyStream;
@@ -91,6 +97,11 @@ public async Task MapOpenApi_ReturnsDefaultDocumentIfNoNameProvided()
9197

9298
// Assert
9399
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
100+
Assert.Equal(expectedContentType, context.Response.ContentType);
101+
var responseString = Encoding.UTF8.GetString(responseBodyStream.ToArray());
102+
// String check to validate that generated document starts with YAML syntax
103+
Assert.Equal(isYaml, responseString.StartsWith("openapi: 3.0.1", StringComparison.OrdinalIgnoreCase));
104+
responseBodyStream.Position = 0;
94105
ValidateOpenApiDocument(responseBodyStream, document =>
95106
{
96107
Assert.Equal("OpenApiEndpointRouteBuilderExtensionsTests | v1", document.Info.Title);
@@ -121,16 +132,19 @@ public async Task MapOpenApi_Returns404ForUnresolvedDocument()
121132
Assert.Equal("No OpenAPI document with the name 'v2' was found.", Encoding.UTF8.GetString(responseBodyStream.ToArray()));
122133
}
123134

124-
[Fact]
125-
public async Task MapOpenApi_ReturnsDocumentIfNameProvidedInQuery()
135+
[Theory]
136+
[InlineData("/openapi.json", "application/json;charset=utf-8", false)]
137+
[InlineData("/openapi.yaml", "text/plain+yaml;charset=utf-8", true)]
138+
[InlineData("/openapi.yml", "text/plain+yaml;charset=utf-8", true)]
139+
public async Task MapOpenApi_ReturnsDocumentIfNameProvidedInQuery(string expectedPath, string expectedContentType, bool isYaml)
126140
{
127141
// Arrange
128142
var documentName = "v2";
129143
var hostEnvironment = new HostEnvironment() { ApplicationName = nameof(OpenApiEndpointRouteBuilderExtensionsTests) };
130144
var serviceProviderIsService = new ServiceProviderIsService();
131145
var serviceProvider = CreateServiceProvider(documentName);
132146
var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider));
133-
builder.MapOpenApi("/openapi.json");
147+
builder.MapOpenApi(expectedPath);
134148
var context = new DefaultHttpContext();
135149
var responseBodyStream = new MemoryStream();
136150
context.Response.Body = responseBodyStream;
@@ -144,6 +158,11 @@ public async Task MapOpenApi_ReturnsDocumentIfNameProvidedInQuery()
144158

145159
// Assert
146160
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
161+
Assert.Equal(expectedContentType, context.Response.ContentType);
162+
var responseString = Encoding.UTF8.GetString(responseBodyStream.ToArray());
163+
// String check to validate that generated document starts with YAML syntax
164+
Assert.Equal(isYaml, responseString.StartsWith("openapi: 3.0.1", StringComparison.OrdinalIgnoreCase));
165+
responseBodyStream.Position = 0;
147166
ValidateOpenApiDocument(responseBodyStream, document =>
148167
{
149168
Assert.Equal($"OpenApiEndpointRouteBuilderExtensionsTests | {documentName}", document.Info.Title);

0 commit comments

Comments
 (0)