diff --git a/src/OpenApi/sample/Program.cs b/src/OpenApi/sample/Program.cs index a622780ff482..8a6182c75b0c 100644 --- a/src/OpenApi/sample/Program.cs +++ b/src/OpenApi/sample/Program.cs @@ -37,6 +37,7 @@ var app = builder.Build(); app.MapOpenApi(); +app.MapOpenApi("/openapi/{documentName}.yaml"); if (app.Environment.IsDevelopment()) { app.MapSwaggerUi(); diff --git a/src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs b/src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs index c5bed38669e4..def54f8c8d95 100644 --- a/src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs +++ b/src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs @@ -49,8 +49,16 @@ public static IEndpointConventionBuilder MapOpenApi(this IEndpointRouteBuilder e using var writer = Utf8BufferTextWriter.Get(output); try { - document.Serialize(new OpenApiJsonWriter(writer), documentOptions.OpenApiVersion); - context.Response.ContentType = "application/json;charset=utf-8"; + if (UseYaml(pattern)) + { + document.Serialize(new OpenApiYamlWriter(writer), documentOptions.OpenApiVersion); + context.Response.ContentType = "text/plain+yaml;charset=utf-8"; + } + else + { + document.Serialize(new OpenApiJsonWriter(writer), documentOptions.OpenApiVersion); + context.Response.ContentType = "application/json;charset=utf-8"; + } await context.Response.BodyWriter.WriteAsync(output.ToArray(), context.RequestAborted); await context.Response.BodyWriter.FlushAsync(context.RequestAborted); } @@ -63,4 +71,8 @@ public static IEndpointConventionBuilder MapOpenApi(this IEndpointRouteBuilder e } }).ExcludeFromDescription(); } + + private static bool UseYaml(string pattern) => + pattern.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) || + pattern.EndsWith(".yml", StringComparison.OrdinalIgnoreCase); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiEndpointRouteBuilderExtensionsTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiEndpointRouteBuilderExtensionsTests.cs index be44b44344ce..5ef1079759e9 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiEndpointRouteBuilderExtensionsTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/OpenApiEndpointRouteBuilderExtensionsTests.cs @@ -29,11 +29,13 @@ public void MapOpenApi_ReturnsEndpointConventionBuilder() Assert.IsAssignableFrom(returnedBuilder); } - [Fact] - public void MapOpenApi_SupportsCustomizingPath() + [Theory] + [InlineData("/custom/{documentName}/openapi.json")] + [InlineData("/custom/{documentName}/openapi.yaml")] + [InlineData("/custom/{documentName}/openapi.yml")] + public void MapOpenApi_SupportsCustomizingPath(string expectedPath) { // Arrange - var expectedPath = "/custom/{documentName}/openapi.json"; var serviceProvider = CreateServiceProvider(); var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider)); @@ -72,13 +74,17 @@ public async Task MapOpenApi_ReturnsRenderedDocument() }); } - [Fact] - public async Task MapOpenApi_ReturnsDefaultDocumentIfNoNameProvided() + [Theory] + [InlineData("/openapi.json", "application/json;charset=utf-8", false)] + [InlineData("/openapi.toml", "application/json;charset=utf-8", false)] + [InlineData("/openapi.yaml", "text/plain+yaml;charset=utf-8", true)] + [InlineData("/openapi.yml", "text/plain+yaml;charset=utf-8", true)] + public async Task MapOpenApi_ReturnsDefaultDocumentIfNoNameProvided(string expectedPath, string expectedContentType, bool isYaml) { // Arrange var serviceProvider = CreateServiceProvider(); var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider)); - builder.MapOpenApi("/openapi.json"); + builder.MapOpenApi(expectedPath); var context = new DefaultHttpContext(); var responseBodyStream = new MemoryStream(); context.Response.Body = responseBodyStream; @@ -91,6 +97,11 @@ public async Task MapOpenApi_ReturnsDefaultDocumentIfNoNameProvided() // Assert Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + Assert.Equal(expectedContentType, context.Response.ContentType); + var responseString = Encoding.UTF8.GetString(responseBodyStream.ToArray()); + // String check to validate that generated document starts with YAML syntax + Assert.Equal(isYaml, responseString.StartsWith("openapi: 3.0.1", StringComparison.OrdinalIgnoreCase)); + responseBodyStream.Position = 0; ValidateOpenApiDocument(responseBodyStream, document => { Assert.Equal("OpenApiEndpointRouteBuilderExtensionsTests | v1", document.Info.Title); @@ -121,8 +132,11 @@ public async Task MapOpenApi_Returns404ForUnresolvedDocument() Assert.Equal("No OpenAPI document with the name 'v2' was found.", Encoding.UTF8.GetString(responseBodyStream.ToArray())); } - [Fact] - public async Task MapOpenApi_ReturnsDocumentIfNameProvidedInQuery() + [Theory] + [InlineData("/openapi.json", "application/json;charset=utf-8", false)] + [InlineData("/openapi.yaml", "text/plain+yaml;charset=utf-8", true)] + [InlineData("/openapi.yml", "text/plain+yaml;charset=utf-8", true)] + public async Task MapOpenApi_ReturnsDocumentIfNameProvidedInQuery(string expectedPath, string expectedContentType, bool isYaml) { // Arrange var documentName = "v2"; @@ -130,7 +144,7 @@ public async Task MapOpenApi_ReturnsDocumentIfNameProvidedInQuery() var serviceProviderIsService = new ServiceProviderIsService(); var serviceProvider = CreateServiceProvider(documentName); var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider)); - builder.MapOpenApi("/openapi.json"); + builder.MapOpenApi(expectedPath); var context = new DefaultHttpContext(); var responseBodyStream = new MemoryStream(); context.Response.Body = responseBodyStream; @@ -144,6 +158,11 @@ public async Task MapOpenApi_ReturnsDocumentIfNameProvidedInQuery() // Assert Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + Assert.Equal(expectedContentType, context.Response.ContentType); + var responseString = Encoding.UTF8.GetString(responseBodyStream.ToArray()); + // String check to validate that generated document starts with YAML syntax + Assert.Equal(isYaml, responseString.StartsWith("openapi: 3.0.1", StringComparison.OrdinalIgnoreCase)); + responseBodyStream.Position = 0; ValidateOpenApiDocument(responseBodyStream, document => { Assert.Equal($"OpenApiEndpointRouteBuilderExtensionsTests | {documentName}", document.Info.Title);