From 78174bc5f21156541833aa5ef47e025dc63672f8 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Tue, 24 May 2022 14:54:04 -0700 Subject: [PATCH 1/3] Add overloads to OpenApiRouteHandlerBuilderExtensions --- .../OpenApiRouteHandlerBuilderExtensions.cs | 242 ++++++++++++++---- src/Http/Routing/src/PublicAPI.Unshipped.txt | 9 + ...penApiRouteHandlerBuilderExtensionsTest.cs | 209 +++++++++++++++ src/Http/samples/MinimalSample/Program.cs | 2 +- 4 files changed, 405 insertions(+), 57 deletions(-) create mode 100644 src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs diff --git a/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs b/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs index 2456ed622c2d..0a3207ea3798 100644 --- a/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs +++ b/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs @@ -16,6 +16,15 @@ public static class OpenApiRouteHandlerBuilderExtensions { private static readonly ExcludeFromDescriptionAttribute _excludeFromDescriptionMetadataAttribute = new(); + /// + /// Adds the to for all endpoints + /// produced by . + /// + /// The . + /// A that can be used to further customize the endpoint. + public static TBuilder ExcludeFromDescription(this TBuilder builder) where TBuilder : IEndpointConventionBuilder + => builder.WithMetadata(_excludeFromDescriptionMetadataAttribute); + /// /// Adds the to for all endpoints /// produced by . @@ -23,10 +32,38 @@ public static class OpenApiRouteHandlerBuilderExtensions /// The . /// A that can be used to further customize the endpoint. public static RouteHandlerBuilder ExcludeFromDescription(this RouteHandlerBuilder builder) + => ExcludeFromDescription(builder); + + /// + /// Adds an to for all endpoints + /// produced by . + /// + /// The . + /// The type of the response. Defaults to null. + /// The response status code. + /// The response content type. Defaults to "application/json" if responseType is not null, otherwise defaults to null. + /// Additional response content types the endpoint produces for the supplied status code. + /// A that can be used to further customize the endpoint. +#pragma warning disable RS0026 + public static TBuilder Produces( +#pragma warning restore RS0026 + this TBuilder builder, + Type? responseType = null, + int statusCode = StatusCodes.Status200OK, + string? contentType = null, + params string[] additionalContentTypes) where TBuilder : IEndpointConventionBuilder { - builder.WithMetadata(_excludeFromDescriptionMetadataAttribute); + if (responseType is Type && string.IsNullOrEmpty(contentType)) + { + contentType = "application/json"; + } - return builder; + if (contentType is null) + { + return builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode)); + } + + return builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode, contentType, additionalContentTypes)); } /// @@ -40,13 +77,14 @@ public static RouteHandlerBuilder ExcludeFromDescription(this RouteHandlerBuilde /// Additional response content types the endpoint produces for the supplied status code. /// A that can be used to further customize the endpoint. #pragma warning disable RS0026 - public static RouteHandlerBuilder Produces(this RouteHandlerBuilder builder, + public static RouteHandlerBuilder Produces( #pragma warning restore RS0026 - int statusCode = StatusCodes.Status200OK, + this RouteHandlerBuilder builder, + int statusCode = StatusCodes.Status200OK, string? contentType = null, params string[] additionalContentTypes) { - return Produces(builder, statusCode, typeof(TResponse), contentType, additionalContentTypes); + return Produces(builder, typeof(TResponse), statusCode, contentType, additionalContentTypes); } /// @@ -60,27 +98,38 @@ public static RouteHandlerBuilder Produces(this RouteHandlerBuilder b /// Additional response content types the endpoint produces for the supplied status code. /// A that can be used to further customize the endpoint. #pragma warning disable RS0026 - public static RouteHandlerBuilder Produces(this RouteHandlerBuilder builder, + public static RouteHandlerBuilder Produces( #pragma warning restore RS0026 - int statusCode, + this RouteHandlerBuilder builder, + int statusCode, Type? responseType = null, string? contentType = null, params string[] additionalContentTypes) { - if (responseType is Type && string.IsNullOrEmpty(contentType)) - { - contentType = "application/json"; - } + return Produces(builder, responseType, statusCode, contentType, additionalContentTypes); + } - if (contentType is null) + /// + /// Adds an with a type + /// to for all endpoints produced by . + /// + /// The . + /// The response status code. + /// The response content type. Defaults to "application/problem+json". + /// A that can be used to further customize the endpoint. +#pragma warning disable RS0026 + public static TBuilder ProducesProblem( +#pragma warning restore RS0026 + this TBuilder builder, + int statusCode, + string? contentType = null) where TBuilder : IEndpointConventionBuilder + { + if (string.IsNullOrEmpty(contentType)) { - builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode)); - return builder; + contentType = "application/problem+json"; } - builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode, contentType, additionalContentTypes)); - - return builder; + return Produces(builder, typeof(ProblemDetails), statusCode, contentType); } /// @@ -91,16 +140,30 @@ public static RouteHandlerBuilder Produces(this RouteHandlerBuilder builder, /// The response status code. /// The response content type. Defaults to "application/problem+json". /// A that can be used to further customize the endpoint. - public static RouteHandlerBuilder ProducesProblem(this RouteHandlerBuilder builder, - int statusCode, - string? contentType = null) + public static RouteHandlerBuilder ProducesProblem(this RouteHandlerBuilder builder, int statusCode, string? contentType = null) + => ProducesProblem(builder, statusCode, contentType); + + /// + /// Adds an with a type + /// to for all endpoints produced by . + /// + /// The . + /// The response status code. Defaults to . + /// The response content type. Defaults to "application/problem+json". + /// A that can be used to further customize the endpoint. +#pragma warning disable RS0026 + public static TBuilder ProducesValidationProblem( +#pragma warning restore RS0026 + this TBuilder builder, + int statusCode = StatusCodes.Status400BadRequest, + string? contentType = null) where TBuilder : IEndpointConventionBuilder { if (string.IsNullOrEmpty(contentType)) { contentType = "application/problem+json"; } - return Produces(builder, statusCode, contentType); + return Produces(builder, typeof(HttpValidationProblemDetails), statusCode, contentType); } /// @@ -111,18 +174,29 @@ public static RouteHandlerBuilder ProducesProblem(this RouteHandlerBuilder build /// The response status code. Defaults to . /// The response content type. Defaults to "application/problem+json". /// A that can be used to further customize the endpoint. - public static RouteHandlerBuilder ProducesValidationProblem(this RouteHandlerBuilder builder, + public static RouteHandlerBuilder ProducesValidationProblem( + this RouteHandlerBuilder builder, int statusCode = StatusCodes.Status400BadRequest, string? contentType = null) { - if (string.IsNullOrEmpty(contentType)) - { - contentType = "application/problem+json"; - } - - return Produces(builder, statusCode, contentType); + return ProducesValidationProblem(builder, statusCode, contentType); } + /// + /// Adds the to for all endpoints + /// produced by . + /// + /// + /// The OpenAPI specification supports a tags classification to categorize operations + /// into related groups. These tags are typically included in the generated specification + /// and are typically used to group operations by tags in the UI. + /// + /// The . + /// A collection of tags to be associated with the endpoint. + /// A that can be used to further customize the endpoint. + public static TBuilder WithTags(this TBuilder builder, params string[] tags) where TBuilder : IEndpointConventionBuilder + => builder.WithMetadata(new TagsAttribute(tags)); + /// /// Adds the to for all endpoints /// produced by . @@ -136,9 +210,45 @@ public static RouteHandlerBuilder ProducesValidationProblem(this RouteHandlerBui /// A collection of tags to be associated with the endpoint. /// A that can be used to further customize the endpoint. public static RouteHandlerBuilder WithTags(this RouteHandlerBuilder builder, params string[] tags) + => WithTags(builder, tags); + + /// + /// Adds to for all endpoints + /// produced by . + /// + /// The . + /// The type of the request body. + /// Sets a value that determines if the request body is optional. + /// The request content type that the endpoint accepts. + /// The list of additional request content types that the endpoint accepts. + /// A that can be used to further customize the endpoint. + public static TBuilder Accepts( + this TBuilder builder, + Type requestType, + bool isOptional, + string contentType, + params string[] additionalContentTypes) where TBuilder : IEndpointConventionBuilder + { + var contentTypes = GetAllContentTypes(contentType, additionalContentTypes); + return builder.WithMetadata(new AcceptsMetadata(requestType, isOptional, contentTypes)); + } + + /// + /// Adds to for all endpoints + /// produced by . + /// + /// The . + /// The type of the request body. + /// The request content type that the endpoint accepts. + /// The list of additional request content types that the endpoint accepts. + /// A that can be used to further customize the endpoint. + public static TBuilder Accepts( + this TBuilder builder, + Type requestType, + string contentType, + params string[] additionalContentTypes) where TBuilder : IEndpointConventionBuilder { - builder.WithMetadata(new TagsAttribute(tags)); - return builder; + return Accepts(builder, requestType, isOptional: false, contentType, additionalContentTypes); } /// @@ -150,12 +260,12 @@ public static RouteHandlerBuilder WithTags(this RouteHandlerBuilder builder, par /// The request content type that the endpoint accepts. /// The list of additional request content types that the endpoint accepts. /// A that can be used to further customize the endpoint. - public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder, - string contentType, params string[] additionalContentTypes) where TRequest : notnull + public static RouteHandlerBuilder Accepts( + this RouteHandlerBuilder builder, + string contentType, + params string[] additionalContentTypes) where TRequest : notnull { - Accepts(builder, typeof(TRequest), contentType, additionalContentTypes); - - return builder; + return Accepts(builder, typeof(TRequest), contentType, additionalContentTypes); } /// @@ -168,12 +278,13 @@ public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder bui /// The request content type that the endpoint accepts. /// The list of additional request content types that the endpoint accepts. /// A that can be used to further customize the endpoint. - public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder, - bool isOptional, string contentType, params string[] additionalContentTypes) where TRequest : notnull + public static RouteHandlerBuilder Accepts( + this RouteHandlerBuilder builder, + bool isOptional, + string contentType, + params string[] additionalContentTypes) where TRequest : notnull { - Accepts(builder, typeof(TRequest), isOptional, contentType, additionalContentTypes); - - return builder; + return Accepts(builder, typeof(TRequest), isOptional, contentType, additionalContentTypes); } /// @@ -185,11 +296,13 @@ public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder bui /// The request content type that the endpoint accepts. /// The list of additional request content types that the endpoint accepts. /// A that can be used to further customize the endpoint. - public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder, - Type requestType, string contentType, params string[] additionalContentTypes) + public static RouteHandlerBuilder Accepts( + this RouteHandlerBuilder builder, + Type requestType, + string contentType, + params string[] additionalContentTypes) { - builder.WithMetadata(new AcceptsMetadata(requestType, false, GetAllContentTypes(contentType, additionalContentTypes))); - return builder; + return Accepts(builder, requestType, contentType, additionalContentTypes); } /// @@ -202,13 +315,26 @@ public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder, /// The request content type that the endpoint accepts. /// The list of additional request content types that the endpoint accepts. /// A that can be used to further customize the endpoint. - public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder, - Type requestType, bool isOptional, string contentType, params string[] additionalContentTypes) + public static RouteHandlerBuilder Accepts( + this RouteHandlerBuilder builder, + Type requestType, + bool isOptional, + string contentType, + params string[] additionalContentTypes) { - builder.WithMetadata(new AcceptsMetadata(requestType, isOptional, GetAllContentTypes(contentType, additionalContentTypes))); - return builder; + return Accepts(builder, requestType, isOptional, contentType, additionalContentTypes); } + /// + /// Adds to for all endpoints + /// produced by . + /// + /// The . + /// A string representing a detailed description of the endpoint. + /// A that can be used to further customize the endpoint. + public static TBuilder WithDescription(this TBuilder builder, string description) where TBuilder : IEndpointConventionBuilder + => builder.WithMetadata(new EndpointDescriptionAttribute(description)); + /// /// Adds to for all endpoints /// produced by . @@ -217,10 +343,17 @@ public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder, /// A string representing a detailed description of the endpoint. /// A that can be used to further customize the endpoint. public static RouteHandlerBuilder WithDescription(this RouteHandlerBuilder builder, string description) - { - builder.WithMetadata(new EndpointDescriptionAttribute(description)); - return builder; - } + => WithDescription(builder, description); + + /// + /// Adds to for all endpoints + /// produced by . + /// + /// The . + /// A string representing a brief description of the endpoint. + /// A that can be used to further customize the endpoint. + public static TBuilder WithSummary(this TBuilder builder, string summary) where TBuilder : IEndpointConventionBuilder + => builder.WithMetadata(new EndpointSummaryAttribute(summary)); /// /// Adds to for all endpoints @@ -230,10 +363,7 @@ public static RouteHandlerBuilder WithDescription(this RouteHandlerBuilder build /// A string representing a brief description of the endpoint. /// A that can be used to further customize the endpoint. public static RouteHandlerBuilder WithSummary(this RouteHandlerBuilder builder, string summary) - { - builder.WithMetadata(new EndpointSummaryAttribute(summary)); - return builder; - } + => WithSummary(builder, summary); private static string[] GetAllContentTypes(string contentType, string[] additionalContentTypes) { diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt index 632d25fbd8af..0ac798e7e490 100644 --- a/src/Http/Routing/src/PublicAPI.Unshipped.txt +++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt @@ -11,6 +11,15 @@ static Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.MapPatch(this override Microsoft.AspNetCore.Routing.RouteValuesAddress.ToString() -> string? *REMOVED*~Microsoft.AspNetCore.Routing.DefaultInlineConstraintResolver.DefaultInlineConstraintResolver(Microsoft.Extensions.Options.IOptions! routeOptions, System.IServiceProvider! serviceProvider) -> void Microsoft.AspNetCore.Routing.DefaultInlineConstraintResolver.DefaultInlineConstraintResolver(Microsoft.Extensions.Options.IOptions! routeOptions, System.IServiceProvider! serviceProvider) -> void +static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Accepts(this TBuilder builder, System.Type! requestType, bool isOptional, string! contentType, params string![]! additionalContentTypes) -> TBuilder +static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Accepts(this TBuilder builder, System.Type! requestType, string! contentType, params string![]! additionalContentTypes) -> TBuilder +static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.ExcludeFromDescription(this TBuilder builder) -> TBuilder +static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Produces(this TBuilder builder, System.Type? responseType = null, int statusCode = 200, string? contentType = null, params string![]! additionalContentTypes) -> TBuilder +static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.ProducesProblem(this TBuilder builder, int statusCode, string? contentType = null) -> TBuilder +static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.ProducesValidationProblem(this TBuilder builder, int statusCode = 400, string? contentType = null) -> TBuilder +static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithDescription(this TBuilder builder, string! description) -> TBuilder +static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithSummary(this TBuilder builder, string! summary) -> TBuilder +static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithTags(this TBuilder builder, params string![]! tags) -> TBuilder static Microsoft.AspNetCore.Http.RouteHandlerFilterExtensions.AddFilter(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder, Microsoft.AspNetCore.Http.IRouteHandlerFilter! filter) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Http.RouteHandlerFilterExtensions.AddFilter(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder, System.Func! filterFactory) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Http.RouteHandlerFilterExtensions.AddFilter(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder, System.Func>! routeHandlerFilter) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! diff --git a/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs new file mode 100644 index 000000000000..dd3f603198ff --- /dev/null +++ b/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Metadata; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNetCore.Routing.Builder; + +public class OpenApiRouteHandlerBuilderExtensionsTest +{ + [Fact] + public void ExcludeFromDescription_AddsExcludeFromDescriptionAttribute() + { + static void GenericExclude(IEndpointConventionBuilder builder) => builder.ExcludeFromDescription(); + static void SpecificExclude(RouteHandlerBuilder builder) => builder.ExcludeFromDescription(); + + static void AssertMetadata(EndpointBuilder builder) + => Assert.IsType(Assert.Single(builder.Metadata)); + + RunWithBothBuilders(GenericExclude, SpecificExclude, AssertMetadata); + } + + [Fact] + public void Produces_AddsProducesResponseTypeMetadataWithJsonContentType() + { + static void GenericProducesType(IEndpointConventionBuilder builder) => + builder.Produces(typeof(TestEndointConventionBuilder)); + static void SpecificProducesType(RouteHandlerBuilder builder) => + builder.Produces(); + + static void AssertMetadata(EndpointBuilder builder) + { + var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + + Assert.Equal(typeof(TestEndointConventionBuilder), metadata.Type); + Assert.Equal(StatusCodes.Status200OK, metadata.StatusCode); + Assert.Equal("application/json", Assert.Single(metadata.ContentTypes)); + } + + RunWithBothBuilders(GenericProducesType, SpecificProducesType, AssertMetadata); + } + + [Fact] + public void Produces_AddsProducesResponseTypeMetadataWithVoidType() + { + static void GenericProducesNotFound(IEndpointConventionBuilder builder) => + builder.Produces(statusCode: StatusCodes.Status404NotFound); + static void SpecificProducesNotFound(RouteHandlerBuilder builder) => + builder.Produces(StatusCodes.Status404NotFound); + + static void AssertMetadata(EndpointBuilder builder) + { + var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + + Assert.Equal(typeof(void), metadata.Type); + Assert.Equal(StatusCodes.Status404NotFound, metadata.StatusCode); + Assert.Empty(metadata.ContentTypes); + } + + RunWithBothBuilders(GenericProducesNotFound, SpecificProducesNotFound, AssertMetadata); + } + + [Fact] + public void Produces_WithNoArgs_AddsProducesResponseTypeMetadata() + { + var builder = new TestEndointConventionBuilder(); + builder.Produces(); + + var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + Assert.Equal(typeof(void), metadata.Type); + Assert.Equal(StatusCodes.Status200OK, metadata.StatusCode); + Assert.Empty(metadata.ContentTypes); + } + + [Fact] + public void ProdcesProblem_AddsProducesResponseTypeMetadataWithProblemDetailsType() + { + static void GenericProducesProblem(IEndpointConventionBuilder builder) => + builder.ProducesProblem(StatusCodes.Status400BadRequest); + static void SpecificProducesProblem(RouteHandlerBuilder builder) => + builder.ProducesProblem(StatusCodes.Status400BadRequest); + + static void AssertMetadata(EndpointBuilder builder) + { + var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + + Assert.Equal(typeof(ProblemDetails), metadata.Type); + Assert.Equal(StatusCodes.Status400BadRequest, metadata.StatusCode); + Assert.Equal("application/problem+json", Assert.Single(metadata.ContentTypes)); + } + + RunWithBothBuilders(GenericProducesProblem, SpecificProducesProblem, AssertMetadata); + } + + [Fact] + public void ProdcesValidiationProblem_AddsProducesResponseTypeMetadataWithHttpValidationProblemDetailsType() + { + static void GenericProducesValidationProblem(IEndpointConventionBuilder builder) => + builder.ProducesValidationProblem(); + static void SpecificProducesValidationProblem(RouteHandlerBuilder builder) => + builder.ProducesValidationProblem(); + + static void AssertMetadata(EndpointBuilder builder) + { + var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + + Assert.Equal(typeof(HttpValidationProblemDetails), metadata.Type); + Assert.Equal(StatusCodes.Status400BadRequest, metadata.StatusCode); + Assert.Equal("application/problem+json", Assert.Single(metadata.ContentTypes)); + } + + RunWithBothBuilders(GenericProducesValidationProblem, SpecificProducesValidationProblem, AssertMetadata); + } + + [Fact] + public void WithTags_AddsTagsAttribute() + { + static void GenericWithTags(IEndpointConventionBuilder builder) => builder.WithTags("a", "b", "c"); + static void SpecificWithTags(RouteHandlerBuilder builder) => builder.WithTags("a", "b", "c"); + + static void AssertMetadata(EndpointBuilder builder) + { + var tags = Assert.IsType(Assert.Single(builder.Metadata)); + Assert.Collection(tags.Tags, + tag => Assert.Equal("a", tag), + tag => Assert.Equal("b", tag), + tag => Assert.Equal("c", tag)); + } + + RunWithBothBuilders(GenericWithTags, SpecificWithTags, AssertMetadata); + } + + [Fact] + public void Accepts_AddsAcceptsMetadataWithSpecifiedType() + { + static void GenericAccepts(IEndpointConventionBuilder builder) => + builder.Accepts(typeof(TestEndointConventionBuilder), "text/plain"); + static void SpecificAccepts(RouteHandlerBuilder builder) => + builder.Accepts("text/plain"); + + static void AssertMetadata(EndpointBuilder builder) + { + var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + + Assert.Equal(typeof(TestEndointConventionBuilder), metadata.RequestType); + Assert.Equal("text/plain", Assert.Single(metadata.ContentTypes)); + Assert.False(metadata.IsOptional); + } + + RunWithBothBuilders(GenericAccepts, SpecificAccepts, AssertMetadata); + } + + [Fact] + public void WithDescription_AddsEndpointDescriptionAttribute() + { + var builder = new TestEndointConventionBuilder(); + builder.WithDescription("test description"); + + var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + Assert.Equal("test description", metadata.Description); + } + + [Fact] + public void WithSummary_AddsEndpointSummaryAttribute() + { + var builder = new TestEndointConventionBuilder(); + builder.WithSummary("test summary"); + + var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + Assert.Equal("test summary", metadata.Summary); + } + + private void RunWithBothBuilders( + Action genericSetup, + Action specificSetup, + Action assert) + { + var testBuilder0 = new TestEndointConventionBuilder(); + genericSetup(testBuilder0); + assert(testBuilder0); + + var (testBuilder1, routeHandlerBuilder) = CreateRouteHandlerBuilder(); + specificSetup(routeHandlerBuilder); + assert(testBuilder1); + } + + private (TestEndointConventionBuilder, RouteHandlerBuilder) CreateRouteHandlerBuilder() + { + var testBuilder = new TestEndointConventionBuilder(); + return (testBuilder, new RouteHandlerBuilder(new[] { testBuilder })); + } + + private sealed class TestEndointConventionBuilder : EndpointBuilder, IEndpointConventionBuilder + { + public void Add(Action convention) + { + convention(this); + } + + public override Endpoint Build() => throw new NotImplementedException(); + } +} diff --git a/src/Http/samples/MinimalSample/Program.cs b/src/Http/samples/MinimalSample/Program.cs index 4eb5ecd34d41..4694e666df9a 100644 --- a/src/Http/samples/MinimalSample/Program.cs +++ b/src/Http/samples/MinimalSample/Program.cs @@ -18,7 +18,7 @@ var nestedGroup = app.MapGroup("/group/{groupName}") .MapGroup("/nested/{nestedName}") - .WithMetadata(new TagsAttribute("nested")); + .WithTags("nested"); nestedGroup .MapGet("/", (string groupName, string nestedName) => From 319223e5cde55a1891736bf41e633fcf2b45fc6f Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Tue, 24 May 2022 18:25:41 -0700 Subject: [PATCH 2/3] Delete unshipped RouteHandlerBuilder overloads --- .../OpenApiRouteHandlerBuilderExtensions.cs | 20 ------------------- src/Http/Routing/src/PublicAPI.Unshipped.txt | 2 -- ...penApiRouteHandlerBuilderExtensionsTest.cs | 17 ++++++---------- 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs b/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs index 0a3207ea3798..b1d2c4367253 100644 --- a/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs +++ b/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs @@ -335,16 +335,6 @@ public static RouteHandlerBuilder Accepts( public static TBuilder WithDescription(this TBuilder builder, string description) where TBuilder : IEndpointConventionBuilder => builder.WithMetadata(new EndpointDescriptionAttribute(description)); - /// - /// Adds to for all endpoints - /// produced by . - /// - /// The . - /// A string representing a detailed description of the endpoint. - /// A that can be used to further customize the endpoint. - public static RouteHandlerBuilder WithDescription(this RouteHandlerBuilder builder, string description) - => WithDescription(builder, description); - /// /// Adds to for all endpoints /// produced by . @@ -355,16 +345,6 @@ public static RouteHandlerBuilder WithDescription(this RouteHandlerBuilder build public static TBuilder WithSummary(this TBuilder builder, string summary) where TBuilder : IEndpointConventionBuilder => builder.WithMetadata(new EndpointSummaryAttribute(summary)); - /// - /// Adds to for all endpoints - /// produced by . - /// - /// The . - /// A string representing a brief description of the endpoint. - /// A that can be used to further customize the endpoint. - public static RouteHandlerBuilder WithSummary(this RouteHandlerBuilder builder, string summary) - => WithSummary(builder, summary); - private static string[] GetAllContentTypes(string contentType, string[] additionalContentTypes) { var allContentTypes = new string[additionalContentTypes.Length + 1]; diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt index 0ac798e7e490..7850148ff445 100644 --- a/src/Http/Routing/src/PublicAPI.Unshipped.txt +++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt @@ -24,8 +24,6 @@ static Microsoft.AspNetCore.Http.RouteHandlerFilterExtensions.AddFilter(this Mic static Microsoft.AspNetCore.Http.RouteHandlerFilterExtensions.AddFilter(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder, System.Func! filterFactory) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Http.RouteHandlerFilterExtensions.AddFilter(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder, System.Func>! routeHandlerFilter) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Http.RouteHandlerFilterExtensions.AddFilter(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! -static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithDescription(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder, string! description) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! -static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithSummary(this Microsoft.AspNetCore.Builder.RouteHandlerBuilder! builder, string! summary) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Routing.LinkGeneratorEndpointNameAddressExtensions.GetPathByName(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, Microsoft.AspNetCore.Http.HttpContext! httpContext, string! endpointName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, Microsoft.AspNetCore.Http.PathString? pathBase = null, Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? static Microsoft.AspNetCore.Routing.LinkGeneratorEndpointNameAddressExtensions.GetPathByName(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, string! endpointName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, Microsoft.AspNetCore.Http.PathString pathBase = default(Microsoft.AspNetCore.Http.PathString), Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? static Microsoft.AspNetCore.Routing.LinkGeneratorEndpointNameAddressExtensions.GetUriByName(this Microsoft.AspNetCore.Routing.LinkGenerator! generator, Microsoft.AspNetCore.Http.HttpContext! httpContext, string! endpointName, Microsoft.AspNetCore.Routing.RouteValueDictionary? values = null, string? scheme = null, Microsoft.AspNetCore.Http.HostString? host = null, Microsoft.AspNetCore.Http.PathString? pathBase = null, Microsoft.AspNetCore.Http.FragmentString fragment = default(Microsoft.AspNetCore.Http.FragmentString), Microsoft.AspNetCore.Routing.LinkOptions? options = null) -> string? diff --git a/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs index dd3f603198ff..2f628bad9e86 100644 --- a/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs +++ b/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs @@ -182,19 +182,14 @@ private void RunWithBothBuilders( Action specificSetup, Action assert) { - var testBuilder0 = new TestEndointConventionBuilder(); - genericSetup(testBuilder0); - assert(testBuilder0); + var testBuilder = new TestEndointConventionBuilder(); + genericSetup(testBuilder); + assert(testBuilder); - var (testBuilder1, routeHandlerBuilder) = CreateRouteHandlerBuilder(); + var routeTestBuilder = new TestEndointConventionBuilder(); + var routeHandlerBuilder = new RouteHandlerBuilder(new[] { routeTestBuilder }); specificSetup(routeHandlerBuilder); - assert(testBuilder1); - } - - private (TestEndointConventionBuilder, RouteHandlerBuilder) CreateRouteHandlerBuilder() - { - var testBuilder = new TestEndointConventionBuilder(); - return (testBuilder, new RouteHandlerBuilder(new[] { testBuilder })); + assert(routeTestBuilder); } private sealed class TestEndointConventionBuilder : EndpointBuilder, IEndpointConventionBuilder From 3652f213a746b059e3caa893bd470b0043eb1394 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Tue, 31 May 2022 17:08:44 -0700 Subject: [PATCH 3/3] Remove overloads that were not api-approved --- .../OpenApiRouteHandlerBuilderExtensions.cs | 134 +++-------------- src/Http/Routing/src/PublicAPI.Unshipped.txt | 5 - ...penApiRouteHandlerBuilderExtensionsTest.cs | 136 ++++++------------ 3 files changed, 65 insertions(+), 210 deletions(-) diff --git a/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs b/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs index b1d2c4367253..2615e70e1ac9 100644 --- a/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs +++ b/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs @@ -34,38 +34,6 @@ public static TBuilder ExcludeFromDescription(this TBuilder builder) w public static RouteHandlerBuilder ExcludeFromDescription(this RouteHandlerBuilder builder) => ExcludeFromDescription(builder); - /// - /// Adds an to for all endpoints - /// produced by . - /// - /// The . - /// The type of the response. Defaults to null. - /// The response status code. - /// The response content type. Defaults to "application/json" if responseType is not null, otherwise defaults to null. - /// Additional response content types the endpoint produces for the supplied status code. - /// A that can be used to further customize the endpoint. -#pragma warning disable RS0026 - public static TBuilder Produces( -#pragma warning restore RS0026 - this TBuilder builder, - Type? responseType = null, - int statusCode = StatusCodes.Status200OK, - string? contentType = null, - params string[] additionalContentTypes) where TBuilder : IEndpointConventionBuilder - { - if (responseType is Type && string.IsNullOrEmpty(contentType)) - { - contentType = "application/json"; - } - - if (contentType is null) - { - return builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode)); - } - - return builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode, contentType, additionalContentTypes)); - } - /// /// Adds an to for all endpoints /// produced by . @@ -84,7 +52,7 @@ public static RouteHandlerBuilder Produces( string? contentType = null, params string[] additionalContentTypes) { - return Produces(builder, typeof(TResponse), statusCode, contentType, additionalContentTypes); + return Produces(builder, statusCode, typeof(TResponse), contentType, additionalContentTypes); } /// @@ -106,30 +74,17 @@ public static RouteHandlerBuilder Produces( string? contentType = null, params string[] additionalContentTypes) { - return Produces(builder, responseType, statusCode, contentType, additionalContentTypes); - } + if (responseType is Type && string.IsNullOrEmpty(contentType)) + { + contentType = "application/json"; + } - /// - /// Adds an with a type - /// to for all endpoints produced by . - /// - /// The . - /// The response status code. - /// The response content type. Defaults to "application/problem+json". - /// A that can be used to further customize the endpoint. -#pragma warning disable RS0026 - public static TBuilder ProducesProblem( -#pragma warning restore RS0026 - this TBuilder builder, - int statusCode, - string? contentType = null) where TBuilder : IEndpointConventionBuilder - { - if (string.IsNullOrEmpty(contentType)) + if (contentType is null) { - contentType = "application/problem+json"; + return builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode)); } - return Produces(builder, typeof(ProblemDetails), statusCode, contentType); + return builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode, contentType, additionalContentTypes)); } /// @@ -141,29 +96,13 @@ public static TBuilder ProducesProblem( /// The response content type. Defaults to "application/problem+json". /// A that can be used to further customize the endpoint. public static RouteHandlerBuilder ProducesProblem(this RouteHandlerBuilder builder, int statusCode, string? contentType = null) - => ProducesProblem(builder, statusCode, contentType); - - /// - /// Adds an with a type - /// to for all endpoints produced by . - /// - /// The . - /// The response status code. Defaults to . - /// The response content type. Defaults to "application/problem+json". - /// A that can be used to further customize the endpoint. -#pragma warning disable RS0026 - public static TBuilder ProducesValidationProblem( -#pragma warning restore RS0026 - this TBuilder builder, - int statusCode = StatusCodes.Status400BadRequest, - string? contentType = null) where TBuilder : IEndpointConventionBuilder { if (string.IsNullOrEmpty(contentType)) { contentType = "application/problem+json"; } - return Produces(builder, typeof(HttpValidationProblemDetails), statusCode, contentType); + return Produces(builder, statusCode, typeof(ProblemDetails), contentType); } /// @@ -179,7 +118,12 @@ public static RouteHandlerBuilder ProducesValidationProblem( int statusCode = StatusCodes.Status400BadRequest, string? contentType = null) { - return ProducesValidationProblem(builder, statusCode, contentType); + if (string.IsNullOrEmpty(contentType)) + { + contentType = "application/problem+json"; + } + + return Produces(builder, statusCode, typeof(HttpValidationProblemDetails), contentType); } /// @@ -212,45 +156,6 @@ public static TBuilder WithTags(this TBuilder builder, params string[] public static RouteHandlerBuilder WithTags(this RouteHandlerBuilder builder, params string[] tags) => WithTags(builder, tags); - /// - /// Adds to for all endpoints - /// produced by . - /// - /// The . - /// The type of the request body. - /// Sets a value that determines if the request body is optional. - /// The request content type that the endpoint accepts. - /// The list of additional request content types that the endpoint accepts. - /// A that can be used to further customize the endpoint. - public static TBuilder Accepts( - this TBuilder builder, - Type requestType, - bool isOptional, - string contentType, - params string[] additionalContentTypes) where TBuilder : IEndpointConventionBuilder - { - var contentTypes = GetAllContentTypes(contentType, additionalContentTypes); - return builder.WithMetadata(new AcceptsMetadata(requestType, isOptional, contentTypes)); - } - - /// - /// Adds to for all endpoints - /// produced by . - /// - /// The . - /// The type of the request body. - /// The request content type that the endpoint accepts. - /// The list of additional request content types that the endpoint accepts. - /// A that can be used to further customize the endpoint. - public static TBuilder Accepts( - this TBuilder builder, - Type requestType, - string contentType, - params string[] additionalContentTypes) where TBuilder : IEndpointConventionBuilder - { - return Accepts(builder, requestType, isOptional: false, contentType, additionalContentTypes); - } - /// /// Adds to for all endpoints /// produced by . @@ -265,7 +170,7 @@ public static RouteHandlerBuilder Accepts( string contentType, params string[] additionalContentTypes) where TRequest : notnull { - return Accepts(builder, typeof(TRequest), contentType, additionalContentTypes); + return Accepts(builder, typeof(TRequest), contentType, additionalContentTypes); } /// @@ -284,7 +189,7 @@ public static RouteHandlerBuilder Accepts( string contentType, params string[] additionalContentTypes) where TRequest : notnull { - return Accepts(builder, typeof(TRequest), isOptional, contentType, additionalContentTypes); + return Accepts(builder, typeof(TRequest), isOptional, contentType, additionalContentTypes); } /// @@ -302,7 +207,7 @@ public static RouteHandlerBuilder Accepts( string contentType, params string[] additionalContentTypes) { - return Accepts(builder, requestType, contentType, additionalContentTypes); + return Accepts(builder, requestType, isOptional: false, contentType, additionalContentTypes); } /// @@ -322,7 +227,8 @@ public static RouteHandlerBuilder Accepts( string contentType, params string[] additionalContentTypes) { - return Accepts(builder, requestType, isOptional, contentType, additionalContentTypes); + var contentTypes = GetAllContentTypes(contentType, additionalContentTypes); + return builder.WithMetadata(new AcceptsMetadata(requestType, isOptional, contentTypes)); } /// diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt index 7850148ff445..6b1ab9652506 100644 --- a/src/Http/Routing/src/PublicAPI.Unshipped.txt +++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt @@ -11,12 +11,7 @@ static Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.MapPatch(this override Microsoft.AspNetCore.Routing.RouteValuesAddress.ToString() -> string? *REMOVED*~Microsoft.AspNetCore.Routing.DefaultInlineConstraintResolver.DefaultInlineConstraintResolver(Microsoft.Extensions.Options.IOptions! routeOptions, System.IServiceProvider! serviceProvider) -> void Microsoft.AspNetCore.Routing.DefaultInlineConstraintResolver.DefaultInlineConstraintResolver(Microsoft.Extensions.Options.IOptions! routeOptions, System.IServiceProvider! serviceProvider) -> void -static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Accepts(this TBuilder builder, System.Type! requestType, bool isOptional, string! contentType, params string![]! additionalContentTypes) -> TBuilder -static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Accepts(this TBuilder builder, System.Type! requestType, string! contentType, params string![]! additionalContentTypes) -> TBuilder static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.ExcludeFromDescription(this TBuilder builder) -> TBuilder -static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Produces(this TBuilder builder, System.Type? responseType = null, int statusCode = 200, string? contentType = null, params string![]! additionalContentTypes) -> TBuilder -static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.ProducesProblem(this TBuilder builder, int statusCode, string? contentType = null) -> TBuilder -static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.ProducesValidationProblem(this TBuilder builder, int statusCode = 400, string? contentType = null) -> TBuilder static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithDescription(this TBuilder builder, string! description) -> TBuilder static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithSummary(this TBuilder builder, string! summary) -> TBuilder static Microsoft.AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.WithTags(this TBuilder builder, params string![]! tags) -> TBuilder diff --git a/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs index 2f628bad9e86..54a87d2aa5aa 100644 --- a/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs +++ b/src/Http/Routing/test/UnitTests/Builder/OpenApiRouteHandlerBuilderExtensionsTest.cs @@ -1,11 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Metadata; @@ -28,133 +23,92 @@ static void AssertMetadata(EndpointBuilder builder) } [Fact] - public void Produces_AddsProducesResponseTypeMetadataWithJsonContentType() + public void WithTags_AddsTagsAttribute() { - static void GenericProducesType(IEndpointConventionBuilder builder) => - builder.Produces(typeof(TestEndointConventionBuilder)); - static void SpecificProducesType(RouteHandlerBuilder builder) => - builder.Produces(); + static void GenericWithTags(IEndpointConventionBuilder builder) => builder.WithTags("a", "b", "c"); + static void SpecificWithTags(RouteHandlerBuilder builder) => builder.WithTags("a", "b", "c"); static void AssertMetadata(EndpointBuilder builder) { - var metadata = Assert.IsType(Assert.Single(builder.Metadata)); - - Assert.Equal(typeof(TestEndointConventionBuilder), metadata.Type); - Assert.Equal(StatusCodes.Status200OK, metadata.StatusCode); - Assert.Equal("application/json", Assert.Single(metadata.ContentTypes)); + var tags = Assert.IsType(Assert.Single(builder.Metadata)); + Assert.Collection(tags.Tags, + tag => Assert.Equal("a", tag), + tag => Assert.Equal("b", tag), + tag => Assert.Equal("c", tag)); } - RunWithBothBuilders(GenericProducesType, SpecificProducesType, AssertMetadata); + RunWithBothBuilders(GenericWithTags, SpecificWithTags, AssertMetadata); } [Fact] - public void Produces_AddsProducesResponseTypeMetadataWithVoidType() + public void Produces_AddsProducesResponseTypeMetadataWithJsonContentType() { - static void GenericProducesNotFound(IEndpointConventionBuilder builder) => - builder.Produces(statusCode: StatusCodes.Status404NotFound); - static void SpecificProducesNotFound(RouteHandlerBuilder builder) => - builder.Produces(StatusCodes.Status404NotFound); - - static void AssertMetadata(EndpointBuilder builder) - { - var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + var testBuilder = new TestEndointConventionBuilder(); + var builder = new RouteHandlerBuilder(new[] { testBuilder }); - Assert.Equal(typeof(void), metadata.Type); - Assert.Equal(StatusCodes.Status404NotFound, metadata.StatusCode); - Assert.Empty(metadata.ContentTypes); - } + builder.Produces(); - RunWithBothBuilders(GenericProducesNotFound, SpecificProducesNotFound, AssertMetadata); + var metadata = Assert.IsType(Assert.Single(testBuilder.Metadata)); + Assert.Equal(typeof(TestEndointConventionBuilder), metadata.Type); + Assert.Equal(StatusCodes.Status200OK, metadata.StatusCode); + Assert.Equal("application/json", Assert.Single(metadata.ContentTypes)); } [Fact] - public void Produces_WithNoArgs_AddsProducesResponseTypeMetadata() + public void Produces_AddsProducesResponseTypeMetadataWithVoidType() { - var builder = new TestEndointConventionBuilder(); - builder.Produces(); + var testBuilder = new TestEndointConventionBuilder(); + var builder = new RouteHandlerBuilder(new[] { testBuilder }); + + builder.Produces(StatusCodes.Status404NotFound); - var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + var metadata = Assert.IsType(Assert.Single(testBuilder.Metadata)); Assert.Equal(typeof(void), metadata.Type); - Assert.Equal(StatusCodes.Status200OK, metadata.StatusCode); + Assert.Equal(StatusCodes.Status404NotFound, metadata.StatusCode); Assert.Empty(metadata.ContentTypes); } [Fact] public void ProdcesProblem_AddsProducesResponseTypeMetadataWithProblemDetailsType() { - static void GenericProducesProblem(IEndpointConventionBuilder builder) => - builder.ProducesProblem(StatusCodes.Status400BadRequest); - static void SpecificProducesProblem(RouteHandlerBuilder builder) => - builder.ProducesProblem(StatusCodes.Status400BadRequest); - - static void AssertMetadata(EndpointBuilder builder) - { - var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + var testBuilder = new TestEndointConventionBuilder(); + var builder = new RouteHandlerBuilder(new[] { testBuilder }); - Assert.Equal(typeof(ProblemDetails), metadata.Type); - Assert.Equal(StatusCodes.Status400BadRequest, metadata.StatusCode); - Assert.Equal("application/problem+json", Assert.Single(metadata.ContentTypes)); - } + builder.ProducesProblem(StatusCodes.Status400BadRequest); - RunWithBothBuilders(GenericProducesProblem, SpecificProducesProblem, AssertMetadata); + var metadata = Assert.IsType(Assert.Single(testBuilder.Metadata)); + Assert.Equal(typeof(ProblemDetails), metadata.Type); + Assert.Equal(StatusCodes.Status400BadRequest, metadata.StatusCode); + Assert.Equal("application/problem+json", Assert.Single(metadata.ContentTypes)); } [Fact] public void ProdcesValidiationProblem_AddsProducesResponseTypeMetadataWithHttpValidationProblemDetailsType() { - static void GenericProducesValidationProblem(IEndpointConventionBuilder builder) => - builder.ProducesValidationProblem(); - static void SpecificProducesValidationProblem(RouteHandlerBuilder builder) => - builder.ProducesValidationProblem(); - - static void AssertMetadata(EndpointBuilder builder) - { - var metadata = Assert.IsType(Assert.Single(builder.Metadata)); - - Assert.Equal(typeof(HttpValidationProblemDetails), metadata.Type); - Assert.Equal(StatusCodes.Status400BadRequest, metadata.StatusCode); - Assert.Equal("application/problem+json", Assert.Single(metadata.ContentTypes)); - } - - RunWithBothBuilders(GenericProducesValidationProblem, SpecificProducesValidationProblem, AssertMetadata); - } - - [Fact] - public void WithTags_AddsTagsAttribute() - { - static void GenericWithTags(IEndpointConventionBuilder builder) => builder.WithTags("a", "b", "c"); - static void SpecificWithTags(RouteHandlerBuilder builder) => builder.WithTags("a", "b", "c"); + var testBuilder = new TestEndointConventionBuilder(); + var builder = new RouteHandlerBuilder(new[] { testBuilder }); - static void AssertMetadata(EndpointBuilder builder) - { - var tags = Assert.IsType(Assert.Single(builder.Metadata)); - Assert.Collection(tags.Tags, - tag => Assert.Equal("a", tag), - tag => Assert.Equal("b", tag), - tag => Assert.Equal("c", tag)); - } + builder.ProducesValidationProblem(); - RunWithBothBuilders(GenericWithTags, SpecificWithTags, AssertMetadata); + var metadata = Assert.IsType(Assert.Single(testBuilder.Metadata)); + Assert.Equal(typeof(HttpValidationProblemDetails), metadata.Type); + Assert.Equal(StatusCodes.Status400BadRequest, metadata.StatusCode); + Assert.Equal("application/problem+json", Assert.Single(metadata.ContentTypes)); } [Fact] public void Accepts_AddsAcceptsMetadataWithSpecifiedType() { - static void GenericAccepts(IEndpointConventionBuilder builder) => - builder.Accepts(typeof(TestEndointConventionBuilder), "text/plain"); - static void SpecificAccepts(RouteHandlerBuilder builder) => - builder.Accepts("text/plain"); + var testBuilder = new TestEndointConventionBuilder(); + var builder = new RouteHandlerBuilder(new[] { testBuilder }); - static void AssertMetadata(EndpointBuilder builder) - { - var metadata = Assert.IsType(Assert.Single(builder.Metadata)); + builder.Accepts("text/plain"); - Assert.Equal(typeof(TestEndointConventionBuilder), metadata.RequestType); - Assert.Equal("text/plain", Assert.Single(metadata.ContentTypes)); - Assert.False(metadata.IsOptional); - } + var metadata = Assert.IsType(Assert.Single(testBuilder.Metadata)); - RunWithBothBuilders(GenericAccepts, SpecificAccepts, AssertMetadata); + Assert.Equal(typeof(TestEndointConventionBuilder), metadata.RequestType); + Assert.Equal("text/plain", Assert.Single(metadata.ContentTypes)); + Assert.False(metadata.IsOptional); } [Fact]