checkpoint
This commit is contained in:
parent
5ba351de9c
commit
ed01f58a0c
@ -27,7 +27,10 @@
|
|||||||
"Bash(dotnet --list-sdks:*)",
|
"Bash(dotnet --list-sdks:*)",
|
||||||
"Bash(dotnet sln:*)",
|
"Bash(dotnet sln:*)",
|
||||||
"Bash(pkill:*)",
|
"Bash(pkill:*)",
|
||||||
"Bash(python3:*)"
|
"Bash(python3:*)",
|
||||||
|
"Bash(grpcurl:*)",
|
||||||
|
"Bash(lsof:*)",
|
||||||
|
"Bash(xargs kill -9)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@ -40,6 +40,9 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
var commandHandlerInterface = compilation.GetTypeByMetadataName("Svrnty.CQRS.Abstractions.ICommandHandler`1");
|
var commandHandlerInterface = compilation.GetTypeByMetadataName("Svrnty.CQRS.Abstractions.ICommandHandler`1");
|
||||||
var commandHandlerWithResultInterface = compilation.GetTypeByMetadataName("Svrnty.CQRS.Abstractions.ICommandHandler`2");
|
var commandHandlerWithResultInterface = compilation.GetTypeByMetadataName("Svrnty.CQRS.Abstractions.ICommandHandler`2");
|
||||||
var queryHandlerInterface = compilation.GetTypeByMetadataName("Svrnty.CQRS.Abstractions.IQueryHandler`2");
|
var queryHandlerInterface = compilation.GetTypeByMetadataName("Svrnty.CQRS.Abstractions.IQueryHandler`2");
|
||||||
|
var dynamicQueryInterface2 = compilation.GetTypeByMetadataName("Svrnty.CQRS.DynamicQuery.Abstractions.IDynamicQuery`2");
|
||||||
|
var dynamicQueryInterface3 = compilation.GetTypeByMetadataName("Svrnty.CQRS.DynamicQuery.Abstractions.IDynamicQuery`3");
|
||||||
|
var queryableProviderInterface = compilation.GetTypeByMetadataName("Svrnty.CQRS.DynamicQuery.Abstractions.IQueryableProvider`1");
|
||||||
|
|
||||||
if (commandHandlerInterface == null || queryHandlerInterface == null)
|
if (commandHandlerInterface == null || queryHandlerInterface == null)
|
||||||
{
|
{
|
||||||
@ -48,6 +51,7 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
|
|
||||||
var commandMap = new Dictionary<INamedTypeSymbol, INamedTypeSymbol?>(SymbolEqualityComparer.Default); // Command -> Result type (null if no result)
|
var commandMap = new Dictionary<INamedTypeSymbol, INamedTypeSymbol?>(SymbolEqualityComparer.Default); // Command -> Result type (null if no result)
|
||||||
var queryMap = new Dictionary<INamedTypeSymbol, INamedTypeSymbol>(SymbolEqualityComparer.Default); // Query -> Result type
|
var queryMap = new Dictionary<INamedTypeSymbol, INamedTypeSymbol>(SymbolEqualityComparer.Default); // Query -> Result type
|
||||||
|
var dynamicQueryMap = new List<(INamedTypeSymbol SourceType, INamedTypeSymbol DestinationType, INamedTypeSymbol? ParamsType)>(); // List of (Source, Destination, Params?)
|
||||||
|
|
||||||
// Find all command and query types by looking at handler implementations
|
// Find all command and query types by looking at handler implementations
|
||||||
foreach (var typeSymbol in types)
|
foreach (var typeSymbol in types)
|
||||||
@ -81,7 +85,68 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
var queryType = iface.TypeArguments[0] as INamedTypeSymbol;
|
var queryType = iface.TypeArguments[0] as INamedTypeSymbol;
|
||||||
var resultType = iface.TypeArguments[1] as INamedTypeSymbol;
|
var resultType = iface.TypeArguments[1] as INamedTypeSymbol;
|
||||||
if (queryType != null && resultType != null)
|
if (queryType != null && resultType != null)
|
||||||
queryMap[queryType] = resultType;
|
{
|
||||||
|
// Check if this is a dynamic query handler
|
||||||
|
if (queryType.IsGenericType &&
|
||||||
|
(SymbolEqualityComparer.Default.Equals(queryType.OriginalDefinition, dynamicQueryInterface2) ||
|
||||||
|
SymbolEqualityComparer.Default.Equals(queryType.OriginalDefinition, dynamicQueryInterface3)))
|
||||||
|
{
|
||||||
|
// Extract source, destination, and optional params types
|
||||||
|
var sourceType = queryType.TypeArguments[0] as INamedTypeSymbol;
|
||||||
|
var destinationType = queryType.TypeArguments[1] as INamedTypeSymbol;
|
||||||
|
INamedTypeSymbol? paramsType = null;
|
||||||
|
|
||||||
|
if (queryType.TypeArguments.Length == 3)
|
||||||
|
{
|
||||||
|
paramsType = queryType.TypeArguments[2] as INamedTypeSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceType != null && destinationType != null)
|
||||||
|
{
|
||||||
|
// Check if already added (avoid duplicates)
|
||||||
|
var exists = dynamicQueryMap.Any(dq =>
|
||||||
|
SymbolEqualityComparer.Default.Equals(dq.SourceType, sourceType) &&
|
||||||
|
SymbolEqualityComparer.Default.Equals(dq.DestinationType, destinationType) &&
|
||||||
|
(dq.ParamsType == null && paramsType == null ||
|
||||||
|
dq.ParamsType != null && paramsType != null && SymbolEqualityComparer.Default.Equals(dq.ParamsType, paramsType)));
|
||||||
|
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
dynamicQueryMap.Add((sourceType, destinationType, paramsType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
queryMap[queryType] = resultType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this type implements IQueryableProvider<T> - this indicates a dynamic query
|
||||||
|
if (queryableProviderInterface != null)
|
||||||
|
{
|
||||||
|
foreach (var iface in typeSymbol.AllInterfaces)
|
||||||
|
{
|
||||||
|
if (iface.IsGenericType && SymbolEqualityComparer.Default.Equals(iface.OriginalDefinition, queryableProviderInterface))
|
||||||
|
{
|
||||||
|
// Extract source type from IQueryableProvider<TSource>
|
||||||
|
var sourceType = iface.TypeArguments[0] as INamedTypeSymbol;
|
||||||
|
if (sourceType != null)
|
||||||
|
{
|
||||||
|
// For IQueryableProvider, we assume TSource = TDestination (no params)
|
||||||
|
var exists = dynamicQueryMap.Any(dq =>
|
||||||
|
SymbolEqualityComparer.Default.Equals(dq.SourceType, sourceType) &&
|
||||||
|
SymbolEqualityComparer.Default.Equals(dq.DestinationType, sourceType) &&
|
||||||
|
dq.ParamsType == null);
|
||||||
|
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
dynamicQueryMap.Add((sourceType, sourceType, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,10 +185,19 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
queries.Add(queryInfo);
|
queries.Add(queryInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate services if we found any commands or queries
|
// Process discovered dynamic query types
|
||||||
if (commands.Any() || queries.Any())
|
var dynamicQueries = new List<DynamicQueryInfo>();
|
||||||
|
foreach (var (sourceType, destinationType, paramsType) in dynamicQueryMap)
|
||||||
{
|
{
|
||||||
GenerateProtoAndServices(context, commands, queries, compilation);
|
var dynamicQueryInfo = ExtractDynamicQueryInfo(sourceType, destinationType, paramsType);
|
||||||
|
if (dynamicQueryInfo != null)
|
||||||
|
dynamicQueries.Add(dynamicQueryInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate services if we found any commands, queries, or dynamic queries
|
||||||
|
if (commands.Any() || queries.Any() || dynamicQueries.Any())
|
||||||
|
{
|
||||||
|
GenerateProtoAndServices(context, commands, queries, dynamicQueries, compilation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,7 +339,52 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
return queryInfo;
|
return queryInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateProtoAndServices(SourceProductionContext context, List<CommandInfo> commands, List<QueryInfo> queries, Compilation compilation)
|
private static DynamicQueryInfo? ExtractDynamicQueryInfo(INamedTypeSymbol sourceType, INamedTypeSymbol destinationType, INamedTypeSymbol? paramsType)
|
||||||
|
{
|
||||||
|
var dynamicQueryInfo = new DynamicQueryInfo
|
||||||
|
{
|
||||||
|
SourceType = sourceType.Name,
|
||||||
|
SourceTypeFullyQualified = sourceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||||
|
DestinationType = destinationType.Name,
|
||||||
|
DestinationTypeFullyQualified = destinationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||||
|
HasParams = paramsType != null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (paramsType != null)
|
||||||
|
{
|
||||||
|
dynamicQueryInfo.ParamsType = paramsType.Name;
|
||||||
|
dynamicQueryInfo.ParamsTypeFullyQualified = paramsType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pluralize destination type for naming (e.g., User -> Users)
|
||||||
|
dynamicQueryInfo.Name = Pluralize(destinationType.Name);
|
||||||
|
|
||||||
|
// Build interface names
|
||||||
|
if (paramsType != null)
|
||||||
|
{
|
||||||
|
dynamicQueryInfo.QueryInterfaceName = $"IDynamicQuery<{dynamicQueryInfo.SourceTypeFullyQualified}, {dynamicQueryInfo.DestinationTypeFullyQualified}, {dynamicQueryInfo.ParamsTypeFullyQualified}>";
|
||||||
|
dynamicQueryInfo.HandlerInterfaceName = $"IQueryHandler<{dynamicQueryInfo.QueryInterfaceName}, IQueryExecutionResult<{dynamicQueryInfo.DestinationTypeFullyQualified}>>";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dynamicQueryInfo.QueryInterfaceName = $"IDynamicQuery<{dynamicQueryInfo.SourceTypeFullyQualified}, {dynamicQueryInfo.DestinationTypeFullyQualified}>";
|
||||||
|
dynamicQueryInfo.HandlerInterfaceName = $"IQueryHandler<{dynamicQueryInfo.QueryInterfaceName}, IQueryExecutionResult<{dynamicQueryInfo.DestinationTypeFullyQualified}>>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamicQueryInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Pluralize(string word)
|
||||||
|
{
|
||||||
|
// Simple pluralization logic - can be enhanced with Pluralize.NET if needed
|
||||||
|
if (word.EndsWith("y") && word.Length > 1 && !"aeiou".Contains(word[word.Length - 2]))
|
||||||
|
return word.Substring(0, word.Length - 1) + "ies";
|
||||||
|
if (word.EndsWith("s") || word.EndsWith("x") || word.EndsWith("z") || word.EndsWith("ch") || word.EndsWith("sh"))
|
||||||
|
return word + "es";
|
||||||
|
return word + "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GenerateProtoAndServices(SourceProductionContext context, List<CommandInfo> commands, List<QueryInfo> queries, List<DynamicQueryInfo> dynamicQueries, Compilation compilation)
|
||||||
{
|
{
|
||||||
// Get root namespace from compilation
|
// Get root namespace from compilation
|
||||||
var rootNamespace = compilation.AssemblyName ?? "Application";
|
var rootNamespace = compilation.AssemblyName ?? "Application";
|
||||||
@ -284,8 +403,15 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
context.AddSource("QueryServiceImpl.g.cs", queryService);
|
context.AddSource("QueryServiceImpl.g.cs", queryService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate service implementations for dynamic queries
|
||||||
|
if (dynamicQueries.Any())
|
||||||
|
{
|
||||||
|
var dynamicQueryService = GenerateDynamicQueryServiceImpl(dynamicQueries, rootNamespace);
|
||||||
|
context.AddSource("DynamicQueryServiceImpl.g.cs", dynamicQueryService);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate registration extensions
|
// Generate registration extensions
|
||||||
var registrationExtensions = GenerateRegistrationExtensions(commands.Any(), queries.Any(), rootNamespace);
|
var registrationExtensions = GenerateRegistrationExtensions(commands.Any(), queries.Any(), dynamicQueries.Any(), rootNamespace);
|
||||||
context.AddSource("GrpcServiceRegistration.g.cs", registrationExtensions);
|
context.AddSource("GrpcServiceRegistration.g.cs", registrationExtensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,7 +668,7 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateRegistrationExtensions(bool hasCommands, bool hasQueries, string rootNamespace)
|
private static string GenerateRegistrationExtensions(bool hasCommands, bool hasQueries, bool hasDynamicQueries, string rootNamespace)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("// <auto-generated />");
|
sb.AppendLine("// <auto-generated />");
|
||||||
@ -605,10 +731,33 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasCommands && hasQueries)
|
if (hasDynamicQueries)
|
||||||
{
|
{
|
||||||
sb.AppendLine(" /// <summary>");
|
sb.AppendLine(" /// <summary>");
|
||||||
sb.AppendLine(" /// Registers both Command and Query gRPC services");
|
sb.AppendLine(" /// Registers the auto-generated DynamicQuery gRPC service");
|
||||||
|
sb.AppendLine(" /// </summary>");
|
||||||
|
sb.AppendLine(" public static IServiceCollection AddGrpcDynamicQueryService(this IServiceCollection services)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" services.AddGrpc();");
|
||||||
|
sb.AppendLine(" services.AddSingleton<DynamicQueryServiceImpl>();");
|
||||||
|
sb.AppendLine(" return services;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" /// <summary>");
|
||||||
|
sb.AppendLine(" /// Maps the auto-generated DynamicQuery gRPC service endpoints");
|
||||||
|
sb.AppendLine(" /// </summary>");
|
||||||
|
sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcDynamicQueries(this IEndpointRouteBuilder endpoints)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" endpoints.MapGrpcService<DynamicQueryServiceImpl>();");
|
||||||
|
sb.AppendLine(" return endpoints;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCommands || hasQueries || hasDynamicQueries)
|
||||||
|
{
|
||||||
|
sb.AppendLine(" /// <summary>");
|
||||||
|
sb.AppendLine(" /// Registers all auto-generated gRPC services (Commands, Queries, and DynamicQueries)");
|
||||||
sb.AppendLine(" /// </summary>");
|
sb.AppendLine(" /// </summary>");
|
||||||
sb.AppendLine(" public static IServiceCollection AddGrpcCommandsAndQueries(this IServiceCollection services)");
|
sb.AppendLine(" public static IServiceCollection AddGrpcCommandsAndQueries(this IServiceCollection services)");
|
||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
@ -618,11 +767,13 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
sb.AppendLine(" services.AddSingleton<CommandServiceImpl>();");
|
sb.AppendLine(" services.AddSingleton<CommandServiceImpl>();");
|
||||||
if (hasQueries)
|
if (hasQueries)
|
||||||
sb.AppendLine(" services.AddSingleton<QueryServiceImpl>();");
|
sb.AppendLine(" services.AddSingleton<QueryServiceImpl>();");
|
||||||
|
if (hasDynamicQueries)
|
||||||
|
sb.AppendLine(" services.AddSingleton<DynamicQueryServiceImpl>();");
|
||||||
sb.AppendLine(" return services;");
|
sb.AppendLine(" return services;");
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine(" /// <summary>");
|
sb.AppendLine(" /// <summary>");
|
||||||
sb.AppendLine(" /// Maps both Command and Query gRPC service endpoints");
|
sb.AppendLine(" /// Maps all auto-generated gRPC service endpoints (Commands, Queries, and DynamicQueries)");
|
||||||
sb.AppendLine(" /// </summary>");
|
sb.AppendLine(" /// </summary>");
|
||||||
sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcCommandsAndQueries(this IEndpointRouteBuilder endpoints)");
|
sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcCommandsAndQueries(this IEndpointRouteBuilder endpoints)");
|
||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
@ -630,6 +781,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
sb.AppendLine(" endpoints.MapGrpcService<CommandServiceImpl>();");
|
sb.AppendLine(" endpoints.MapGrpcService<CommandServiceImpl>();");
|
||||||
if (hasQueries)
|
if (hasQueries)
|
||||||
sb.AppendLine(" endpoints.MapGrpcService<QueryServiceImpl>();");
|
sb.AppendLine(" endpoints.MapGrpcService<QueryServiceImpl>();");
|
||||||
|
if (hasDynamicQueries)
|
||||||
|
sb.AppendLine(" endpoints.MapGrpcService<DynamicQueryServiceImpl>();");
|
||||||
sb.AppendLine(" return endpoints;");
|
sb.AppendLine(" return endpoints;");
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
}
|
}
|
||||||
@ -943,5 +1096,389 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
typeName.StartsWith("System.Nullable<") ||
|
typeName.StartsWith("System.Nullable<") ||
|
||||||
typeName.EndsWith("?");
|
typeName.EndsWith("?");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GenerateDynamicQueryMessages(List<DynamicQueryInfo> dynamicQueries, string rootNamespace)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("// <auto-generated />");
|
||||||
|
sb.AppendLine("#nullable enable");
|
||||||
|
sb.AppendLine("using System.Collections.Generic;");
|
||||||
|
sb.AppendLine("using System.ServiceModel;");
|
||||||
|
sb.AppendLine("using System.Runtime.Serialization;");
|
||||||
|
sb.AppendLine("using ProtoBuf;");
|
||||||
|
sb.AppendLine("using ProtoBuf.Grpc;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"namespace {rootNamespace}.Grpc.DynamicQuery");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
|
||||||
|
// Common message types
|
||||||
|
sb.AppendLine(" /// <summary>");
|
||||||
|
sb.AppendLine(" /// Dynamic query filter with support for nested AND/OR logic");
|
||||||
|
sb.AppendLine(" /// </summary>");
|
||||||
|
sb.AppendLine(" [ProtoContract]");
|
||||||
|
sb.AppendLine(" [DataContract]");
|
||||||
|
sb.AppendLine(" public sealed class DynamicQueryFilter");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" [ProtoMember(1)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 1)]");
|
||||||
|
sb.AppendLine(" public string Path { get; set; } = string.Empty;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(2)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 2)]");
|
||||||
|
sb.AppendLine(" public int Type { get; set; } // Maps to PoweredSoft.DynamicQuery.Core.FilterType");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(3)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 3)]");
|
||||||
|
sb.AppendLine(" public string Value { get; set; } = string.Empty;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(4)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 4)]");
|
||||||
|
sb.AppendLine(" public List<DynamicQueryFilter>? And { get; set; }");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(5)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 5)]");
|
||||||
|
sb.AppendLine(" public List<DynamicQueryFilter>? Or { get; set; }");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine(" /// <summary>");
|
||||||
|
sb.AppendLine(" /// Dynamic query sort");
|
||||||
|
sb.AppendLine(" /// </summary>");
|
||||||
|
sb.AppendLine(" [ProtoContract]");
|
||||||
|
sb.AppendLine(" [DataContract]");
|
||||||
|
sb.AppendLine(" public sealed class DynamicQuerySort");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" [ProtoMember(1)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 1)]");
|
||||||
|
sb.AppendLine(" public string Path { get; set; } = string.Empty;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(2)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 2)]");
|
||||||
|
sb.AppendLine(" public bool Ascending { get; set; } = true;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine(" /// <summary>");
|
||||||
|
sb.AppendLine(" /// Dynamic query group");
|
||||||
|
sb.AppendLine(" /// </summary>");
|
||||||
|
sb.AppendLine(" [ProtoContract]");
|
||||||
|
sb.AppendLine(" [DataContract]");
|
||||||
|
sb.AppendLine(" public sealed class DynamicQueryGroup");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" [ProtoMember(1)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 1)]");
|
||||||
|
sb.AppendLine(" public string Path { get; set; } = string.Empty;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine(" /// <summary>");
|
||||||
|
sb.AppendLine(" /// Dynamic query aggregate");
|
||||||
|
sb.AppendLine(" /// </summary>");
|
||||||
|
sb.AppendLine(" [ProtoContract]");
|
||||||
|
sb.AppendLine(" [DataContract]");
|
||||||
|
sb.AppendLine(" public sealed class DynamicQueryAggregate");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" [ProtoMember(1)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 1)]");
|
||||||
|
sb.AppendLine(" public string Path { get; set; } = string.Empty;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(2)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 2)]");
|
||||||
|
sb.AppendLine(" public int Type { get; set; } // Maps to PoweredSoft.DynamicQuery.Core.AggregateType");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Generate request/response messages for each dynamic query
|
||||||
|
foreach (var dynamicQuery in dynamicQueries)
|
||||||
|
{
|
||||||
|
// Request message
|
||||||
|
sb.AppendLine($" /// <summary>");
|
||||||
|
sb.AppendLine($" /// Request message for dynamic query on {dynamicQuery.Name}");
|
||||||
|
sb.AppendLine($" /// </summary>");
|
||||||
|
sb.AppendLine(" [ProtoContract]");
|
||||||
|
sb.AppendLine(" [DataContract]");
|
||||||
|
sb.AppendLine($" public sealed class DynamicQuery{dynamicQuery.Name}Request");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" [ProtoMember(1)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 1)]");
|
||||||
|
sb.AppendLine(" public int Page { get; set; }");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(2)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 2)]");
|
||||||
|
sb.AppendLine(" public int PageSize { get; set; }");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(3)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 3)]");
|
||||||
|
sb.AppendLine(" public List<DynamicQueryFilter> Filters { get; set; } = new();");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(4)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 4)]");
|
||||||
|
sb.AppendLine(" public List<DynamicQuerySort> Sorts { get; set; } = new();");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(5)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 5)]");
|
||||||
|
sb.AppendLine(" public List<DynamicQueryGroup> Groups { get; set; } = new();");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(6)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 6)]");
|
||||||
|
sb.AppendLine(" public List<DynamicQueryAggregate> Aggregates { get; set; } = new();");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Response message
|
||||||
|
sb.AppendLine($" /// <summary>");
|
||||||
|
sb.AppendLine($" /// Response message for dynamic query on {dynamicQuery.Name}");
|
||||||
|
sb.AppendLine($" /// </summary>");
|
||||||
|
sb.AppendLine(" [ProtoContract]");
|
||||||
|
sb.AppendLine(" [DataContract]");
|
||||||
|
sb.AppendLine($" public sealed class DynamicQuery{dynamicQuery.Name}Response");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" [ProtoMember(1)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 1)]");
|
||||||
|
sb.AppendLine($" public List<{dynamicQuery.DestinationTypeFullyQualified}> Data {{ get; set; }} = new();");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(2)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 2)]");
|
||||||
|
sb.AppendLine(" public long TotalCount { get; set; }");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" [ProtoMember(3)]");
|
||||||
|
sb.AppendLine(" [DataMember(Order = 3)]");
|
||||||
|
sb.AppendLine(" public int NumberOfPages { get; set; }");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate service interface
|
||||||
|
sb.AppendLine(" /// <summary>");
|
||||||
|
sb.AppendLine(" /// gRPC service interface for DynamicQueries");
|
||||||
|
sb.AppendLine(" /// </summary>");
|
||||||
|
sb.AppendLine(" [ServiceContract]");
|
||||||
|
sb.AppendLine(" public interface IDynamicQueryService");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
|
||||||
|
foreach (var dynamicQuery in dynamicQueries)
|
||||||
|
{
|
||||||
|
var methodName = $"Query{dynamicQuery.Name}";
|
||||||
|
sb.AppendLine($" /// <summary>");
|
||||||
|
sb.AppendLine($" /// Execute dynamic query on {dynamicQuery.Name}");
|
||||||
|
sb.AppendLine($" /// </summary>");
|
||||||
|
sb.AppendLine(" [OperationContract]");
|
||||||
|
sb.AppendLine($" System.Threading.Tasks.Task<DynamicQuery{dynamicQuery.Name}Response> {methodName}Async(DynamicQuery{dynamicQuery.Name}Request request, CallContext context = default);");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine("}");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateDynamicQueryServiceImpl(List<DynamicQueryInfo> dynamicQueries, string rootNamespace)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("// <auto-generated />");
|
||||||
|
sb.AppendLine("#nullable enable");
|
||||||
|
sb.AppendLine("using Grpc.Core;");
|
||||||
|
sb.AppendLine("using System.Threading.Tasks;");
|
||||||
|
sb.AppendLine("using System.Collections.Generic;");
|
||||||
|
sb.AppendLine("using System.Linq;");
|
||||||
|
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
|
||||||
|
sb.AppendLine($"using {rootNamespace}.Grpc;");
|
||||||
|
sb.AppendLine("using Svrnty.CQRS.Abstractions;");
|
||||||
|
sb.AppendLine("using Svrnty.CQRS.DynamicQuery.Abstractions;");
|
||||||
|
sb.AppendLine("using PoweredSoft.DynamicQuery.Core;");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine($"namespace {rootNamespace}.Grpc.Services");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
sb.AppendLine(" /// <summary>");
|
||||||
|
sb.AppendLine(" /// Auto-generated gRPC service implementation for DynamicQueries");
|
||||||
|
sb.AppendLine(" /// </summary>");
|
||||||
|
sb.AppendLine(" public sealed class DynamicQueryServiceImpl : DynamicQueryService.DynamicQueryServiceBase");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" private readonly IServiceProvider _serviceProvider;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" public DynamicQueryServiceImpl(IServiceProvider serviceProvider)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" _serviceProvider = serviceProvider;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
foreach (var dynamicQuery in dynamicQueries)
|
||||||
|
{
|
||||||
|
var methodName = $"Query{dynamicQuery.Name}";
|
||||||
|
var requestType = $"DynamicQuery{dynamicQuery.Name}Request";
|
||||||
|
var responseType = $"DynamicQuery{dynamicQuery.Name}Response";
|
||||||
|
|
||||||
|
sb.AppendLine($" public override async Task<{responseType}> {methodName}(");
|
||||||
|
sb.AppendLine($" {requestType} request,");
|
||||||
|
sb.AppendLine(" ServerCallContext context)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
|
||||||
|
// Build the dynamic query object
|
||||||
|
if (dynamicQuery.HasParams)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" var query = new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQuery<{dynamicQuery.SourceTypeFullyQualified}, {dynamicQuery.DestinationTypeFullyQualified}, {dynamicQuery.ParamsTypeFullyQualified}>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.AppendLine($" var query = new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQuery<{dynamicQuery.SourceTypeFullyQualified}, {dynamicQuery.DestinationTypeFullyQualified}>");
|
||||||
|
}
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" Page = request.Page > 0 ? request.Page : null,");
|
||||||
|
sb.AppendLine(" PageSize = request.PageSize > 0 ? request.PageSize : null,");
|
||||||
|
sb.AppendLine(" Filters = ConvertFilters(request.Filters),");
|
||||||
|
sb.AppendLine(" Sorts = ConvertSorts(request.Sorts),");
|
||||||
|
sb.AppendLine(" Groups = ConvertGroups(request.Groups),");
|
||||||
|
sb.AppendLine(" Aggregates = ConvertAggregates(request.Aggregates)");
|
||||||
|
sb.AppendLine(" };");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Get the handler and execute
|
||||||
|
sb.AppendLine($" var handler = _serviceProvider.GetRequiredService<IQueryHandler<{dynamicQuery.QueryInterfaceName}, IQueryExecutionResult<{dynamicQuery.DestinationTypeFullyQualified}>>>();");
|
||||||
|
sb.AppendLine(" var result = await handler.HandleAsync(query, context.CancellationToken);");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Build response
|
||||||
|
sb.AppendLine($" var response = new {responseType}");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" TotalRecords = result.TotalRecords,");
|
||||||
|
sb.AppendLine(" NumberOfPages = (int)(result.NumberOfPages ?? 0)");
|
||||||
|
sb.AppendLine(" };");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" if (result.Data != null)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" foreach (var item in result.Data)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine($" response.Data.Add(MapTo{dynamicQuery.Name}ProtoModel(item));");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" // TODO: Add aggregates and groups to response if needed");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" return response;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add helper methods for converting proto messages to AspNetCore types
|
||||||
|
sb.AppendLine(" private static List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter>? ConvertFilters(Google.Protobuf.Collections.RepeatedField<DynamicQueryFilter> protoFilters)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" if (protoFilters == null || protoFilters.Count == 0)");
|
||||||
|
sb.AppendLine(" return null;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" var filters = new List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter>();");
|
||||||
|
sb.AppendLine(" foreach (var protoFilter in protoFilters)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" var filter = new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" Path = protoFilter.Path,");
|
||||||
|
sb.AppendLine(" Type = ((PoweredSoft.DynamicQuery.Core.FilterType)protoFilter.Type).ToString(),");
|
||||||
|
sb.AppendLine(" Value = protoFilter.Value");
|
||||||
|
sb.AppendLine(" };");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" // Handle nested AND filters");
|
||||||
|
sb.AppendLine(" if (protoFilter.And != null && protoFilter.And.Count > 0)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" filter.Filters = ConvertProtoFiltersToList(protoFilter.And);");
|
||||||
|
sb.AppendLine(" filter.And = true;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine(" // Handle nested OR filters");
|
||||||
|
sb.AppendLine(" else if (protoFilter.Or != null && protoFilter.Or.Count > 0)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" filter.Filters = ConvertProtoFiltersToList(protoFilter.Or);");
|
||||||
|
sb.AppendLine(" filter.And = false;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" filters.Add(filter);");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine(" return filters;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" private static List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter> ConvertProtoFiltersToList(Google.Protobuf.Collections.RepeatedField<DynamicQueryFilter> protoFilters)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" var result = new List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter>();");
|
||||||
|
sb.AppendLine(" foreach (var pf in protoFilters)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" var filter = new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" Path = pf.Path,");
|
||||||
|
sb.AppendLine(" Type = ((PoweredSoft.DynamicQuery.Core.FilterType)pf.Type).ToString(),");
|
||||||
|
sb.AppendLine(" Value = pf.Value");
|
||||||
|
sb.AppendLine(" };");
|
||||||
|
sb.AppendLine(" if (pf.And != null && pf.And.Count > 0)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" filter.Filters = ConvertProtoFiltersToList(pf.And);");
|
||||||
|
sb.AppendLine(" filter.And = true;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine(" else if (pf.Or != null && pf.Or.Count > 0)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" filter.Filters = ConvertProtoFiltersToList(pf.Or);");
|
||||||
|
sb.AppendLine(" filter.And = false;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine(" result.Add(filter);");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine(" return result;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine(" private static List<PoweredSoft.DynamicQuery.Sort>? ConvertSorts(Google.Protobuf.Collections.RepeatedField<DynamicQuerySort> protoSorts)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" if (protoSorts == null || protoSorts.Count == 0)");
|
||||||
|
sb.AppendLine(" return null;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" return protoSorts.Select(s => new PoweredSoft.DynamicQuery.Sort");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" Path = s.Path,");
|
||||||
|
sb.AppendLine(" Ascending = s.Ascending");
|
||||||
|
sb.AppendLine(" }).ToList();");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine(" private static List<PoweredSoft.DynamicQuery.Group>? ConvertGroups(Google.Protobuf.Collections.RepeatedField<DynamicQueryGroup> protoGroups)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" if (protoGroups == null || protoGroups.Count == 0)");
|
||||||
|
sb.AppendLine(" return null;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" return protoGroups.Select(g => new PoweredSoft.DynamicQuery.Group");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" Path = g.Path");
|
||||||
|
sb.AppendLine(" }).ToList();");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine(" private static List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryAggregate>? ConvertAggregates(Google.Protobuf.Collections.RepeatedField<DynamicQueryAggregate> protoAggregates)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" if (protoAggregates == null || protoAggregates.Count == 0)");
|
||||||
|
sb.AppendLine(" return null;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" return protoAggregates.Select(a => new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryAggregate");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" Path = a.Path,");
|
||||||
|
sb.AppendLine(" Type = ((PoweredSoft.DynamicQuery.Core.AggregateType)a.Type).ToString()");
|
||||||
|
sb.AppendLine(" }).ToList();");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Add mapper methods for each entity type
|
||||||
|
foreach (var dynamicQuery in dynamicQueries)
|
||||||
|
{
|
||||||
|
var entityName = dynamicQuery.Name;
|
||||||
|
var protoTypeName = $"{entityName.TrimEnd('s')}"; // User from Users
|
||||||
|
|
||||||
|
sb.AppendLine($" private static {protoTypeName} MapTo{entityName}ProtoModel({dynamicQuery.DestinationTypeFullyQualified} domainModel)");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine($" // Use JSON serialization for mapping between domain and proto models");
|
||||||
|
sb.AppendLine(" var json = System.Text.Json.JsonSerializer.Serialize(domainModel);");
|
||||||
|
sb.AppendLine($" return System.Text.Json.JsonSerializer.Deserialize<{protoTypeName}>(json, new System.Text.Json.JsonSerializerOptions {{ PropertyNameCaseInsensitive = true }}) ?? new {protoTypeName}();");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine("}");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
Svrnty.CQRS.Grpc.Generators/Models/DynamicQueryInfo.cs
Normal file
28
Svrnty.CQRS.Grpc.Generators/Models/DynamicQueryInfo.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||||
|
{
|
||||||
|
public class DynamicQueryInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string SourceType { get; set; }
|
||||||
|
public string SourceTypeFullyQualified { get; set; }
|
||||||
|
public string DestinationType { get; set; }
|
||||||
|
public string DestinationTypeFullyQualified { get; set; }
|
||||||
|
public string? ParamsType { get; set; }
|
||||||
|
public string? ParamsTypeFullyQualified { get; set; }
|
||||||
|
public string HandlerInterfaceName { get; set; }
|
||||||
|
public string QueryInterfaceName { get; set; }
|
||||||
|
public bool HasParams { get; set; }
|
||||||
|
|
||||||
|
public DynamicQueryInfo()
|
||||||
|
{
|
||||||
|
Name = string.Empty;
|
||||||
|
SourceType = string.Empty;
|
||||||
|
SourceTypeFullyQualified = string.Empty;
|
||||||
|
DestinationType = string.Empty;
|
||||||
|
DestinationTypeFullyQualified = string.Empty;
|
||||||
|
HandlerInterfaceName = string.Empty;
|
||||||
|
QueryInterfaceName = string.Empty;
|
||||||
|
HasParams = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,6 +24,7 @@ internal class ProtoFileGenerator
|
|||||||
{
|
{
|
||||||
var commands = DiscoverCommands();
|
var commands = DiscoverCommands();
|
||||||
var queries = DiscoverQueries();
|
var queries = DiscoverQueries();
|
||||||
|
var dynamicQueries = DiscoverDynamicQueries();
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
@ -76,6 +77,27 @@ internal class ProtoFileGenerator
|
|||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DynamicQuery Service
|
||||||
|
if (dynamicQueries.Any())
|
||||||
|
{
|
||||||
|
sb.AppendLine("// DynamicQuery service for CQRS operations");
|
||||||
|
sb.AppendLine("service DynamicQueryService {");
|
||||||
|
foreach (var dq in dynamicQueries)
|
||||||
|
{
|
||||||
|
var entityName = dq.Name;
|
||||||
|
var pluralName = Pluralize(entityName);
|
||||||
|
var methodName = $"Query{pluralName}";
|
||||||
|
var requestType = $"DynamicQuery{pluralName}Request";
|
||||||
|
var responseType = $"DynamicQuery{pluralName}Response";
|
||||||
|
|
||||||
|
sb.AppendLine($" // Dynamic query for {entityName}");
|
||||||
|
sb.AppendLine($" rpc {methodName} ({requestType}) returns ({responseType});");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
sb.AppendLine("}");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
// Generate messages for commands
|
// Generate messages for commands
|
||||||
foreach (var command in commands)
|
foreach (var command in commands)
|
||||||
{
|
{
|
||||||
@ -90,6 +112,12 @@ internal class ProtoFileGenerator
|
|||||||
GenerateResponseMessage(query);
|
GenerateResponseMessage(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate messages for dynamic queries
|
||||||
|
foreach (var dq in dynamicQueries)
|
||||||
|
{
|
||||||
|
GenerateDynamicQueryMessages(dq);
|
||||||
|
}
|
||||||
|
|
||||||
// Append all generated messages
|
// Append all generated messages
|
||||||
sb.Append(_messagesBuilder);
|
sb.Append(_messagesBuilder);
|
||||||
|
|
||||||
@ -335,4 +363,112 @@ internal class ProtoFileGenerator
|
|||||||
|
|
||||||
return $"{type.Name} operation";
|
return $"{type.Name} operation";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<INamedTypeSymbol> DiscoverDynamicQueries()
|
||||||
|
{
|
||||||
|
// Find IQueryableProvider<T> implementations
|
||||||
|
var queryableProviderInterface = _compilation.GetTypeByMetadataName("Svrnty.CQRS.DynamicQuery.Abstractions.IQueryableProvider`1");
|
||||||
|
if (queryableProviderInterface == null)
|
||||||
|
return new List<INamedTypeSymbol>();
|
||||||
|
|
||||||
|
var dynamicQueryTypes = new List<INamedTypeSymbol>();
|
||||||
|
var allTypes = _compilation.GetSymbolsWithName(_ => true, SymbolFilter.Type)
|
||||||
|
.OfType<INamedTypeSymbol>();
|
||||||
|
|
||||||
|
foreach (var type in allTypes)
|
||||||
|
{
|
||||||
|
if (type.IsAbstract || type.IsStatic)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var iface in type.AllInterfaces)
|
||||||
|
{
|
||||||
|
if (iface.IsGenericType && SymbolEqualityComparer.Default.Equals(iface.OriginalDefinition, queryableProviderInterface))
|
||||||
|
{
|
||||||
|
// Extract the entity type from IQueryableProvider<TEntity>
|
||||||
|
var entityType = iface.TypeArguments[0] as INamedTypeSymbol;
|
||||||
|
if (entityType != null && !dynamicQueryTypes.Contains(entityType, SymbolEqualityComparer.Default))
|
||||||
|
{
|
||||||
|
dynamicQueryTypes.Add(entityType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamicQueryTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateDynamicQueryMessages(INamedTypeSymbol entityType)
|
||||||
|
{
|
||||||
|
var pluralName = Pluralize(entityType.Name);
|
||||||
|
var requestMessageName = $"DynamicQuery{pluralName}Request";
|
||||||
|
var responseMessageName = $"DynamicQuery{pluralName}Response";
|
||||||
|
|
||||||
|
// Common filter/sort/group/aggregate types (only generate once)
|
||||||
|
if (!_generatedMessages.Contains("DynamicQueryFilter"))
|
||||||
|
{
|
||||||
|
_generatedMessages.Add("DynamicQueryFilter");
|
||||||
|
_messagesBuilder.AppendLine("// Dynamic query filter with AND/OR support");
|
||||||
|
_messagesBuilder.AppendLine("message DynamicQueryFilter {");
|
||||||
|
_messagesBuilder.AppendLine(" string path = 1;");
|
||||||
|
_messagesBuilder.AppendLine(" int32 type = 2; // PoweredSoft.DynamicQuery.Core.FilterType");
|
||||||
|
_messagesBuilder.AppendLine(" string value = 3;");
|
||||||
|
_messagesBuilder.AppendLine(" repeated DynamicQueryFilter and = 4;");
|
||||||
|
_messagesBuilder.AppendLine(" repeated DynamicQueryFilter or = 5;");
|
||||||
|
_messagesBuilder.AppendLine("}");
|
||||||
|
_messagesBuilder.AppendLine();
|
||||||
|
|
||||||
|
_messagesBuilder.AppendLine("// Dynamic query sort");
|
||||||
|
_messagesBuilder.AppendLine("message DynamicQuerySort {");
|
||||||
|
_messagesBuilder.AppendLine(" string path = 1;");
|
||||||
|
_messagesBuilder.AppendLine(" bool ascending = 2;");
|
||||||
|
_messagesBuilder.AppendLine("}");
|
||||||
|
_messagesBuilder.AppendLine();
|
||||||
|
|
||||||
|
_messagesBuilder.AppendLine("// Dynamic query group");
|
||||||
|
_messagesBuilder.AppendLine("message DynamicQueryGroup {");
|
||||||
|
_messagesBuilder.AppendLine(" string path = 1;");
|
||||||
|
_messagesBuilder.AppendLine("}");
|
||||||
|
_messagesBuilder.AppendLine();
|
||||||
|
|
||||||
|
_messagesBuilder.AppendLine("// Dynamic query aggregate");
|
||||||
|
_messagesBuilder.AppendLine("message DynamicQueryAggregate {");
|
||||||
|
_messagesBuilder.AppendLine(" string path = 1;");
|
||||||
|
_messagesBuilder.AppendLine(" int32 type = 2; // PoweredSoft.DynamicQuery.Core.AggregateType");
|
||||||
|
_messagesBuilder.AppendLine("}");
|
||||||
|
_messagesBuilder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request message
|
||||||
|
_messagesBuilder.AppendLine($"// Dynamic query request for {entityType.Name}");
|
||||||
|
_messagesBuilder.AppendLine($"message {requestMessageName} {{");
|
||||||
|
_messagesBuilder.AppendLine(" int32 page = 1;");
|
||||||
|
_messagesBuilder.AppendLine(" int32 page_size = 2;");
|
||||||
|
_messagesBuilder.AppendLine(" repeated DynamicQueryFilter filters = 3;");
|
||||||
|
_messagesBuilder.AppendLine(" repeated DynamicQuerySort sorts = 4;");
|
||||||
|
_messagesBuilder.AppendLine(" repeated DynamicQueryGroup groups = 5;");
|
||||||
|
_messagesBuilder.AppendLine(" repeated DynamicQueryAggregate aggregates = 6;");
|
||||||
|
_messagesBuilder.AppendLine("}");
|
||||||
|
_messagesBuilder.AppendLine();
|
||||||
|
|
||||||
|
// Response message
|
||||||
|
_messagesBuilder.AppendLine($"// Dynamic query response for {entityType.Name}");
|
||||||
|
_messagesBuilder.AppendLine($"message {responseMessageName} {{");
|
||||||
|
_messagesBuilder.AppendLine($" repeated {entityType.Name} data = 1;");
|
||||||
|
_messagesBuilder.AppendLine(" int64 total_records = 2;");
|
||||||
|
_messagesBuilder.AppendLine(" int32 number_of_pages = 3;");
|
||||||
|
_messagesBuilder.AppendLine("}");
|
||||||
|
_messagesBuilder.AppendLine();
|
||||||
|
|
||||||
|
// Generate entity message if not already generated
|
||||||
|
GenerateComplexTypeMessage(entityType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Pluralize(string word)
|
||||||
|
{
|
||||||
|
if (word.EndsWith("y") && word.Length > 1 && !"aeiou".Contains(word[word.Length - 2].ToString()))
|
||||||
|
return word.Substring(0, word.Length - 1) + "ies";
|
||||||
|
if (word.EndsWith("s") || word.EndsWith("x") || word.EndsWith("z") || word.EndsWith("ch") || word.EndsWith("sh"))
|
||||||
|
return word + "es";
|
||||||
|
return word + "s";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,44 +19,33 @@ builder.WebHost.ConfigureKestrel(options =>
|
|||||||
options.ListenLocalhost(6001, o => o.Protocols = HttpProtocols.Http1);
|
options.ListenLocalhost(6001, o => o.Protocols = HttpProtocols.Http1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register command handlers with CQRS and FluentValidation
|
|
||||||
builder.Services.AddCommand<AddUserCommand, int, AddUserCommandHandler, AddUserCommandValidator>();
|
builder.Services.AddCommand<AddUserCommand, int, AddUserCommandHandler, AddUserCommandValidator>();
|
||||||
builder.Services.AddCommand<RemoveUserCommand, RemoveUserCommandHandler>();
|
builder.Services.AddCommand<RemoveUserCommand, RemoveUserCommandHandler>();
|
||||||
|
|
||||||
// Register query handlers with CQRS
|
|
||||||
builder.Services.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
|
builder.Services.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
|
||||||
|
|
||||||
// Register PoweredSoft.DynamicQuery services
|
|
||||||
builder.Services.AddTransient<PoweredSoft.Data.Core.IAsyncQueryableService, SimpleAsyncQueryableService>();
|
builder.Services.AddTransient<PoweredSoft.Data.Core.IAsyncQueryableService, SimpleAsyncQueryableService>();
|
||||||
builder.Services.AddTransient<PoweredSoft.DynamicQuery.Core.IQueryHandlerAsync, PoweredSoft.DynamicQuery.QueryHandlerAsync>();
|
builder.Services.AddTransient<PoweredSoft.DynamicQuery.Core.IQueryHandlerAsync, PoweredSoft.DynamicQuery.QueryHandlerAsync>();
|
||||||
|
|
||||||
// Register dynamic query for User entity with queryable provider
|
|
||||||
builder.Services.AddDynamicQueryWithProvider<User, UserQueryableProvider>();
|
builder.Services.AddDynamicQueryWithProvider<User, UserQueryableProvider>();
|
||||||
|
|
||||||
// Register discovery services for MinimalApi
|
|
||||||
builder.Services.AddDefaultCommandDiscovery();
|
builder.Services.AddDefaultCommandDiscovery();
|
||||||
builder.Services.AddDefaultQueryDiscovery();
|
builder.Services.AddDefaultQueryDiscovery();
|
||||||
|
|
||||||
// Auto-generated: Register gRPC services for both commands and queries (includes reflection)
|
|
||||||
builder.Services.AddGrpcCommandsAndQueries();
|
|
||||||
|
|
||||||
// Add Swagger/OpenAPI support
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Auto-generated: Map gRPC endpoints for both commands and queries
|
builder.Services.AddGrpcCommandsAndQueries();
|
||||||
app.MapGrpcCommandsAndQueries();
|
app.MapGrpcCommandsAndQueries();
|
||||||
|
|
||||||
// Map gRPC reflection service
|
|
||||||
app.MapGrpcReflectionService();
|
app.MapGrpcReflectionService();
|
||||||
|
|
||||||
// Enable Swagger middleware
|
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
|
|
||||||
// Map MinimalApi endpoints for commands and queries
|
|
||||||
app.MapSvrntyCommands();
|
app.MapSvrntyCommands();
|
||||||
app.MapSvrntyQueries();
|
app.MapSvrntyQueries();
|
||||||
app.MapSvrntyDynamicQueries();
|
app.MapSvrntyDynamicQueries();
|
||||||
|
|||||||
@ -21,6 +21,13 @@ service QueryService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DynamicQuery service for CQRS operations
|
||||||
|
service DynamicQueryService {
|
||||||
|
// Dynamic query for User
|
||||||
|
rpc QueryUsers (DynamicQueryUsersRequest) returns (DynamicQueryUsersResponse);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Request message for AddUserCommand
|
// Request message for AddUserCommand
|
||||||
message AddUserCommandRequest {
|
message AddUserCommandRequest {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
@ -59,3 +66,46 @@ message User {
|
|||||||
string email = 3;
|
string email = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dynamic query filter with AND/OR support
|
||||||
|
message DynamicQueryFilter {
|
||||||
|
string path = 1;
|
||||||
|
int32 type = 2; // PoweredSoft.DynamicQuery.Core.FilterType
|
||||||
|
string value = 3;
|
||||||
|
repeated DynamicQueryFilter and = 4;
|
||||||
|
repeated DynamicQueryFilter or = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic query sort
|
||||||
|
message DynamicQuerySort {
|
||||||
|
string path = 1;
|
||||||
|
bool ascending = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic query group
|
||||||
|
message DynamicQueryGroup {
|
||||||
|
string path = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic query aggregate
|
||||||
|
message DynamicQueryAggregate {
|
||||||
|
string path = 1;
|
||||||
|
int32 type = 2; // PoweredSoft.DynamicQuery.Core.AggregateType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic query request for User
|
||||||
|
message DynamicQueryUsersRequest {
|
||||||
|
int32 page = 1;
|
||||||
|
int32 page_size = 2;
|
||||||
|
repeated DynamicQueryFilter filters = 3;
|
||||||
|
repeated DynamicQuerySort sorts = 4;
|
||||||
|
repeated DynamicQueryGroup groups = 5;
|
||||||
|
repeated DynamicQueryAggregate aggregates = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic query response for User
|
||||||
|
message DynamicQueryUsersResponse {
|
||||||
|
repeated User data = 1;
|
||||||
|
int64 total_records = 2;
|
||||||
|
int32 number_of_pages = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user