1485 lines
76 KiB
C#
1485 lines
76 KiB
C#
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Svrnty.CQRS.Grpc.Generators.Helpers;
|
|
using Svrnty.CQRS.Grpc.Generators.Models;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace Svrnty.CQRS.Grpc.Generators
|
|
{
|
|
[Generator]
|
|
public class GrpcGenerator : IIncrementalGenerator
|
|
{
|
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
{
|
|
// Find all types that might be commands or queries
|
|
var typeDeclarations = context.SyntaxProvider
|
|
.CreateSyntaxProvider(
|
|
predicate: static (node, _) => node is TypeDeclarationSyntax,
|
|
transform: static (ctx, _) => GetTypeSymbol(ctx))
|
|
.Where(static symbol => symbol is not null);
|
|
|
|
// Combine with compilation
|
|
var compilationAndTypes = context.CompilationProvider.Combine(typeDeclarations.Collect());
|
|
|
|
// Register source output
|
|
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => Execute(source.Left, source.Right!, spc));
|
|
}
|
|
|
|
private static INamedTypeSymbol? GetTypeSymbol(GeneratorSyntaxContext context)
|
|
{
|
|
var typeDeclaration = (TypeDeclarationSyntax)context.Node;
|
|
var symbol = context.SemanticModel.GetDeclaredSymbol(typeDeclaration);
|
|
return symbol as INamedTypeSymbol;
|
|
}
|
|
|
|
private static void Execute(Compilation compilation, IEnumerable<INamedTypeSymbol?> types, SourceProductionContext context)
|
|
{
|
|
var grpcIgnoreAttribute = compilation.GetTypeByMetadataName("Svrnty.CQRS.Grpc.Abstractions.Attributes.GrpcIgnoreAttribute");
|
|
var commandHandlerInterface = compilation.GetTypeByMetadataName("Svrnty.CQRS.Abstractions.ICommandHandler`1");
|
|
var commandHandlerWithResultInterface = compilation.GetTypeByMetadataName("Svrnty.CQRS.Abstractions.ICommandHandler`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)
|
|
{
|
|
return; // Handler interfaces not found
|
|
}
|
|
|
|
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 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
|
|
foreach (var typeSymbol in types)
|
|
{
|
|
if (typeSymbol == null || typeSymbol.IsAbstract || typeSymbol.IsStatic)
|
|
continue;
|
|
|
|
// Check if this type implements ICommandHandler<T> or ICommandHandler<T, TResult>
|
|
foreach (var iface in typeSymbol.AllInterfaces)
|
|
{
|
|
if (iface.IsGenericType)
|
|
{
|
|
// Check for ICommandHandler<TCommand>
|
|
if (SymbolEqualityComparer.Default.Equals(iface.OriginalDefinition, commandHandlerInterface) && iface.TypeArguments.Length == 1)
|
|
{
|
|
var commandType = iface.TypeArguments[0] as INamedTypeSymbol;
|
|
if (commandType != null && !commandMap.ContainsKey(commandType))
|
|
commandMap[commandType] = null; // No result type
|
|
}
|
|
// Check for ICommandHandler<TCommand, TResult>
|
|
else if (SymbolEqualityComparer.Default.Equals(iface.OriginalDefinition, commandHandlerWithResultInterface) && iface.TypeArguments.Length == 2)
|
|
{
|
|
var commandType = iface.TypeArguments[0] as INamedTypeSymbol;
|
|
var resultType = iface.TypeArguments[1] as INamedTypeSymbol;
|
|
if (commandType != null && resultType != null)
|
|
commandMap[commandType] = resultType;
|
|
}
|
|
// Check for IQueryHandler<TQuery, TResult>
|
|
else if (SymbolEqualityComparer.Default.Equals(iface.OriginalDefinition, queryHandlerInterface) && iface.TypeArguments.Length == 2)
|
|
{
|
|
var queryType = iface.TypeArguments[0] as INamedTypeSymbol;
|
|
var resultType = iface.TypeArguments[1] as INamedTypeSymbol;
|
|
if (queryType != null && resultType != null)
|
|
{
|
|
// 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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var commands = new List<CommandInfo>();
|
|
var queries = new List<QueryInfo>();
|
|
|
|
// Process discovered command types
|
|
foreach (var kvp in commandMap)
|
|
{
|
|
var commandType = kvp.Key;
|
|
var resultType = kvp.Value;
|
|
|
|
// Skip if marked with [GrpcIgnore]
|
|
if (grpcIgnoreAttribute != null && HasAttribute(commandType, grpcIgnoreAttribute))
|
|
continue;
|
|
|
|
var commandInfo = ExtractCommandInfo(commandType, resultType);
|
|
if (commandInfo != null)
|
|
commands.Add(commandInfo);
|
|
}
|
|
|
|
// Process discovered query types
|
|
foreach (var kvp in queryMap)
|
|
{
|
|
var queryType = kvp.Key;
|
|
var resultType = kvp.Value;
|
|
|
|
// Skip if marked with [GrpcIgnore]
|
|
if (grpcIgnoreAttribute != null && HasAttribute(queryType, grpcIgnoreAttribute))
|
|
continue;
|
|
|
|
var queryInfo = ExtractQueryInfo(queryType, resultType);
|
|
if (queryInfo != null)
|
|
queries.Add(queryInfo);
|
|
}
|
|
|
|
// Process discovered dynamic query types
|
|
var dynamicQueries = new List<DynamicQueryInfo>();
|
|
foreach (var (sourceType, destinationType, paramsType) in dynamicQueryMap)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
private static bool HasAttribute(INamedTypeSymbol typeSymbol, INamedTypeSymbol attributeSymbol)
|
|
{
|
|
return typeSymbol.GetAttributes().Any(attr =>
|
|
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, attributeSymbol));
|
|
}
|
|
|
|
private static bool ImplementsInterface(INamedTypeSymbol typeSymbol, INamedTypeSymbol? interfaceSymbol)
|
|
{
|
|
if (interfaceSymbol == null)
|
|
return false;
|
|
|
|
return typeSymbol.AllInterfaces.Any(i =>
|
|
SymbolEqualityComparer.Default.Equals(i, interfaceSymbol));
|
|
}
|
|
|
|
private static bool ImplementsGenericInterface(INamedTypeSymbol typeSymbol, INamedTypeSymbol? genericInterfaceSymbol)
|
|
{
|
|
if (genericInterfaceSymbol == null)
|
|
return false;
|
|
|
|
return typeSymbol.AllInterfaces.Any(i =>
|
|
i.IsGenericType && SymbolEqualityComparer.Default.Equals(i.OriginalDefinition, genericInterfaceSymbol));
|
|
}
|
|
|
|
private static CommandInfo? ExtractCommandInfo(INamedTypeSymbol commandType, INamedTypeSymbol? resultType)
|
|
{
|
|
var commandInfo = new CommandInfo
|
|
{
|
|
Name = commandType.Name,
|
|
FullyQualifiedName = commandType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
|
Namespace = commandType.ContainingNamespace.ToDisplayString(),
|
|
Properties = new List<PropertyInfo>()
|
|
};
|
|
|
|
// Set result type if provided
|
|
if (resultType != null)
|
|
{
|
|
commandInfo.ResultType = resultType.Name;
|
|
commandInfo.ResultFullyQualifiedName = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
// Use fully qualified names to avoid ambiguity with proto-generated types
|
|
var commandTypeFullyQualified = commandType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
var resultTypeFullyQualified = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
commandInfo.HandlerInterfaceName = $"ICommandHandler<{commandTypeFullyQualified}, {resultTypeFullyQualified}>";
|
|
}
|
|
else
|
|
{
|
|
var commandTypeFullyQualified = commandType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
commandInfo.HandlerInterfaceName = $"ICommandHandler<{commandTypeFullyQualified}>";
|
|
}
|
|
|
|
// Extract properties
|
|
var properties = commandType.GetMembers().OfType<IPropertySymbol>()
|
|
.Where(p => p.DeclaredAccessibility == Accessibility.Public && !p.IsStatic)
|
|
.ToList();
|
|
|
|
int fieldNumber = 1;
|
|
foreach (var property in properties)
|
|
{
|
|
var propertyType = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
var protoType = ProtoTypeMapper.MapToProtoType(propertyType, out bool isRepeated, out bool isOptional);
|
|
|
|
commandInfo.Properties.Add(new PropertyInfo
|
|
{
|
|
Name = property.Name,
|
|
Type = propertyType,
|
|
ProtoType = protoType,
|
|
FieldNumber = fieldNumber++
|
|
});
|
|
}
|
|
|
|
return commandInfo;
|
|
}
|
|
|
|
private static QueryInfo? ExtractQueryInfo(INamedTypeSymbol queryType, INamedTypeSymbol resultType)
|
|
{
|
|
var queryInfo = new QueryInfo
|
|
{
|
|
Name = queryType.Name,
|
|
FullyQualifiedName = queryType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
|
Namespace = queryType.ContainingNamespace.ToDisplayString(),
|
|
Properties = new List<PropertyInfo>()
|
|
};
|
|
|
|
// Set result type
|
|
queryInfo.ResultType = resultType.Name;
|
|
queryInfo.ResultFullyQualifiedName = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
// Use fully qualified names to avoid ambiguity with proto-generated types
|
|
var queryTypeFullyQualified = queryType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
var resultTypeFullyQualified = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
queryInfo.HandlerInterfaceName = $"IQueryHandler<{queryTypeFullyQualified}, {resultTypeFullyQualified}>";
|
|
|
|
// Check if result type is primitive
|
|
var resultTypeString = resultType.ToDisplayString();
|
|
queryInfo.IsResultPrimitiveType = IsPrimitiveType(resultTypeString);
|
|
|
|
// Extract result type properties if it's a complex type
|
|
if (!queryInfo.IsResultPrimitiveType)
|
|
{
|
|
var resultProperties = resultType.GetMembers().OfType<IPropertySymbol>()
|
|
.Where(p => p.DeclaredAccessibility == Accessibility.Public && !p.IsStatic)
|
|
.ToList();
|
|
|
|
foreach (var property in resultProperties)
|
|
{
|
|
queryInfo.ResultProperties.Add(new PropertyInfo
|
|
{
|
|
Name = property.Name,
|
|
Type = property.Type.ToDisplayString(),
|
|
FullyQualifiedType = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
|
ProtoType = string.Empty, // Not needed for result mapping
|
|
FieldNumber = 0 // Not needed for result mapping
|
|
});
|
|
}
|
|
}
|
|
|
|
// Extract properties
|
|
var properties = queryType.GetMembers().OfType<IPropertySymbol>()
|
|
.Where(p => p.DeclaredAccessibility == Accessibility.Public && !p.IsStatic)
|
|
.ToList();
|
|
|
|
int fieldNumber = 1;
|
|
foreach (var property in properties)
|
|
{
|
|
var propertyType = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
var protoType = ProtoTypeMapper.MapToProtoType(propertyType, out bool isRepeated, out bool isOptional);
|
|
|
|
queryInfo.Properties.Add(new PropertyInfo
|
|
{
|
|
Name = property.Name,
|
|
Type = propertyType,
|
|
ProtoType = protoType,
|
|
FieldNumber = fieldNumber++
|
|
});
|
|
}
|
|
|
|
return queryInfo;
|
|
}
|
|
|
|
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
|
|
var rootNamespace = compilation.AssemblyName ?? "Application";
|
|
|
|
// Generate service implementations for commands
|
|
if (commands.Any())
|
|
{
|
|
var commandService = GenerateCommandServiceImpl(commands, rootNamespace);
|
|
context.AddSource("CommandServiceImpl.g.cs", commandService);
|
|
}
|
|
|
|
// Generate service implementations for queries
|
|
if (queries.Any())
|
|
{
|
|
var queryService = GenerateQueryServiceImpl(queries, rootNamespace);
|
|
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
|
|
var registrationExtensions = GenerateRegistrationExtensions(commands.Any(), queries.Any(), dynamicQueries.Any(), rootNamespace);
|
|
context.AddSource("GrpcServiceRegistration.g.cs", registrationExtensions);
|
|
}
|
|
|
|
private static string GenerateCommandMessages(List<CommandInfo> commands, string rootNamespace)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("// <auto-generated />");
|
|
sb.AppendLine("#nullable enable");
|
|
sb.AppendLine("using System.Runtime.Serialization;");
|
|
sb.AppendLine("using ProtoBuf;");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"namespace {rootNamespace}.Grpc.Messages");
|
|
sb.AppendLine("{");
|
|
|
|
foreach (var command in commands)
|
|
{
|
|
// Generate command DTO
|
|
sb.AppendLine(" [ProtoContract]");
|
|
sb.AppendLine(" [DataContract]");
|
|
sb.AppendLine($" public sealed class {command.Name}Dto");
|
|
sb.AppendLine(" {");
|
|
|
|
foreach (var prop in command.Properties)
|
|
{
|
|
sb.AppendLine($" [ProtoMember({prop.FieldNumber})]");
|
|
sb.AppendLine(" [DataMember(Order = " + prop.FieldNumber + ")]");
|
|
sb.AppendLine($" public {prop.Type} {prop.Name} {{ get; set; }}");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
|
|
// Generate result DTO if command has a result
|
|
if (command.HasResult)
|
|
{
|
|
sb.AppendLine(" [ProtoContract]");
|
|
sb.AppendLine(" [DataContract]");
|
|
sb.AppendLine($" public sealed class {command.Name}ResultDto");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" [ProtoMember(1)]");
|
|
sb.AppendLine(" [DataMember(Order = 1)]");
|
|
sb.AppendLine($" public {command.ResultFullyQualifiedName} Result {{ get; set; }}");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
}
|
|
}
|
|
|
|
sb.AppendLine("}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string GenerateQueryMessages(List<QueryInfo> queries, string rootNamespace)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("// <auto-generated />");
|
|
sb.AppendLine("#nullable enable");
|
|
sb.AppendLine("using System.Runtime.Serialization;");
|
|
sb.AppendLine("using ProtoBuf;");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"namespace {rootNamespace}.Grpc.Messages");
|
|
sb.AppendLine("{");
|
|
|
|
foreach (var query in queries)
|
|
{
|
|
// Generate query DTO
|
|
sb.AppendLine(" [ProtoContract]");
|
|
sb.AppendLine(" [DataContract]");
|
|
sb.AppendLine($" public sealed class {query.Name}Dto");
|
|
sb.AppendLine(" {");
|
|
|
|
foreach (var prop in query.Properties)
|
|
{
|
|
sb.AppendLine($" [ProtoMember({prop.FieldNumber})]");
|
|
sb.AppendLine(" [DataMember(Order = " + prop.FieldNumber + ")]");
|
|
sb.AppendLine($" public {prop.Type} {prop.Name} {{ get; set; }}");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
|
|
// Generate result DTO
|
|
sb.AppendLine(" [ProtoContract]");
|
|
sb.AppendLine(" [DataContract]");
|
|
sb.AppendLine($" public sealed class {query.Name}ResultDto");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" [ProtoMember(1)]");
|
|
sb.AppendLine(" [DataMember(Order = 1)]");
|
|
sb.AppendLine($" public {query.ResultFullyQualifiedName} Result {{ get; set; }}");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
sb.AppendLine("}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string GenerateCommandService(List<CommandInfo> commands, string rootNamespace)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("// <auto-generated />");
|
|
sb.AppendLine("#nullable enable");
|
|
sb.AppendLine("using System.ServiceModel;");
|
|
sb.AppendLine("using System.Threading;");
|
|
sb.AppendLine("using System.Threading.Tasks;");
|
|
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
|
|
sb.AppendLine($"using {rootNamespace}.Grpc.Messages;");
|
|
sb.AppendLine("using Svrnty.CQRS.Abstractions;");
|
|
sb.AppendLine("using ProtoBuf.Grpc;");
|
|
sb.AppendLine();
|
|
|
|
// Generate service interface
|
|
sb.AppendLine($"namespace {rootNamespace}.Grpc.Services");
|
|
sb.AppendLine("{");
|
|
sb.AppendLine(" [ServiceContract]");
|
|
sb.AppendLine(" public interface ICommandService");
|
|
sb.AppendLine(" {");
|
|
|
|
foreach (var command in commands)
|
|
{
|
|
if (command.HasResult)
|
|
{
|
|
sb.AppendLine($" [OperationContract]");
|
|
sb.AppendLine($" Task<{command.Name}ResultDto> Execute{command.Name}Async({command.Name}Dto request, CallContext context = default);");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($" [OperationContract]");
|
|
sb.AppendLine($" Task Execute{command.Name}Async({command.Name}Dto request, CallContext context = default);");
|
|
}
|
|
sb.AppendLine();
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
|
|
// Generate service implementation
|
|
sb.AppendLine(" public sealed class CommandService : ICommandService");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" private readonly IServiceProvider _serviceProvider;");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" public CommandService(IServiceProvider serviceProvider)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" _serviceProvider = serviceProvider;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
|
|
foreach (var command in commands)
|
|
{
|
|
if (command.HasResult)
|
|
{
|
|
sb.AppendLine($" public async Task<{command.Name}ResultDto> Execute{command.Name}Async({command.Name}Dto request, CallContext context = default)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine($" var handler = _serviceProvider.GetRequiredService<{command.HandlerInterfaceName}>();");
|
|
sb.AppendLine($" var command = new {command.FullyQualifiedName}");
|
|
sb.AppendLine(" {");
|
|
foreach (var prop in command.Properties)
|
|
{
|
|
sb.AppendLine($" {prop.Name} = request.{prop.Name}!,");
|
|
}
|
|
sb.AppendLine(" };");
|
|
sb.AppendLine(" var result = await handler.HandleAsync(command, context.CancellationToken);");
|
|
sb.AppendLine($" return new {command.Name}ResultDto {{ Result = result }};");
|
|
sb.AppendLine(" }");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($" public async Task Execute{command.Name}Async({command.Name}Dto request, CallContext context = default)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine($" var handler = _serviceProvider.GetRequiredService<{command.HandlerInterfaceName}>();");
|
|
sb.AppendLine($" var command = new {command.FullyQualifiedName}");
|
|
sb.AppendLine(" {");
|
|
foreach (var prop in command.Properties)
|
|
{
|
|
sb.AppendLine($" {prop.Name} = request.{prop.Name}!,");
|
|
}
|
|
sb.AppendLine(" };");
|
|
sb.AppendLine(" await handler.HandleAsync(command, context.CancellationToken);");
|
|
sb.AppendLine(" }");
|
|
}
|
|
sb.AppendLine();
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine("}");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string GenerateQueryService(List<QueryInfo> queries, string rootNamespace)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("// <auto-generated />");
|
|
sb.AppendLine("#nullable enable");
|
|
sb.AppendLine("using System.ServiceModel;");
|
|
sb.AppendLine("using System.Threading;");
|
|
sb.AppendLine("using System.Threading.Tasks;");
|
|
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
|
|
sb.AppendLine($"using {rootNamespace}.Grpc.Messages;");
|
|
sb.AppendLine("using Svrnty.CQRS.Abstractions;");
|
|
sb.AppendLine("using ProtoBuf.Grpc;");
|
|
sb.AppendLine();
|
|
|
|
// Generate service interface
|
|
sb.AppendLine($"namespace {rootNamespace}.Grpc.Services");
|
|
sb.AppendLine("{");
|
|
sb.AppendLine(" [ServiceContract]");
|
|
sb.AppendLine(" public interface IQueryService");
|
|
sb.AppendLine(" {");
|
|
|
|
foreach (var query in queries)
|
|
{
|
|
sb.AppendLine($" [OperationContract]");
|
|
sb.AppendLine($" Task<{query.Name}ResultDto> Execute{query.Name}Async({query.Name}Dto request, CallContext context = default);");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
|
|
// Generate service implementation
|
|
sb.AppendLine(" public sealed class QueryService : IQueryService");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" private readonly IServiceProvider _serviceProvider;");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" public QueryService(IServiceProvider serviceProvider)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" _serviceProvider = serviceProvider;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
|
|
foreach (var query in queries)
|
|
{
|
|
sb.AppendLine($" public async Task<{query.Name}ResultDto> Execute{query.Name}Async({query.Name}Dto request, CallContext context = default)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine($" var handler = _serviceProvider.GetRequiredService<{query.HandlerInterfaceName}>();");
|
|
sb.AppendLine($" var query = new {query.FullyQualifiedName}");
|
|
sb.AppendLine(" {");
|
|
foreach (var prop in query.Properties)
|
|
{
|
|
sb.AppendLine($" {prop.Name} = request.{prop.Name}!,");
|
|
}
|
|
sb.AppendLine(" };");
|
|
sb.AppendLine(" var result = await handler.HandleAsync(query, context.CancellationToken);");
|
|
sb.AppendLine($" return new {query.Name}ResultDto {{ Result = result }};");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine("}");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string GenerateRegistrationExtensions(bool hasCommands, bool hasQueries, bool hasDynamicQueries, string rootNamespace)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("// <auto-generated />");
|
|
sb.AppendLine("using Microsoft.AspNetCore.Builder;");
|
|
sb.AppendLine("using Microsoft.AspNetCore.Routing;");
|
|
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
|
|
sb.AppendLine($"using {rootNamespace}.Grpc.Services;");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"namespace {rootNamespace}.Grpc.Extensions");
|
|
sb.AppendLine("{");
|
|
sb.AppendLine(" /// <summary>");
|
|
sb.AppendLine(" /// Auto-generated extension methods for registering and mapping gRPC services");
|
|
sb.AppendLine(" /// </summary>");
|
|
sb.AppendLine(" public static class GrpcServiceRegistrationExtensions");
|
|
sb.AppendLine(" {");
|
|
|
|
if (hasCommands)
|
|
{
|
|
sb.AppendLine(" /// <summary>");
|
|
sb.AppendLine(" /// Registers the auto-generated Command gRPC service");
|
|
sb.AppendLine(" /// </summary>");
|
|
sb.AppendLine(" public static IServiceCollection AddGrpcCommandService(this IServiceCollection services)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" services.AddGrpc();");
|
|
sb.AppendLine(" services.AddSingleton<CommandServiceImpl>();");
|
|
sb.AppendLine(" return services;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" /// <summary>");
|
|
sb.AppendLine(" /// Maps the auto-generated Command gRPC service endpoints");
|
|
sb.AppendLine(" /// </summary>");
|
|
sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcCommands(this IEndpointRouteBuilder endpoints)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" endpoints.MapGrpcService<CommandServiceImpl>();");
|
|
sb.AppendLine(" return endpoints;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
if (hasQueries)
|
|
{
|
|
sb.AppendLine(" /// <summary>");
|
|
sb.AppendLine(" /// Registers the auto-generated Query gRPC service");
|
|
sb.AppendLine(" /// </summary>");
|
|
sb.AppendLine(" public static IServiceCollection AddGrpcQueryService(this IServiceCollection services)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" services.AddGrpc();");
|
|
sb.AppendLine(" services.AddSingleton<QueryServiceImpl>();");
|
|
sb.AppendLine(" return services;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" /// <summary>");
|
|
sb.AppendLine(" /// Maps the auto-generated Query gRPC service endpoints");
|
|
sb.AppendLine(" /// </summary>");
|
|
sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcQueries(this IEndpointRouteBuilder endpoints)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" endpoints.MapGrpcService<QueryServiceImpl>();");
|
|
sb.AppendLine(" return endpoints;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
if (hasDynamicQueries)
|
|
{
|
|
sb.AppendLine(" /// <summary>");
|
|
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(" public static IServiceCollection AddGrpcCommandsAndQueries(this IServiceCollection services)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" services.AddGrpc();");
|
|
sb.AppendLine(" services.AddGrpcReflection();");
|
|
if (hasCommands)
|
|
sb.AppendLine(" services.AddSingleton<CommandServiceImpl>();");
|
|
if (hasQueries)
|
|
sb.AppendLine(" services.AddSingleton<QueryServiceImpl>();");
|
|
if (hasDynamicQueries)
|
|
sb.AppendLine(" services.AddSingleton<DynamicQueryServiceImpl>();");
|
|
sb.AppendLine(" return services;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" /// <summary>");
|
|
sb.AppendLine(" /// Maps all auto-generated gRPC service endpoints (Commands, Queries, and DynamicQueries)");
|
|
sb.AppendLine(" /// </summary>");
|
|
sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcCommandsAndQueries(this IEndpointRouteBuilder endpoints)");
|
|
sb.AppendLine(" {");
|
|
if (hasCommands)
|
|
sb.AppendLine(" endpoints.MapGrpcService<CommandServiceImpl>();");
|
|
if (hasQueries)
|
|
sb.AppendLine(" endpoints.MapGrpcService<QueryServiceImpl>();");
|
|
if (hasDynamicQueries)
|
|
sb.AppendLine(" endpoints.MapGrpcService<DynamicQueryServiceImpl>();");
|
|
sb.AppendLine(" return endpoints;");
|
|
sb.AppendLine(" }");
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine("}");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string ToCamelCase(string str)
|
|
{
|
|
if (string.IsNullOrEmpty(str) || char.IsLower(str[0]))
|
|
return str;
|
|
|
|
return char.ToLowerInvariant(str[0]) + str.Substring(1);
|
|
}
|
|
|
|
// New methods for standard gRPC generation
|
|
|
|
private static string GenerateCommandsProto(List<CommandInfo> commands, string rootNamespace)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("syntax = \"proto3\";");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"option csharp_namespace = \"{rootNamespace}.Grpc\";");
|
|
sb.AppendLine();
|
|
sb.AppendLine("package cqrs;");
|
|
sb.AppendLine();
|
|
sb.AppendLine("// Command service for CQRS operations");
|
|
sb.AppendLine("service CommandService {");
|
|
|
|
foreach (var command in commands)
|
|
{
|
|
var methodName = command.Name.Replace("Command", "");
|
|
sb.AppendLine($" // {command.Name}");
|
|
sb.AppendLine($" rpc {methodName} ({command.Name}Request) returns ({command.Name}Response);");
|
|
}
|
|
|
|
sb.AppendLine("}");
|
|
sb.AppendLine();
|
|
|
|
// Generate message types
|
|
foreach (var command in commands)
|
|
{
|
|
// Request message
|
|
sb.AppendLine($"message {command.Name}Request {{");
|
|
foreach (var prop in command.Properties)
|
|
{
|
|
sb.AppendLine($" {prop.ProtoType} {ToCamelCase(prop.Name)} = {prop.FieldNumber};");
|
|
}
|
|
sb.AppendLine("}");
|
|
sb.AppendLine();
|
|
|
|
// Response message
|
|
sb.AppendLine($"message {command.Name}Response {{");
|
|
if (command.HasResult)
|
|
{
|
|
sb.AppendLine($" {ProtoTypeMapper.MapToProtoType(command.ResultFullyQualifiedName!, out _, out _)} result = 1;");
|
|
}
|
|
sb.AppendLine("}");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string GenerateQueriesProto(List<QueryInfo> queries, string rootNamespace)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("syntax = \"proto3\";");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"option csharp_namespace = \"{rootNamespace}.Grpc\";");
|
|
sb.AppendLine();
|
|
sb.AppendLine("package cqrs;");
|
|
sb.AppendLine();
|
|
sb.AppendLine("// Query service for CQRS operations");
|
|
sb.AppendLine("service QueryService {");
|
|
|
|
foreach (var query in queries)
|
|
{
|
|
var methodName = query.Name.Replace("Query", "");
|
|
sb.AppendLine($" // {query.Name}");
|
|
sb.AppendLine($" rpc {methodName} ({query.Name}Request) returns ({query.Name}Response);");
|
|
}
|
|
|
|
sb.AppendLine("}");
|
|
sb.AppendLine();
|
|
|
|
// Generate message types
|
|
foreach (var query in queries)
|
|
{
|
|
// Request message
|
|
sb.AppendLine($"message {query.Name}Request {{");
|
|
foreach (var prop in query.Properties)
|
|
{
|
|
sb.AppendLine($" {prop.ProtoType} {ToCamelCase(prop.Name)} = {prop.FieldNumber};");
|
|
}
|
|
sb.AppendLine("}");
|
|
sb.AppendLine();
|
|
|
|
// Response message
|
|
sb.AppendLine($"message {query.Name}Response {{");
|
|
sb.AppendLine($" {ProtoTypeMapper.MapToProtoType(query.ResultFullyQualifiedName, out _, out _)} result = 1;");
|
|
sb.AppendLine("}");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string GenerateCommandServiceImpl(List<CommandInfo> commands, 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.Linq;");
|
|
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
|
|
sb.AppendLine("using FluentValidation;");
|
|
sb.AppendLine("using Google.Rpc;");
|
|
sb.AppendLine("using Google.Protobuf.WellKnownTypes;");
|
|
sb.AppendLine($"using {rootNamespace}.Grpc;");
|
|
sb.AppendLine("using Svrnty.CQRS.Abstractions;");
|
|
sb.AppendLine();
|
|
|
|
sb.AppendLine($"namespace {rootNamespace}.Grpc.Services");
|
|
sb.AppendLine("{");
|
|
sb.AppendLine(" /// <summary>");
|
|
sb.AppendLine(" /// Auto-generated gRPC service implementation for Commands");
|
|
sb.AppendLine(" /// </summary>");
|
|
sb.AppendLine(" public sealed class CommandServiceImpl : CommandService.CommandServiceBase");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" private readonly IServiceProvider _serviceProvider;");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" public CommandServiceImpl(IServiceProvider serviceProvider)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" _serviceProvider = serviceProvider;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
|
|
foreach (var command in commands)
|
|
{
|
|
var methodName = command.Name.Replace("Command", "");
|
|
var requestType = $"{command.Name}Request";
|
|
var responseType = $"{command.Name}Response";
|
|
|
|
sb.AppendLine($" public override async Task<{responseType}> {methodName}(");
|
|
sb.AppendLine($" {requestType} request,");
|
|
sb.AppendLine(" ServerCallContext context)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine($" var command = new {command.FullyQualifiedName}");
|
|
sb.AppendLine(" {");
|
|
foreach (var prop in command.Properties)
|
|
{
|
|
sb.AppendLine($" {prop.Name} = request.{char.ToUpper(prop.Name[0]) + prop.Name.Substring(1)},");
|
|
}
|
|
sb.AppendLine(" };");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" // Validate command if validator is registered");
|
|
sb.AppendLine($" var validator = _serviceProvider.GetService<IValidator<{command.FullyQualifiedName}>>();");
|
|
sb.AppendLine(" if (validator != null)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" var validationResult = await validator.ValidateAsync(command, context.CancellationToken);");
|
|
sb.AppendLine(" if (!validationResult.IsValid)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" // Create Rich Error Model with structured field violations");
|
|
sb.AppendLine(" var badRequest = new BadRequest();");
|
|
sb.AppendLine(" foreach (var error in validationResult.Errors)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" badRequest.FieldViolations.Add(new BadRequest.Types.FieldViolation");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" Field = error.PropertyName,");
|
|
sb.AppendLine(" Description = error.ErrorMessage");
|
|
sb.AppendLine(" });");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" var status = new Google.Rpc.Status");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" Code = (int)Code.InvalidArgument,");
|
|
sb.AppendLine(" Message = \"Validation failed\",");
|
|
sb.AppendLine(" Details = { Any.Pack(badRequest) }");
|
|
sb.AppendLine(" };");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" throw status.ToRpcException();");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
sb.AppendLine($" var handler = _serviceProvider.GetRequiredService<{command.HandlerInterfaceName}>();");
|
|
|
|
if (command.HasResult)
|
|
{
|
|
sb.AppendLine(" var result = await handler.HandleAsync(command, context.CancellationToken);");
|
|
sb.AppendLine($" return new {responseType} {{ Result = result }};");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine(" await handler.HandleAsync(command, context.CancellationToken);");
|
|
sb.AppendLine($" return new {responseType}();");
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine("}");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string GenerateQueryServiceImpl(List<QueryInfo> queries, 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 Microsoft.Extensions.DependencyInjection;");
|
|
sb.AppendLine($"using {rootNamespace}.Grpc;");
|
|
sb.AppendLine("using Svrnty.CQRS.Abstractions;");
|
|
sb.AppendLine();
|
|
|
|
sb.AppendLine($"namespace {rootNamespace}.Grpc.Services");
|
|
sb.AppendLine("{");
|
|
sb.AppendLine(" /// <summary>");
|
|
sb.AppendLine(" /// Auto-generated gRPC service implementation for Queries");
|
|
sb.AppendLine(" /// </summary>");
|
|
sb.AppendLine(" public sealed class QueryServiceImpl : QueryService.QueryServiceBase");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" private readonly IServiceProvider _serviceProvider;");
|
|
sb.AppendLine();
|
|
sb.AppendLine(" public QueryServiceImpl(IServiceProvider serviceProvider)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine(" _serviceProvider = serviceProvider;");
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
|
|
foreach (var query in queries)
|
|
{
|
|
var methodName = query.Name.Replace("Query", "");
|
|
var requestType = $"{query.Name}Request";
|
|
var responseType = $"{query.Name}Response";
|
|
|
|
sb.AppendLine($" public override async Task<{responseType}> {methodName}(");
|
|
sb.AppendLine($" {requestType} request,");
|
|
sb.AppendLine(" ServerCallContext context)");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine($" var handler = _serviceProvider.GetRequiredService<{query.HandlerInterfaceName}>();");
|
|
sb.AppendLine($" var query = new {query.FullyQualifiedName}");
|
|
sb.AppendLine(" {");
|
|
foreach (var prop in query.Properties)
|
|
{
|
|
sb.AppendLine($" {prop.Name} = request.{char.ToUpper(prop.Name[0]) + prop.Name.Substring(1)},");
|
|
}
|
|
sb.AppendLine(" };");
|
|
sb.AppendLine(" var result = await handler.HandleAsync(query, context.CancellationToken);");
|
|
|
|
// Generate response with mapping if complex type
|
|
if (query.IsResultPrimitiveType)
|
|
{
|
|
sb.AppendLine($" return new {responseType} {{ Result = result }};");
|
|
}
|
|
else
|
|
{
|
|
// Complex type - need to map from C# type to proto type
|
|
sb.AppendLine($" return new {responseType}");
|
|
sb.AppendLine(" {");
|
|
sb.AppendLine($" Result = new {query.ResultType}");
|
|
sb.AppendLine(" {");
|
|
foreach (var prop in query.ResultProperties)
|
|
{
|
|
sb.AppendLine($" {prop.Name} = result.{prop.Name},");
|
|
}
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine(" };");
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
sb.AppendLine(" }");
|
|
sb.AppendLine("}");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static bool IsPrimitiveType(string typeName)
|
|
{
|
|
// Check for common primitive and built-in types
|
|
var primitiveTypes = new[]
|
|
{
|
|
"int", "System.Int32",
|
|
"long", "System.Int64",
|
|
"short", "System.Int16",
|
|
"byte", "System.Byte",
|
|
"bool", "System.Boolean",
|
|
"float", "System.Single",
|
|
"double", "System.Double",
|
|
"decimal", "System.Decimal",
|
|
"string", "System.String",
|
|
"System.DateTime",
|
|
"System.DateTimeOffset",
|
|
"System.TimeSpan",
|
|
"System.Guid"
|
|
};
|
|
|
|
return primitiveTypes.Contains(typeName) ||
|
|
typeName.StartsWith("System.Nullable<") ||
|
|
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();
|
|
}
|
|
}
|
|
}
|