Skip to content

Commit 76f6cab

Browse files
authored
Расширенное описание ошибок Swagger (#1)
* Вывод списка ошибок с одинаковым StatusCode * Добавить метод для получения описания ошибки * Добавить атрибуты для разных ошибок
1 parent f0b0616 commit 76f6cab

11 files changed

Lines changed: 206 additions & 1 deletion

src/SmartHead.Essentials.Application/SmartHead.Essentials.Application.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<IsPackable>true</IsPackable>
88
<Authors>Rustam Asylgareev</Authors>
99
<PackageProjectUrl>https://github.com/smarthead-dev/SmartHead.Essentials</PackageProjectUrl>
10-
<Version>0.0.9</Version>
10+
<Version>0.0.10</Version>
1111
<Company>SmartHead</Company>
1212
<IsTestProject>false</IsTestProject>
1313
<Description>SmartHead.Essentials.Application</Description>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Net;
3+
4+
namespace SmartHead.Essentials.Application.Swagger.Attributes
5+
{
6+
public class BadRequestAttribute : ReturnsAttribute
7+
{
8+
public BadRequestAttribute(string subStatus, string description = null, Type type = null)
9+
:base((int)HttpStatusCode.BadRequest, subStatus, description, type)
10+
{
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Net;
3+
4+
namespace SmartHead.Essentials.Application.Swagger.Attributes
5+
{
6+
public class CreatedAttribute : ReturnsAttribute
7+
{
8+
public CreatedAttribute(string description = null, Type type = null)
9+
: base((int)HttpStatusCode.Created, null, description, type)
10+
{
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Net;
3+
4+
namespace SmartHead.Essentials.Application.Swagger.Attributes
5+
{
6+
public class ForbiddenAttribute : ReturnsAttribute
7+
{
8+
public ForbiddenAttribute(string subStatus = null, string description = null, Type type = null)
9+
: base((int)HttpStatusCode.Forbidden, subStatus, description, type)
10+
{
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Net;
3+
4+
namespace SmartHead.Essentials.Application.Swagger.Attributes
5+
{
6+
public class InternalServerErrorAttribute : ReturnsAttribute
7+
{
8+
public InternalServerErrorAttribute(string subStatus = null, string description = null, Type type = null)
9+
: base((int)HttpStatusCode.InternalServerError, subStatus, description, type)
10+
{
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Net;
3+
4+
namespace SmartHead.Essentials.Application.Swagger.Attributes
5+
{
6+
public class NoContentAttribute : ReturnsAttribute
7+
{
8+
public NoContentAttribute(string description = null)
9+
: base((int)HttpStatusCode.NoContent, null, description, null)
10+
{
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Net;
3+
4+
namespace SmartHead.Essentials.Application.Swagger.Attributes
5+
{
6+
public class OkAttribute : ReturnsAttribute
7+
{
8+
public OkAttribute(string description = null, Type type = null)
9+
: base((int)HttpStatusCode.OK, null, description, type)
10+
{
11+
}
12+
}
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
using Swashbuckle.AspNetCore.Annotations;
3+
4+
namespace SmartHead.Essentials.Application.Swagger.Attributes
5+
{
6+
public class ReturnsAttribute : SwaggerResponseAttribute
7+
{
8+
public string SubStatus { get; }
9+
10+
public ReturnsAttribute(int statusCode, string subStatus, string description = null, Type type = null) : base(statusCode, description, type)
11+
{
12+
SubStatus = subStatus;
13+
}
14+
}
15+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net;
4+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
5+
using Microsoft.AspNetCore.Mvc.ModelBinding;
6+
using Microsoft.OpenApi.Models;
7+
using SmartHead.Essentials.Application.Controller;
8+
using SmartHead.Essentials.Application.Swagger.Attributes;
9+
using Swashbuckle.AspNetCore.SwaggerGen;
10+
11+
namespace SmartHead.Essentials.Application.Swagger
12+
{
13+
public class BadRequestOperationFilter<T> : IOperationFilter
14+
where T: IDescriptionProvider
15+
{
16+
private IDescriptionProvider _descriptionProvider = null;
17+
18+
public IDescriptionProvider Description => GetDescriptionProvider();
19+
20+
public void Apply(OpenApiOperation operation, OperationFilterContext context)
21+
{
22+
var isController = context.MethodInfo.ReflectedType?.BaseType == typeof(FormattedApiControllerBase) ||
23+
context.MethodInfo.ReflectedType?.BaseType?.BaseType == typeof(FormattedApiControllerBase) ||
24+
context.MethodInfo.ReflectedType?.BaseType?.BaseType?.BaseType == typeof(FormattedApiControllerBase);
25+
if (!isController) return;
26+
27+
var groups = context.MethodInfo.DeclaringType
28+
.GetCustomAttributes(true)
29+
.Union(context.MethodInfo.GetCustomAttributes(true))
30+
.OfType<ReturnsAttribute>()
31+
.Where(x => x.StatusCode == (int)HttpStatusCode.BadRequest)
32+
.GroupBy(x => x.StatusCode);
33+
34+
foreach (var group in groups)
35+
{
36+
var count = group.Count();
37+
var responses = group.ToArray();
38+
39+
for (var i = 0; i < count; i++)
40+
{
41+
var response = responses[i];
42+
43+
var key = $"{response.StatusCode}";
44+
if (count > 1)
45+
key += $" ({i + 1} из {count})";
46+
47+
if (operation.Responses.ContainsKey(response.StatusCode.ToString()))
48+
operation.Responses.Remove(response.StatusCode.ToString());
49+
50+
if (operation.Responses.ContainsKey(key))
51+
continue;
52+
53+
var apiResponseType = context
54+
.ApiDescription
55+
.SupportedResponseTypes
56+
.FirstOrDefault(x => x.Type == response.Type);
57+
58+
operation.Responses.Add(key, CreateOpenApiResponse(context, response, apiResponseType));
59+
}
60+
}
61+
}
62+
63+
private OpenApiResponse CreateOpenApiResponse(OperationFilterContext context, ReturnsAttribute response, ApiResponseType apiResponseType)
64+
=> new OpenApiResponse()
65+
{
66+
Description = $"{response.SubStatus}<br/><br/>{Description?.GetValue(response.SubStatus) ?? response.Description}",
67+
Content = apiResponseType
68+
?.ApiResponseFormats
69+
.ToDictionary(x => x.MediaType,
70+
x => CreateResponseMediaType(context, apiResponseType.ModelMetadata))
71+
};
72+
73+
private OpenApiMediaType CreateResponseMediaType(OperationFilterContext context, ModelMetadata modelMetadata)
74+
=> new OpenApiMediaType
75+
{
76+
Schema = context.SchemaGenerator.GenerateSchema(modelMetadata.ModelType, context.SchemaRepository)
77+
};
78+
79+
private IDescriptionProvider GetDescriptionProvider()
80+
{
81+
try
82+
{
83+
return _descriptionProvider ??= Activator.CreateInstance<T>();
84+
}
85+
catch (Exception e)
86+
{
87+
return null;
88+
}
89+
}
90+
}
91+
92+
public class BadRequestOperationFilter: BadRequestOperationFilter<DummyDescriptionProvider>
93+
{
94+
}
95+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace SmartHead.Essentials.Application.Swagger
2+
{
3+
public class DummyDescriptionProvider : IDescriptionProvider
4+
{
5+
public string GetValue(string key)
6+
{
7+
return null;
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)