8000 feat: enhance openapi support by ikesnowy · Pull Request #246 · cnblogs/Architecture · GitHub
[go: up one dir, main page]

Skip to content

feat: enhance openapi support #246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
Expand Down
88 changes: 64 additions & 24 deletions src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ public static IEndpointConventionBuilder MapQuery(
string nullRouteParameterPattern = "-",
bool enableHead = false)
{
var returnType = EnsureReturnTypeIsQuery(handler);
var (queryType, returnType) = EnsureReturnTypeIsQuery(handler);
if (mapNullableRouteParameters is MapNullableRouteParameter.Disable)
{
return MapRoutes(route);
return MapRoutes(queryType, returnType, route);
}

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

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

return MapRoutes(route);
return MapRoutes(queryType, returnType, route);

IEndpointConventionBuilder MapRoutes(string r)
IEndpointConventionBuilder MapRoutes(Type query, Type queryFor, string r)
{
var endpoint = enableHead ? app.MapMethods(r, GetAndHeadMethods, handler) : app.MapGet(r, handler);
return endpoint.AddEndpointFilter<QueryEndpointHandler>();
var builder = endpoint.AddEndpointFilter<QueryEndpointHandler>()
.Produces(200, queryFor)
.WithTags("Queries");
if (query.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQuery<>)))
{
// may be null
builder.Produces(404, queryFor);
}

return builder;
}
}

Expand Down Expand Up @@ -220,7 +229,7 @@ public static IEndpointConventionBuilder MapCommand(
[StringSyntax("Route")] string route,
Delegate handler)
{
var commandTypeName = EnsureReturnTypeIsCommand(handler).Name;
var commandTypeName = EnsureReturnTypeIsCommand(handler).CommandType.Name;
if (PostCommandPrefixes.Any(x => commandTypeName.StartsWith(x)))
{
return app.MapPostCommand(route, handler);
Expand Down Expand Up @@ -265,8 +274,11 @@ public static IEndpointConventionBuilder MapPostCommand(
[StringSyntax("Route")] string route,
Delegate handler)
{
EnsureReturnTypeIsCommand(handler);
return app.MapPost(route, handler).AddEndpointFilter<CommandEndpointHandler>();
var (commandType, responseType, errorType) = EnsureReturnTypeIsCommand(handler);
var builder = app.MapPost(route, handler)
.AddEndpointFilter<CommandEndpointHandler>()
.AddCommandOpenApiDescriptions(commandType, responseType, errorType);
return builder;
}

/// <summary>
Expand Down Expand Up @@ -295,8 +307,9 @@ public static IEndpointConventionBuilder MapPutCommand(
[StringSyntax("Route")] string route,
Delegate handler)
{
EnsureReturnTypeIsCommand(handler);
return app.MapPut(route, handler).AddEndpointFilter<CommandEndpointHandler>();
var (commandType, responseType, errorType) = EnsureReturnTypeIsCommand(handler);
return app.MapPut(route, handler).AddEndpointFilter<CommandEndpointHandler>()
.AddCommandOpenApiDescriptions(commandType, responseType, errorType);
}

/// <summary>
Expand Down Expand Up @@ -325,8 +338,9 @@ public static IEndpointConventionBuilder MapDeleteCommand(
[StringSyntax("Route")] string route,
Delegate handler)
{
EnsureReturnTypeIsCommand(handler);
return app.MapDelete(route, handler).AddEndpointFilter<CommandEndpointHandler>();
var (commandType, responseType, errorType) = EnsureReturnTypeIsCommand(handler);
return app.MapDelete(route, handler).AddEndpointFilter<CommandEndpointHandler>()
.AddCommandOpenApiDescriptions(commandType, responseType, errorType);
}

/// <summary>
Expand Down Expand Up @@ -395,42 +409,48 @@ public static IEndpointRouteBuilder StopMappingPrefixToDelete(this IEndpointRout
return app;
}

private static Type EnsureReturnTypeIsCommand(Delegate handler)
private static (Type CommandType, Type? ResponseType, Type ErrorType) EnsureReturnTypeIsCommand(Delegate handler)
{
var returnType = handler.Method.ReturnType;
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
returnType = returnType.GenericTypeArguments.First();
}

var isCommand = returnType.GetInterfaces().Where(x => x.IsGenericType)
.Any(x => CommandTypes.Contains(x.GetGenericTypeDefinition()));
if (isCommand == false)
var commandType = returnType.GetInterfaces().Where(x => x.IsGenericType)
.FirstOrDefault(x => CommandTypes.Contains(x.GetGenericTypeDefinition()));
if (commandType == null)
{
throw new ArgumentException(
"handler does not return command, check if delegate returns type that implements ICommand<> or ICommand<,>");
}

return returnType;
Type?[] genericParams = commandType.GetGenericArguments();
if (genericParams.Length == 1)
{
genericParams = [null, genericParams[0]];
}

return (returnType, genericParams[0], genericParams[1]!);
}

private static Type EnsureReturnTypeIsQuery(Delegate handler)
private static (Type, Type) EnsureReturnTypeIsQuery(Delegate handler)
{
var returnType = handler.Method.ReturnType;
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
returnType = returnType.GenericTypeArguments.First();
}

var isCommand = returnType.GetInterfaces().Where(x => x.IsGenericType)
.Any(x => QueryTypes.Contains(x.GetGenericTypeDefinition()));
if (isCommand == false)
var queryInterface = returnType.GetInterfaces().Where(x => x.IsGenericType)
.FirstOrDefault(x => QueryTypes.Contains(x.GetGenericTypeDefinition()));
if (queryInterface == null)
{
throw new ArgumentException(
"handler does not return query, check if delegate returns type that implements IQuery<>");
}

return returnType;
return (returnType, queryInterface.GenericTypeArguments[0]);
}

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

return results;
}

private static RouteHandlerBuilder AddCommandOpenApiDescriptions(
this RouteHandlerBuilder builder,
Type commandType,
Type? responseType,
Type errorType)
{
var commandResponseType = responseType is null
? typeof(CommandResponse<>).MakeGenericType(errorType)
: typeof(CommandResponse<,>).MakeGenericType(responseType, errorType);
builder.Produces(200, commandResponseType)
.Produces(400, commandResponseType)
.WithTags("Commands");
if (commandType.GetInterfaces().Any(i => i == typeof(ILockableRequest)))
{
builder.Produces(429);
}

return builder;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Reflection;
using Cnblogs.Architecture.Ddd.EventBus.Abstractions;
using Cnblogs.Architecture.Ddd.EventBus.Dapr;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.De 67F4 pendencyInjection;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -58,7 +59,8 @@ public static IEndpointRouteBuilder Subscribe<TEvent>(

builder
.MapPost(route, (TEvent receivedEvent, IEventBus eventBus) => eventBus.ReceiveAsync(receivedEvent))
.WithTopic(DaprOptions.PubSubName, DaprUtils.GetDaprTopicName<TEvent>(appName));
.WithTopic(DaprOptions.PubSubName, DaprUtils.GetDaprTopicName<TEvent>(appName))
.WithTags("Subscriptions");

return builder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.25.0" />
<PackageReference Include="MongoDB.Driver" Version="2.26.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7200;http://localhost:5200",
"applicationUrl": "https://localhost:8200;http://localhost:5200",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
Expand Down
Loading
0