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 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"); if (commandHandlerInterface == null || queryHandlerInterface == null) { return; // Handler interfaces not found } var commandMap = new Dictionary(SymbolEqualityComparer.Default); // Command -> Result type (null if no result) var queryMap = new Dictionary(SymbolEqualityComparer.Default); // Query -> Result type // 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 or ICommandHandler foreach (var iface in typeSymbol.AllInterfaces) { if (iface.IsGenericType) { // Check for ICommandHandler 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 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 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) queryMap[queryType] = resultType; } } } } var commands = new List(); var queries = new List(); // 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); } // Generate services if we found any commands or queries if (commands.Any() || queries.Any()) { GenerateProtoAndServices(context, commands, queries, 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() }; // Set result type if provided if (resultType != null) { commandInfo.ResultType = resultType.Name; commandInfo.ResultFullyQualifiedName = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); commandInfo.HandlerInterfaceName = $"ICommandHandler<{commandType.Name}, {resultType.Name}>"; } else { commandInfo.HandlerInterfaceName = $"ICommandHandler<{commandType.Name}>"; } // Extract properties var properties = commandType.GetMembers().OfType() .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() }; // Set result type queryInfo.ResultType = resultType.Name; queryInfo.ResultFullyQualifiedName = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); queryInfo.HandlerInterfaceName = $"IQueryHandler<{queryType.Name}, {resultType.Name}>"; // Extract properties var properties = queryType.GetMembers().OfType() .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 void GenerateProtoAndServices(SourceProductionContext context, List commands, List queries, 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 registration extensions var registrationExtensions = GenerateRegistrationExtensions(commands.Any(), queries.Any(), rootNamespace); context.AddSource("GrpcServiceRegistration.g.cs", registrationExtensions); } private static string GenerateCommandMessages(List commands, string rootNamespace) { var sb = new StringBuilder(); sb.AppendLine("// "); 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 queries, string rootNamespace) { var sb = new StringBuilder(); sb.AppendLine("// "); 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 commands, string rootNamespace) { var sb = new StringBuilder(); sb.AppendLine("// "); 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 queries, string rootNamespace) { var sb = new StringBuilder(); sb.AppendLine("// "); 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, string rootNamespace) { var sb = new StringBuilder(); sb.AppendLine("// "); 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(" /// "); sb.AppendLine(" /// Auto-generated extension methods for registering and mapping gRPC services"); sb.AppendLine(" /// "); sb.AppendLine(" public static class GrpcServiceRegistrationExtensions"); sb.AppendLine(" {"); if (hasCommands) { sb.AppendLine(" /// "); sb.AppendLine(" /// Registers the auto-generated Command gRPC service"); sb.AppendLine(" /// "); sb.AppendLine(" public static IServiceCollection AddGrpcCommandService(this IServiceCollection services)"); sb.AppendLine(" {"); sb.AppendLine(" services.AddGrpc();"); sb.AppendLine(" services.AddSingleton();"); sb.AppendLine(" return services;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// Maps the auto-generated Command gRPC service endpoints"); sb.AppendLine(" /// "); sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcCommands(this IEndpointRouteBuilder endpoints)"); sb.AppendLine(" {"); sb.AppendLine(" endpoints.MapGrpcService();"); sb.AppendLine(" return endpoints;"); sb.AppendLine(" }"); sb.AppendLine(); } if (hasQueries) { sb.AppendLine(" /// "); sb.AppendLine(" /// Registers the auto-generated Query gRPC service"); sb.AppendLine(" /// "); sb.AppendLine(" public static IServiceCollection AddGrpcQueryService(this IServiceCollection services)"); sb.AppendLine(" {"); sb.AppendLine(" services.AddGrpc();"); sb.AppendLine(" services.AddSingleton();"); sb.AppendLine(" return services;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// Maps the auto-generated Query gRPC service endpoints"); sb.AppendLine(" /// "); sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcQueries(this IEndpointRouteBuilder endpoints)"); sb.AppendLine(" {"); sb.AppendLine(" endpoints.MapGrpcService();"); sb.AppendLine(" return endpoints;"); sb.AppendLine(" }"); sb.AppendLine(); } if (hasCommands && hasQueries) { sb.AppendLine(" /// "); sb.AppendLine(" /// Registers both Command and Query gRPC services"); sb.AppendLine(" /// "); 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();"); if (hasQueries) sb.AppendLine(" services.AddSingleton();"); sb.AppendLine(" return services;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// Maps both Command and Query gRPC service endpoints"); sb.AppendLine(" /// "); sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcCommandsAndQueries(this IEndpointRouteBuilder endpoints)"); sb.AppendLine(" {"); if (hasCommands) sb.AppendLine(" endpoints.MapGrpcService();"); if (hasQueries) sb.AppendLine(" endpoints.MapGrpcService();"); 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 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 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 commands, string rootNamespace) { var sb = new StringBuilder(); sb.AppendLine("// "); 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 {rootNamespace}.Grpc;"); sb.AppendLine("using Svrnty.CQRS.Abstractions;"); sb.AppendLine(); sb.AppendLine($"namespace {rootNamespace}.Grpc.Services"); sb.AppendLine("{"); sb.AppendLine(" /// "); sb.AppendLine(" /// Auto-generated gRPC service implementation for Commands"); sb.AppendLine(" /// "); 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>();"); 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(" var errors = string.Join(\", \", validationResult.Errors.Select(e => e.ErrorMessage));"); sb.AppendLine(" throw new RpcException(new Status(StatusCode.InvalidArgument, $\"Validation failed: {errors}\"));"); 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 queries, string rootNamespace) { var sb = new StringBuilder(); sb.AppendLine("// "); 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(" /// "); sb.AppendLine(" /// Auto-generated gRPC service implementation for Queries"); sb.AppendLine(" /// "); 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);"); sb.AppendLine($" return new {responseType} {{ Result = result }};"); sb.AppendLine(" }"); sb.AppendLine(); } sb.AppendLine(" }"); sb.AppendLine("}"); return sb.ToString(); } } }