Skip to content

Commit 231c06d

Browse files
committed
feat: Add example of multiple responses in swagger
1 parent 84d581c commit 231c06d

5 files changed

Lines changed: 106 additions & 2 deletions

File tree

BookLibrary.Api/Features/Books/GetBook.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using BookLibrary.Api.Extensions;
2+
using BookLibrary.Api.Swagger;
23
using BookLibrary.Application.Features.Books.GetBook;
4+
using BookLibrary.Domain.Exceptions;
35
using JetBrains.Annotations;
46
using Microsoft.AspNetCore.Mvc;
57
using Sstv.DomainExceptions.Extensions.ProblemDetails;
@@ -26,7 +28,8 @@ public sealed class GetBookController : ApiController
2628
/// <param name="useCase">UseCase - get book by id.</param>
2729
/// <param name="ct">Token for cancel operation.</param>
2830
[HttpGet]
29-
[SwaggerResponse(StatusCodes.Status200OK, "Success", typeof(BookDto))]
31+
[SwaggerErrorCodeResponse(ErrorCodes.BookNotFound)]
32+
[SwaggerOkResponse<BookDto>]
3033
[SwaggerResponseExample(StatusCodes.Status200OK, typeof(BookDtoExample))]
3134
[SwaggerResponse(StatusCodes.Status400BadRequest, "In case bad request parameters", typeof(ErrorCodeProblemDetails))]
3235
[SwaggerResponse(StatusCodes.Status500InternalServerError, "In case server error", typeof(ErrorCodeProblemDetails))]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Microsoft.OpenApi.Models;
2+
using Swashbuckle.AspNetCore.Annotations;
3+
using Swashbuckle.AspNetCore.SwaggerGen;
4+
5+
namespace BookLibrary.Api.Swagger;
6+
7+
internal sealed class MultipleProducesOperationFilter : IOperationFilter
8+
{
9+
public void Apply(OpenApiOperation operation, OperationFilterContext context)
10+
{
11+
var attrs = context.MethodInfo.GetCustomAttributes(false)
12+
.OfType<SwaggerResponseAttribute>()
13+
.GroupBy(attr => attr.StatusCode)
14+
.Select(group => new { StatusCode = group.Key, Attributes = group.ToArray() })
15+
.OrderBy(x => x.StatusCode)
16+
.ToArray();
17+
18+
var duplicates = attrs
19+
.Where(x => x.Attributes.Length > 1)
20+
.ToArray();
21+
22+
if (duplicates.Length == 0)
23+
{
24+
return;
25+
}
26+
27+
foreach (var details in duplicates)
28+
{
29+
var response = operation.Responses[details.StatusCode.ToString()];
30+
31+
foreach (var attr in details.Attributes)
32+
{
33+
if (attr is SwaggerErrorCodeResponse errorCodeResponse)
34+
{
35+
var contentType = errorCodeResponse.ContentTypes.Single();
36+
if (!response.Content.TryGetValue(contentType, out var mediaType))
37+
{
38+
response.Content[contentType] = mediaType = new OpenApiMediaType();
39+
}
40+
41+
mediaType.Schema = context.SchemaGenerator.GenerateSchema(attr.Type, context.SchemaRepository);
42+
mediaType.Example = errorCodeResponse.GetExample();
43+
}
44+
}
45+
}
46+
}
47+
}

BookLibrary.Api/Swagger/SwaggerConfigureOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.AspNetCore.Mvc.ApiExplorer;
1+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
22
using Microsoft.Extensions.Options;
33
using Microsoft.OpenApi.Models;
44
using Swashbuckle.AspNetCore.Filters;
@@ -48,6 +48,7 @@ public void Configure(SwaggerGenOptions options)
4848
options.ExampleFilters();
4949
options.AddServer(new OpenApiServer { Description = "BookLibrary API", Url = pathBase });
5050

51+
options.OperationFilter<MultipleProducesOperationFilter>();
5152
options.AddEnumsWithValuesFixFilters(o =>
5253
{
5354
o.DescriptionSource = DescriptionSources.DescriptionAttributesThenXmlComments;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using BookLibrary.Api.ProblemDetails;
2+
using BookLibrary.Domain.Exceptions;
3+
using Microsoft.OpenApi.Any;
4+
using Sstv.DomainExceptions.Extensions.ProblemDetails;
5+
using Swashbuckle.AspNetCore.Annotations;
6+
using System.Net.Mime;
7+
8+
namespace BookLibrary.Api.Swagger;
9+
10+
/// <summary>
11+
/// Error code response.
12+
/// </summary>
13+
public sealed class SwaggerErrorCodeResponse : SwaggerResponseAttribute
14+
{
15+
public ErrorCodes ErrorCode { get; }
16+
17+
public SwaggerErrorCodeResponse(ErrorCodes errorCode, string? description = null)
18+
: base(
19+
ErrorCodeMapping.MapToStatusCode(errorCode.GetDescription()),
20+
description,
21+
typeof(ErrorCodeProblemDetails),
22+
contentTypes: [MediaTypeNames.Application.ProblemJson]
23+
)
24+
{
25+
ErrorCode = errorCode;
26+
}
27+
28+
public IOpenApiAny GetExample()
29+
{
30+
var error = ErrorCode.GetDescription();
31+
32+
return new OpenApiObject
33+
{
34+
["code"] = new OpenApiString(error.ErrorCode),
35+
["type"] = new OpenApiString(error.HelpLink),
36+
["title"] = new OpenApiString(error.Description),
37+
["status"] = new OpenApiInteger(StatusCode),
38+
["criticalityLevel"] = new OpenApiString(Enum.GetName(error.Level))
39+
};
40+
}
41+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Swashbuckle.AspNetCore.Annotations;
2+
using System.Net.Mime;
3+
4+
namespace BookLibrary.Api.Swagger;
5+
6+
public sealed class SwaggerOkResponse<T> : SwaggerResponseAttribute
7+
{
8+
public SwaggerOkResponse(string? description = null)
9+
: base(StatusCodes.Status200OK, description, typeof(T), MediaTypeNames.Application.Json)
10+
{
11+
}
12+
}

0 commit comments

Comments
 (0)