Skip to content

Commit 9c8ab5f

Browse files
Fix descriptions, add module ID to OpenAPI (#2944)
Co-authored-by: Artem Dudarev <[email protected]>
1 parent b52c18b commit 9c8ab5f

File tree

4 files changed

+130
-59
lines changed

4 files changed

+130
-59
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Microsoft.AspNetCore.Mvc.Controllers;
4+
using Microsoft.OpenApi.Any;
5+
using Microsoft.OpenApi.Interfaces;
6+
using Microsoft.OpenApi.Models;
7+
using Swashbuckle.AspNetCore.SwaggerGen;
8+
using VirtoCommerce.Platform.Core.Common;
9+
using VirtoCommerce.Platform.Core.Modularity;
10+
11+
namespace VirtoCommerce.Platform.Web.Swagger;
12+
13+
/// <summary>
14+
/// This operation filter assigns module info (such as name and id) to operations based on their module association.
15+
/// </summary>
16+
public class ModuleInfoFilter : IOperationFilter, IDocumentFilter
17+
{
18+
private const string _moduleIdExtension = "x-virtocommerce-module-id";
19+
20+
private readonly Dictionary<string, SwaggerModule> _moduleByTitle;
21+
private readonly Dictionary<string, SwaggerModule> _moduleByAssemblyName;
22+
23+
/// <summary>
24+
/// This operation filter assigns module info (such as name and id) to operations based on their module association.
25+
/// </summary>
26+
public ModuleInfoFilter(IModuleCatalog moduleCatalog)
27+
{
28+
var modules = moduleCatalog.Modules
29+
.OfType<ManifestModuleInfo>()
30+
.Where(x => x.ModuleInstance != null)
31+
.Select(x =>
32+
new SwaggerModule
33+
{
34+
Id = x.Id,
35+
Title = x.Title,
36+
Description = x.Description,
37+
AssemblyName = x.ModuleInstance.GetType().Assembly.GetName().Name,
38+
})
39+
.ToList();
40+
41+
modules.Insert(0, new SwaggerModule
42+
{
43+
Id = "VirtoCommerce.Platform",
44+
Title = "VirtoCommerce Platform",
45+
AssemblyName = GetType().Assembly.GetName().Name,
46+
});
47+
48+
_moduleByTitle = modules.ToDictionary(x => x.Title);
49+
_moduleByAssemblyName = modules.ToDictionary(x => x.AssemblyName);
50+
}
51+
52+
public void Apply(OpenApiOperation operation, OperationFilterContext context)
53+
{
54+
var controllerAssemblyName = ((ControllerActionDescriptor)context.ApiDescription.ActionDescriptor).ControllerTypeInfo.Assembly.GetName().Name;
55+
56+
if (_moduleByAssemblyName.TryGetValueSafe(controllerAssemblyName, out var module))
57+
{
58+
operation.Tags = [new OpenApiTag { Name = module.Title }];
59+
operation.Extensions[_moduleIdExtension] = new OpenApiString(module.Id);
60+
}
61+
}
62+
63+
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
64+
{
65+
swaggerDoc.Tags ??= [];
66+
67+
// Collect unique tag names from operations
68+
var operationTagNames = swaggerDoc.Paths
69+
.SelectMany(path => path.Value.Operations.Values)
70+
.SelectMany(operation => operation.Tags ?? [])
71+
.Select(tag => tag.Name)
72+
.Distinct()
73+
.ToList();
74+
75+
// Add tags at document level with descriptions and extensions
76+
foreach (var tagName in operationTagNames)
77+
{
78+
if (_moduleByTitle.TryGetValue(tagName, out var module))
79+
{
80+
swaggerDoc.Tags.Add(new OpenApiTag
81+
{
82+
Name = module.Title,
83+
Description = module.Description,
84+
Extensions = new Dictionary<string, IOpenApiExtension>
85+
{
86+
{ _moduleIdExtension, new OpenApiString(module.Id) },
87+
},
88+
});
89+
}
90+
}
91+
}
92+
93+
private sealed class SwaggerModule
94+
{
95+
public string Id { get; init; }
96+
public string Title { get; init; }
97+
public string Description { get; init; }
98+
public string AssemblyName { get; init; }
99+
}
100+
}

src/VirtoCommerce.Platform.Web/Swagger/SwaggerServiceCollectionExtensions.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,15 @@ public static void AddSwagger(this IServiceCollection services, IConfiguration c
7878
c.TagActionsBy(api => [api.GetModuleName(provider)]);
7979
c.IgnoreObsoleteActions();
8080
c.DocumentFilter<ExcludeRedundantDepsFilter>();
81+
c.DocumentFilter<ModuleInfoFilter>();
8182
// This temporary filter removes broken "application/*+json" content-type.
8283
// It seems it's some openapi/swagger bug, because Autorest fails.
8384
c.OperationFilter<ConsumeFromBodyFilter>();
8485
c.OperationFilter<FileResponseTypeFilter>();
8586
c.OperationFilter<OptionalParametersFilter>();
8687
c.OperationFilter<ArrayInQueryParametersFilter>();
8788
c.OperationFilter<SecurityRequirementsOperationFilter>();
88-
c.OperationFilter<TagsFilter>();
89+
c.OperationFilter<ModuleInfoFilter>();
8990
c.OperationFilter<OpenIDEndpointDescriptionFilter>();
9091
c.SchemaFilter<EnumSchemaFilter>();
9192
c.SchemaFilter<SwaggerIgnoreFilter>();
@@ -141,7 +142,7 @@ private static bool DocInclusionPredicateCustomStrategy(ManifestModuleInfo[] mod
141142
return true;
142143
}
143144

144-
// It's a module endpoint.
145+
// It's a module endpoint.
145146
var module = modules.FirstOrDefault(m => m.ModuleName.EqualsIgnoreCase(docName));
146147
return module != null && module.Assembly == currentAssembly;
147148
}
@@ -172,7 +173,7 @@ public static void UseSwagger(this IApplicationBuilder applicationBuilder)
172173
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
173174
applicationBuilder.UseSwaggerUI(c =>
174175
{
175-
// Json Format Support
176+
// Json Format Support
176177
c.SwaggerEndpoint($"./{PlatformUIDocName}/swagger.json", PlatformUIDocName);
177178
c.SwaggerEndpoint($"./{PlatformDocName}/swagger.json", PlatformDocName);
178179

@@ -242,7 +243,7 @@ private static void AddModulesXmlComments(this SwaggerGenOptions options, Servic
242243

243244
foreach (var path in xmlCommentsDirectoryPaths)
244245
{
245-
var xmlComments = Directory.GetFiles(path, "*.XML");
246+
var xmlComments = Directory.GetFiles(path, "*.xml", new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive });
246247
foreach (var xmlComment in xmlComments)
247248
{
248249
options.IncludeXmlComments(xmlComment);

src/VirtoCommerce.Platform.Web/Swagger/TagsFilter.cs

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/VirtoCommerce.Platform.Web/wwwroot/swagger/vc.css

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ body
9393

9494
.swagger-section .swagger-ui-wrap ul#resources,
9595
.swagger-ui .opblock-tag,
96-
.swagger-ui .opblock-tag small
96+
.swagger-ui .opblock-tag small,
9797
.swagger-ui .opblock .opblock-summary-operation-id,
9898
.swagger-ui .opblock .opblock-summary-path,
9999
.swagger-ui .opblock .opblock-summary-path__deprecated,
@@ -126,30 +126,46 @@ body
126126

127127
.swagger-ui .opblock-tag
128128
{
129-
/* display: block; */
130-
clear: none;
131-
float: left;
129+
display: block !important;
130+
position: relative;
132131
font-weight: bold;
133132
font-size: 1.3em;
134-
padding: 10px 0 10px 0;
133+
padding: 10px 40px 10px 0;
134+
}
135+
136+
.swagger-ui .opblock-tag small
137+
{
138+
margin-top: 5px;
139+
padding: 0;
140+
}
141+
142+
.swagger-ui .opblock-tag small .renderedMarkdown p {
143+
margin: 5px 0;
144+
}
145+
146+
.swagger-ui .opblock-tag .expand-operation {
147+
position: absolute;
148+
top: 0;
149+
right: 0;
150+
height: 100%
135151
}
136152

137153
.swagger-ui .opblock
138154
{
139155
border-radius: 0px;
140156
}
141157

142-
.swagger-ui .opblock .opblock-summary-operation-id,
143-
.swagger-ui .opblock .opblock-summary-path,
158+
.swagger-ui .opblock .opblock-summary-operation-id,
159+
.swagger-ui .opblock .opblock-summary-path,
144160
.swagger-ui .opblock .opblock-summary-path__deprecated,
145-
.swagger-ui .opblock .opblock-summary-method
161+
.swagger-ui .opblock .opblock-summary-method
146162
{
147163
font-family: 'Exo 2';
148164
font-weight: bold;
149165
font-size: 0.9em;
150166
}
151167

152-
.swagger-ui .opblock .opblock-summary-method
168+
.swagger-ui .opblock .opblock-summary-method
153169
{
154170
padding: 5px;
155171
border-radius:0;

0 commit comments

Comments
 (0)