Skip to content

Commit 442d101

Browse files
committed
Add Description to OpenApiRouteHandlerBuilderExtensions and extra constructor and static methods to ProducesResponseTypeMetadata for correct description overloads
1 parent 3bdbe26 commit 442d101

File tree

2 files changed

+199
-1
lines changed

2 files changed

+199
-1
lines changed

src/Http/Http.Abstractions/src/Metadata/ProducesResponseTypeMetadata.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,43 @@ static void ValidateContentType(string type)
5050
}
5151
}
5252

53+
/// <summary>
54+
/// Initializes an instance of <see cref="ProducesResponseTypeMetadata"/>.
55+
/// </summary>
56+
/// <param name="statusCode">The HTTP response status code.</param>
57+
/// <param name="type">The <see cref="Type"/> of object that is going to be written in the response.</param>
58+
/// <param name="description">The descrption of the response.</param>
59+
/// <param name="contentTypes">Content types supported by the response.</param>
60+
public ProducesResponseTypeMetadata(int statusCode, Type? type = null, string? description = null, string[]? contentTypes = null)
61+
{
62+
StatusCode = statusCode;
63+
Type = type;
64+
Description = description;
65+
66+
if (contentTypes is null || contentTypes.Length == 0)
67+
{
68+
ContentTypes = Enumerable.Empty<string>();
69+
}
70+
else
71+
{
72+
for (var i = 0; i < contentTypes.Length; i++)
73+
{
74+
MediaTypeHeaderValue.Parse(contentTypes[i]);
75+
ValidateContentType(contentTypes[i]);
76+
}
77+
78+
ContentTypes = contentTypes;
79+
}
80+
81+
static void ValidateContentType(string type)
82+
{
83+
if (type.Contains('*', StringComparison.OrdinalIgnoreCase))
84+
{
85+
throw new InvalidOperationException($"Could not parse '{type}'. Content types with wildcards are not supported.");
86+
}
87+
}
88+
}
89+
5390
// Only for internal use where validation is unnecessary.
5491
private ProducesResponseTypeMetadata(int statusCode, Type? type, IEnumerable<string> contentTypes)
5592
{
@@ -58,6 +95,15 @@ private ProducesResponseTypeMetadata(int statusCode, Type? type, IEnumerable<str
5895
ContentTypes = contentTypes;
5996
}
6097

98+
// Only for internal use where validation is unnecessary.
99+
private ProducesResponseTypeMetadata(int statusCode, Type? type, string? description, IEnumerable<string> contentTypes)
100+
{
101+
Type = type;
102+
StatusCode = statusCode;
103+
Description = description;
104+
ContentTypes = contentTypes;
105+
}
106+
61107
/// <summary>
62108
/// Gets or sets the type of the value returned by an action.
63109
/// </summary>
@@ -81,8 +127,9 @@ private ProducesResponseTypeMetadata(int statusCode, Type? type, IEnumerable<str
81127
/// <inheritdoc/>
82128
public override string ToString()
83129
{
84-
return DebuggerHelpers.GetDebugText(nameof(StatusCode), StatusCode, nameof(ContentTypes), ContentTypes, nameof(Type), Type, includeNullValues: false, prefix: "Produces");
130+
return DebuggerHelpers.GetDebugText(nameof(StatusCode), StatusCode, nameof(ContentTypes), ContentTypes, nameof(Type), Type, nameof(Description), Description, includeNullValues: false, prefix: "Produces");
85131
}
86132

87133
internal static ProducesResponseTypeMetadata CreateUnvalidated(Type? type, int statusCode, IEnumerable<string> contentTypes) => new(statusCode, type, contentTypes);
134+
internal static ProducesResponseTypeMetadata CreateUnvalidated(Type? type, int statusCode, string? description, IEnumerable<string> contentTypes) => new(statusCode, type, description, contentTypes);
88135
}

src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ public static RouteHandlerBuilder Produces<TResponse>(
5555
return Produces(builder, statusCode, typeof(TResponse), contentType, additionalContentTypes);
5656
}
5757

58+
/// <summary>
59+
/// Adds an <see cref="IProducesResponseTypeMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all endpoints
60+
/// produced by <paramref name="builder"/>.
61+
/// </summary>
62+
/// <typeparam name="TResponse">The type of the response.</typeparam>
63+
/// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
64+
/// <param name="statusCode">The response status code. Defaults to <see cref="StatusCodes.Status200OK"/>.</param>
65+
/// <param name="contentType">The response content type. Defaults to "application/json".</param>
66+
/// <param name="description">The description of the response. Defaults to null.</param>
67+
/// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
68+
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
69+
#pragma warning disable RS0026
70+
public static RouteHandlerBuilder Produces<TResponse>(
71+
#pragma warning restore RS0026
72+
this RouteHandlerBuilder builder,
73+
int statusCode = StatusCodes.Status200OK,
74+
string? contentType = null,
75+
string? description = null,
76+
params string[] additionalContentTypes)
77+
{
78+
return Produces(builder, statusCode, typeof(TResponse), contentType, description, additionalContentTypes);
79+
}
80+
5881
/// <summary>
5982
/// Adds an <see cref="IProducesResponseTypeMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all endpoints
6083
/// produced by <paramref name="builder"/>.
@@ -91,6 +114,44 @@ public static RouteHandlerBuilder Produces(
91114
return builder.WithMetadata(new ProducesResponseTypeMetadata(statusCode, responseType ?? typeof(void), contentTypes));
92115
}
93116

117+
/// <summary>
118+
/// Adds an <see cref="IProducesResponseTypeMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all endpoints
119+
/// produced by <paramref name="builder"/>.
120+
/// </summary>
121+
/// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
122+
/// <param name="statusCode">The response status code.</param>
123+
/// <param name="responseType">The type of the response. Defaults to null.</param>
124+
/// <param name="description">The description of the response. Defaults to null.</param>
125+
/// <param name="contentType">The response content type. Defaults to "application/json" if responseType is not null, otherwise defaults to null.</param>
126+
/// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
127+
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
128+
#pragma warning disable RS0026
129+
public static RouteHandlerBuilder Produces(
130+
#pragma warning restore RS0026
131+
this RouteHandlerBuilder builder,
132+
int statusCode,
133+
Type? responseType = null,
134+
string? description = null,
135+
string? contentType = null,
136+
params string[] additionalContentTypes)
137+
{
138+
if (responseType is Type && string.IsNullOrEmpty(contentType))
139+
{
140+
contentType = ContentTypeConstants.JsonContentType;
141+
}
142+
143+
if (contentType is null)
144+
{
145+
return builder.WithMetadata(new ProducesResponseTypeMetadata(statusCode, responseType ?? typeof(void), description));
146+
}
147+
148+
var contentTypes = new string[additionalContentTypes.Length + 1];
149+
contentTypes[0] = contentType;
150+
additionalContentTypes.CopyTo(contentTypes, 1);
151+
152+
return builder.WithMetadata(new ProducesResponseTypeMetadata(statusCode, responseType ?? typeof(void), description, contentTypes));
153+
}
154+
94155
/// <summary>
95156
/// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="ProblemDetails"/> type
96157
/// to <see cref="EndpointBuilder.Metadata"/> for all endpoints produced by <paramref name="builder"/>.
@@ -109,6 +170,25 @@ public static RouteHandlerBuilder ProducesProblem(this RouteHandlerBuilder build
109170
return Produces(builder, statusCode, typeof(ProblemDetails), contentType);
110171
}
111172

173+
/// <summary>
174+
/// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="ProblemDetails"/> type
175+
/// to <see cref="EndpointBuilder.Metadata"/> for all endpoints produced by <paramref name="builder"/>.
176+
/// </summary>
177+
/// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
178+
/// <param name="statusCode">The response status code.</param>
179+
/// <param name="description">The description of the response. Defaults to null.</param>
180+
/// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
181+
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
182+
public static RouteHandlerBuilder ProducesProblem(this RouteHandlerBuilder builder, int statusCode, string? description = null, string? contentType = null)
183+
{
184+
if (string.IsNullOrEmpty(contentType))
185+
{
186+
contentType = ContentTypeConstants.ProblemDetailsContentType;
187+
}
188+
189+
return Produces(builder, statusCode, typeof(ProblemDetails), description, contentType);
190+
}
191+
112192
/// <summary>
113193
/// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="ProblemDetails"/> type
114194
/// to <see cref="EndpointBuilder.Metadata"/> for all endpoints produced by <paramref name="builder"/>.
@@ -130,6 +210,28 @@ public static TBuilder ProducesProblem<TBuilder>(this TBuilder builder, int stat
130210
return builder.WithMetadata(new ProducesResponseTypeMetadata(statusCode, typeof(ProblemDetails), [contentType]));
131211
}
132212

213+
/// <summary>
214+
/// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="ProblemDetails"/> type
215+
/// to <see cref="EndpointBuilder.Metadata"/> for all endpoints produced by <paramref name="builder"/>.
216+
/// </summary>
217+
/// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
218+
/// <param name="statusCode">The response status code.</param>
219+
/// <param name="description">The description of the response. Defaults to null.</param>
220+
/// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
221+
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
222+
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
223+
public static TBuilder ProducesProblem<TBuilder>(this TBuilder builder, int statusCode, string? description = null, string? contentType = null)
224+
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
225+
where TBuilder : IEndpointConventionBuilder
226+
{
227+
if (string.IsNullOrEmpty(contentType))
228+
{
229+
contentType = ContentTypeConstants.ProblemDetailsContentType;
230+
}
231+
232+
return builder.WithMetadata(new ProducesResponseTypeMetadata(statusCode, typeof(ProblemDetails), description, [contentType]));
233+
}
234+
133235
/// <summary>
134236
/// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="HttpValidationProblemDetails"/> type
135237
/// to <see cref="EndpointBuilder.Metadata"/> for all endpoints produced by <paramref name="builder"/>.
@@ -151,6 +253,29 @@ public static RouteHandlerBuilder ProducesValidationProblem(
151253
return Produces(builder, statusCode, typeof(HttpValidationProblemDetails), contentType);
152254
}
153255

256+
/// <summary>
257+
/// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="HttpValidationProblemDetails"/> type
258+
/// to <see cref="EndpointBuilder.Metadata"/> for all endpoints produced by <paramref name="builder"/>.
259+
/// </summary>
260+
/// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
261+
/// <param name="statusCode">The response status code. Defaults to <see cref="StatusCodes.Status400BadRequest"/>.</param>
262+
/// <param name="description">The description of the response. Defaults to null.</param>
263+
/// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
264+
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
265+
public static RouteHandlerBuilder ProducesValidationProblem(
266+
this RouteHandlerBuilder builder,
267+
int statusCode = StatusCodes.Status400BadRequest,
268+
string? description = null,
269+
string? contentType = null)
270+
{
271+
if (string.IsNullOrEmpty(contentType))
272+
{
273+
contentType = ContentTypeConstants.ProblemDetailsContentType;
274+
}
275+
276+
return Produces(builder, statusCode, typeof(HttpValidationProblemDetails), description, contentType);
277+
}
278+
154279
/// <summary>
155280
/// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="HttpValidationProblemDetails"/> type
156281
/// to <see cref="EndpointBuilder.Metadata"/> for all endpoints produced by <paramref name="builder"/>.
@@ -175,6 +300,32 @@ public static TBuilder ProducesValidationProblem<TBuilder>(
175300
return builder.WithMetadata(new ProducesResponseTypeMetadata(statusCode, typeof(HttpValidationProblemDetails), [contentType]));
176301
}
177302

303+
/// <summary>
304+
/// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="HttpValidationProblemDetails"/> type
305+
/// to <see cref="EndpointBuilder.Metadata"/> for all endpoints produced by <paramref name="builder"/>.
306+
/// </summary>
307+
/// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
308+
/// <param name="statusCode">The response status code. Defaults to <see cref="StatusCodes.Status400BadRequest"/>.</param>
309+
/// <param name="description">The description of the response. Defaults to null.</param>
310+
/// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
311+
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
312+
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
313+
public static TBuilder ProducesValidationProblem<TBuilder>(
314+
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
315+
this TBuilder builder,
316+
int statusCode = StatusCodes.Status400BadRequest,
317+
string? description = null,
318+
string? contentType = null)
319+
where TBuilder : IEndpointConventionBuilder
320+
{
321+
if (string.IsNullOrEmpty(contentType))
322+
{
323+
contentType = ContentTypeConstants.ProblemDetailsContentType;
324+
}
325+
326+
return builder.WithMetadata(new ProducesResponseTypeMetadata(statusCode, typeof(HttpValidationProblemDetails), description, [contentType]));
327+
}
328+
178329
/// <summary>
179330
/// Adds the <see cref="ITagsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all endpoints
180331
/// produced by <paramref name="builder"/>.

0 commit comments

Comments
 (0)