8000 Merge pull request #246 from cnblogs/support-openapi · cnblogs/Architecture@aa0ee44 · GitHub
[go: up one dir, main page]

Skip to content

Commit aa0ee44

Browse files
authored
Merge pull request #246 from cnblogs/support-openapi
feat: enhance openapi support
2 parents 98b106f + 56fa0ae commit aa0ee44

File tree

5 files changed

+70
-27
lines changed

5 files changed

+70
-27
lines changed

src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
</PropertyGroup>
99
<ItemGroup>
1010
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
11+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6" />
1112
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
1213
</ItemGroup>
1314
<ItemGroup>

src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@ public static IEndpointConventionBuilder MapQuery(
114114
string nullRouteParameterPattern = "-",
115115
bool enableHead = false)
116116
{
117-
var returnType = EnsureReturnTypeIsQuery(handler);
117+
var (queryType, returnType) = EnsureReturnTypeIsQuery(handler);
118118
if (mapNullableRouteParameters is MapNullableRouteParameter.Disable)
119119
{
120-
return MapRoutes(route);
120+
return MapRoutes(queryType, returnType, route);
121121
}
122122

123123
if (string.IsNullOrWhiteSpace(nullRouteParameterPattern))
@@ -129,7 +129,7 @@ public static IEndpointConventionBuilder MapQuery(
129129

130130
var parsedRoute = RoutePatternFactory.Parse(route);
131131
var context = new NullabilityInfoContext();
132-
var nullableRouteProperties = returnType.GetProperties()
132+
var nullableRouteProperties = queryType.GetProperties()
133133
.Where(
134134
p => p.GetMethod != null
135135
&& p.SetMethod != null
@@ -150,15 +150,24 @@ public static IEndpointConventionBuilder MapQuery(
150150
var regex = new Regex("{" + x.Name + "[^}]*?}", RegexOptions.IgnoreCase);
151151
return regex.Replace(r, nullRouteParameterPattern);
152152
});
153-
MapRoutes(newRoute);
153+
MapRoutes(queryType, returnType, newRoute);
154154
}
155155

156-
return MapRoutes(route);
156+
return MapRoutes(queryType, returnType, route);
157157

158-
IEndpointConventionBuilder MapRoutes(string r)
158+
IEndpointConventionBuilder MapRoutes(Type query, Type queryFor, string r)
159159
{
160160
var endpoint = enableHead ? app.MapMethods(r, GetAndHeadMethods, handler) : app.MapGet(r, handler);
161-
return endpoint.AddEndpointFilter<QueryEndpointHandler>();
161+
var builder = endpoint.AddEndpointFilter<QueryEndpointHandler>()
162+
.Produces(200, queryFor)
163+
.WithTags("Queries");
164+
if (query.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQuery<>)))
165+
{
166+
// may be null
167+
builder.Produces(404, queryFor);
168+
}
169+
170+
return builder;
162171
}
163172
}
164173

@@ -220,7 +229,7 @@ public static IEndpointConventionBuilder MapCommand(
220229
[StringSyntax("Route")] string route,
221230
Delegate handler)
222231
{
223-
var commandTypeName = EnsureReturnTypeIsCommand(handler).Name;
232+
var commandTypeName = EnsureReturnTypeIsCommand(handler).CommandType.Name;
224233
if (PostCommandPrefixes.Any(x => commandTypeName.StartsWith(x)))
225234
{
226235
return app.MapPostCommand(route, handler);
@@ -265,8 +274,11 @@ public static IEndpointConventionBuilder MapPostCommand(
265274
[StringSyntax("Route")] string route,
266275
Delegate handler)
267276
{
268-
EnsureReturnTypeIsCommand(handler);
269-
return app.MapPost(route, handler).AddEndpointFilter<CommandEndpointHandler>();
277+
var (commandType, responseType, errorType) = EnsureReturnTypeIsCommand(handler);
278+
var builder = app.MapPost(route, handler)
279+
.AddEndpointFilter<CommandEndpointHandler>()
280+
.AddCommandOpenApiDescriptions(commandType, responseType, errorType);
281+
return builder;
270282
}
271283

272284
/// <summary>
@@ -295,8 +307,9 @@ public static IEndpointConventionBuilder MapPutCommand(
295307
[StringSyntax("Route")] string route,
296308
Delegate handler)
297309
{
298-
EnsureReturnTypeIsCommand(handler);
299-
return app.MapPut(route, handler).AddEndpointFilter<CommandEndpointHandler>();
310+
var (commandType, responseType, errorType) = EnsureReturnTypeIsCommand(handler);
311+
return app.MapPut(route, handler).AddEndpointFilter<CommandEndpointHandler>()
312+
.AddCommandOpenApiDescriptions(commandType, responseType, errorType);
300313
}
301314

302315
/// <summary>
@@ -325,8 +338,9 @@ public static IEndpointConventionBuilder MapDeleteCommand(
325338
[StringSyntax("Route")] string route,
326339
Delegate handler)
327340
{
328-
EnsureReturnTypeIsCommand(handler);
329-
return app.MapDelete(route, handler).AddEndpointFilter<CommandEndpointHandler>();
341+
var (commandType, responseType, errorType) = EnsureReturnTypeIsCommand(handler);
342+
return app.MapDelete(route, handler).AddEndpointFilter<CommandEndpointHandler>()
343+
.AddCommandOpenApiDescriptions(commandType, responseType, errorType);
330344
}
331345

332346
/// <summary>
@@ -395,42 +409,48 @@ public static IEndpointRouteBuilder StopMappingPrefixToDelete(this IEndpointRout
395409
return app;
396410
}
397411

398-
private static Type EnsureReturnTypeIsCommand(Delegate handler)
412+
private static (Type CommandType, Type? ResponseType, Type ErrorType) EnsureReturnTypeIsCommand(Delegate handler)
399413
{
400414
var returnType = handler.Method.ReturnType;
401415
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
402416
{
403417
returnType = returnType.GenericTypeArguments.First();
404418
}
405419

406-
var isCommand = returnType.GetInterfaces().Where(x => x.IsGenericType)
407-
.Any(x => CommandTypes.Contains(x.GetGenericTypeDefinition()));
408-
if (isCommand == false)
420+
var commandType = returnType.GetInterfaces().Where(x => x.IsGenericType)
421+
.FirstOrDefault(x => CommandTypes.Contains(x.GetGenericTypeDefinition()));
422+
if (commandType == null)
409423
{
410424
throw new ArgumentException(
411425
"handler does not return command, check if delegate returns type that implements ICommand<> or ICommand<,>");
412426
}
413427

414-
return returnType;
428+
Type?[] genericParams = commandType.GetGenericArguments();
429+
if (genericParams.Length == 1)
430+
{
431+
genericParams = [null, genericParams[0]];
432+
}
433+
434+
return (returnType, genericParams[0], genericParams[1]!);
415435
}
416436

417-
private static Type EnsureReturnTypeIsQuery(Delegate handler)
437+
private static (Type, Type) EnsureReturnTypeIsQuery(Delegate handler)
418438
{
419439
var returnType = handler.Method.ReturnType;
420440
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
421441
{
422442
returnType = returnType.GenericTypeArguments.First();
423443
}
424444

425-
var isCommand = returnType.GetInterfaces().Where(x => x.IsGenericType)
426-
.Any(x => QueryTypes.Contains(x.GetGenericTypeDefinition()));
427-
if (isCommand == false)
445+
var queryInterface = returnType.GetInterfaces().Where(x => x.IsGenericType)
446+
.FirstOrDefault(x => QueryTypes.Contains(x.GetGenericTypeDefinition()));
447+
if (queryInterface == null)
428448
{
429449
throw new ArgumentException(
430450
"handler does not return query, check if delegate returns type that implements IQuery<>");
431451
}
432452

433-
return returnType;
453+
return (returnType, queryInterface.GenericTypeArguments[0]);
434454
}
435455

436456
private static List<T[]> GetNotEmptySubsets<T>(ICollection<T> items)
@@ -446,4 +466,24 @@ private static List<T[]> GetNotEmptySubsets<T>(ICollection<T> items)
446466

447467
return results;
448468
}
469+
470+
private static RouteHandlerBuilder AddCommandOpenApiDescriptions(
471+
this RouteHandlerBuilder builder,
472+
Type commandType,
473+
Type? responseType,
474+
Type errorType)
475+
{
476+
var commandResponseType = responseType is null
477+
? typeof(CommandResponse<>).MakeGenericType(errorType)
478+
: typeof(CommandResponse<,>).MakeGenericType(responseType, errorType);
479+
builder.Produces(200, commandResponseType)
480+
.Produces(400, commandResponseType)
481+
.WithTags("Commands");
482+
if (commandType.GetInterfaces().Any(i => i == typeof(ILockableRequest)))
483+
{
484+
builder.Produces(429);
485+
}
486+
487+
return builder;
488+
}
449489
}

src/Cnblogs.Architecture.Ddd.EventBus.Dapr/EndPointExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Reflection;
22
using Cnblogs.Architecture.Ddd.EventBus.Abstractions;
33
using Cnblogs.Architecture.Ddd.EventBus.Dapr;
4+
using Microsoft.AspNetCore.Http;
45
using Microsoft.AspNetCore.Routing;
56
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Extensions.Options;
@@ -58,7 +59,8 @@ public static IEndpointRouteBuilder Subscribe<TEvent>(
5859

5960
builder
6061
.MapPost(route, (TEvent receivedEvent, IEventBus eventBus) => eventBus.ReceiveAsync(receivedEvent))
61-
.WithTopic(DaprOptions.PubSubName, DaprUtils.GetDaprTopicName<TEvent>(appName));
62+
.WithTopic(DaprOptions.PubSubName, DaprUtils.GetDaprTopicName<TEvent>(appName))
63+
.WithTags("Subscriptions");
6264

6365
return builder;
6466
}

src/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13-
<PackageReference Include="MongoDB.Driver" Version="2.25.0" />
13+
<PackageReference Include="MongoDB.Driver" Version="2.26.0" />
1414
</ItemGroup>
1515

1616
<ItemGroup>

test/Cnblogs.Architecture.IntegrationTestProject/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"dotnetRunMessages": true,
1515
"launchBrowser": true,
1616
"launchUrl": "swagger",
17-
"applicationUrl": "https://localhost:7200;http://localhost:5200",
17+
"applicationUrl": "https://localhost:8200;http://localhost:5200",
1818
"environmentVariables": {
1919
"ASPNETCORE_ENVIRONMENT": "Development"
2020
}

0 commit comments

Comments
 (0)
0