From a0426aa0d15a28c5c541c7fc842ea7d9d7d531f3 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Mon, 3 Nov 2025 07:44:17 -0500 Subject: [PATCH] yessir --- Svrnty.CQRS.Grpc.Generators/GrpcGenerator.cs | 84 ++++++++++++++++++- .../Models/CommandInfo.cs | 6 ++ .../Models/QueryInfo.cs | 4 + Svrnty.CQRS.sln | 2 +- .../AddUserCommand.cs | 2 +- .../FetchUserQuery.cs | 2 +- .../InternalCommand.cs | 2 +- .../Program.cs | 4 +- .../Protos/cqrs_services.proto | 2 +- .../RemoveUserCommand.cs | 2 +- .../Svrnty.Sample.csproj | 0 .../appsettings.Development.json | 0 .../appsettings.json | 0 13 files changed, 98 insertions(+), 12 deletions(-) rename {Svrnty.CQRS.Grpc.Sample => Svrnty.Sample}/AddUserCommand.cs (96%) rename {Svrnty.CQRS.Grpc.Sample => Svrnty.Sample}/FetchUserQuery.cs (94%) rename {Svrnty.CQRS.Grpc.Sample => Svrnty.Sample}/InternalCommand.cs (94%) rename {Svrnty.CQRS.Grpc.Sample => Svrnty.Sample}/Program.cs (96%) rename {Svrnty.CQRS.Grpc.Sample => Svrnty.Sample}/Protos/cqrs_services.proto (95%) rename {Svrnty.CQRS.Grpc.Sample => Svrnty.Sample}/RemoveUserCommand.cs (91%) rename Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj => Svrnty.Sample/Svrnty.Sample.csproj (100%) rename {Svrnty.CQRS.Grpc.Sample => Svrnty.Sample}/appsettings.Development.json (100%) rename {Svrnty.CQRS.Grpc.Sample => Svrnty.Sample}/appsettings.json (100%) diff --git a/Svrnty.CQRS.Grpc.Generators/GrpcGenerator.cs b/Svrnty.CQRS.Grpc.Generators/GrpcGenerator.cs index af3f70f..e147baa 100644 --- a/Svrnty.CQRS.Grpc.Generators/GrpcGenerator.cs +++ b/Svrnty.CQRS.Grpc.Generators/GrpcGenerator.cs @@ -166,11 +166,15 @@ namespace Svrnty.CQRS.Grpc.Generators { commandInfo.ResultType = resultType.Name; commandInfo.ResultFullyQualifiedName = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - commandInfo.HandlerInterfaceName = $"ICommandHandler<{commandType.Name}, {resultType.Name}>"; + // 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 { - commandInfo.HandlerInterfaceName = $"ICommandHandler<{commandType.Name}>"; + var commandTypeFullyQualified = commandType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + commandInfo.HandlerInterfaceName = $"ICommandHandler<{commandTypeFullyQualified}>"; } // Extract properties @@ -209,7 +213,34 @@ namespace Svrnty.CQRS.Grpc.Generators // Set result type queryInfo.ResultType = resultType.Name; queryInfo.ResultFullyQualifiedName = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - queryInfo.HandlerInterfaceName = $"IQueryHandler<{queryType.Name}, {resultType.Name}>"; + // 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() + .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() @@ -857,7 +888,27 @@ namespace Svrnty.CQRS.Grpc.Generators } sb.AppendLine(" };"); sb.AppendLine(" var result = await handler.HandleAsync(query, context.CancellationToken);"); - sb.AppendLine($" return new {responseType} {{ Result = result }};"); + + // 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(); } @@ -867,5 +918,30 @@ namespace Svrnty.CQRS.Grpc.Generators 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("?"); + } } } diff --git a/Svrnty.CQRS.Grpc.Generators/Models/CommandInfo.cs b/Svrnty.CQRS.Grpc.Generators/Models/CommandInfo.cs index d2219ec..d874ad6 100644 --- a/Svrnty.CQRS.Grpc.Generators/Models/CommandInfo.cs +++ b/Svrnty.CQRS.Grpc.Generators/Models/CommandInfo.cs @@ -13,6 +13,8 @@ namespace Svrnty.CQRS.Grpc.Generators.Models public string? ResultFullyQualifiedName { get; set; } public bool HasResult => ResultType != null; public string HandlerInterfaceName { get; set; } + public List ResultProperties { get; set; } + public bool IsResultPrimitiveType { get; set; } public CommandInfo() { @@ -21,6 +23,8 @@ namespace Svrnty.CQRS.Grpc.Generators.Models Namespace = string.Empty; Properties = new List(); HandlerInterfaceName = string.Empty; + ResultProperties = new List(); + IsResultPrimitiveType = false; } } @@ -28,6 +32,7 @@ namespace Svrnty.CQRS.Grpc.Generators.Models { public string Name { get; set; } public string Type { get; set; } + public string FullyQualifiedType { get; set; } public string ProtoType { get; set; } public int FieldNumber { get; set; } @@ -35,6 +40,7 @@ namespace Svrnty.CQRS.Grpc.Generators.Models { Name = string.Empty; Type = string.Empty; + FullyQualifiedType = string.Empty; ProtoType = string.Empty; } } diff --git a/Svrnty.CQRS.Grpc.Generators/Models/QueryInfo.cs b/Svrnty.CQRS.Grpc.Generators/Models/QueryInfo.cs index 267d1be..93924f4 100644 --- a/Svrnty.CQRS.Grpc.Generators/Models/QueryInfo.cs +++ b/Svrnty.CQRS.Grpc.Generators/Models/QueryInfo.cs @@ -11,6 +11,8 @@ namespace Svrnty.CQRS.Grpc.Generators.Models public string ResultType { get; set; } public string ResultFullyQualifiedName { get; set; } public string HandlerInterfaceName { get; set; } + public List ResultProperties { get; set; } + public bool IsResultPrimitiveType { get; set; } public QueryInfo() { @@ -21,6 +23,8 @@ namespace Svrnty.CQRS.Grpc.Generators.Models ResultType = string.Empty; ResultFullyQualifiedName = string.Empty; HandlerInterfaceName = string.Empty; + ResultProperties = new List(); + IsResultPrimitiveType = false; } } } diff --git a/Svrnty.CQRS.sln b/Svrnty.CQRS.sln index 3564654..ae8249c 100644 --- a/Svrnty.CQRS.sln +++ b/Svrnty.CQRS.sln @@ -31,7 +31,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.Grpc.Abstractio EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.Grpc.Generators", "Svrnty.CQRS.Grpc.Generators\Svrnty.CQRS.Grpc.Generators.csproj", "{11537382-592C-4FE5-9103-272F358F0CAA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.Grpc.Sample", "Svrnty.CQRS.Grpc.Sample\Svrnty.CQRS.Grpc.Sample.csproj", "{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.Sample", "Svrnty.Sample\Svrnty.Sample.csproj", "{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Svrnty.CQRS.Grpc.Sample/AddUserCommand.cs b/Svrnty.Sample/AddUserCommand.cs similarity index 96% rename from Svrnty.CQRS.Grpc.Sample/AddUserCommand.cs rename to Svrnty.Sample/AddUserCommand.cs index 081435f..0e341fc 100644 --- a/Svrnty.CQRS.Grpc.Sample/AddUserCommand.cs +++ b/Svrnty.Sample/AddUserCommand.cs @@ -1,7 +1,7 @@ using FluentValidation; using Svrnty.CQRS.Abstractions; -namespace Svrnty.CQRS.Grpc.Sample; +namespace Svrnty.Sample; public record AddUserCommand { diff --git a/Svrnty.CQRS.Grpc.Sample/FetchUserQuery.cs b/Svrnty.Sample/FetchUserQuery.cs similarity index 94% rename from Svrnty.CQRS.Grpc.Sample/FetchUserQuery.cs rename to Svrnty.Sample/FetchUserQuery.cs index b754b5e..b6b1bf6 100644 --- a/Svrnty.CQRS.Grpc.Sample/FetchUserQuery.cs +++ b/Svrnty.Sample/FetchUserQuery.cs @@ -1,6 +1,6 @@ using Svrnty.CQRS.Abstractions; -namespace Svrnty.CQRS.Grpc.Sample; +namespace Svrnty.Sample; public record User { diff --git a/Svrnty.CQRS.Grpc.Sample/InternalCommand.cs b/Svrnty.Sample/InternalCommand.cs similarity index 94% rename from Svrnty.CQRS.Grpc.Sample/InternalCommand.cs rename to Svrnty.Sample/InternalCommand.cs index 51892b2..c39bded 100644 --- a/Svrnty.CQRS.Grpc.Sample/InternalCommand.cs +++ b/Svrnty.Sample/InternalCommand.cs @@ -1,7 +1,7 @@ using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Grpc.Abstractions.Attributes; -namespace Svrnty.CQRS.Grpc.Sample; +namespace Svrnty.Sample; // This command is marked with [GrpcIgnore] so it won't be exposed via gRPC [GrpcIgnore] diff --git a/Svrnty.CQRS.Grpc.Sample/Program.cs b/Svrnty.Sample/Program.cs similarity index 96% rename from Svrnty.CQRS.Grpc.Sample/Program.cs rename to Svrnty.Sample/Program.cs index 59e412e..9324c26 100644 --- a/Svrnty.CQRS.Grpc.Sample/Program.cs +++ b/Svrnty.Sample/Program.cs @@ -2,8 +2,8 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Svrnty.CQRS; using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.FluentValidation; -using Svrnty.CQRS.Grpc.Sample; -using Svrnty.CQRS.Grpc.Sample.Grpc.Extensions; +using Svrnty.Sample; +using Svrnty.Sample.Grpc.Extensions; using Svrnty.CQRS.MinimalApi; var builder = WebApplication.CreateBuilder(args); diff --git a/Svrnty.CQRS.Grpc.Sample/Protos/cqrs_services.proto b/Svrnty.Sample/Protos/cqrs_services.proto similarity index 95% rename from Svrnty.CQRS.Grpc.Sample/Protos/cqrs_services.proto rename to Svrnty.Sample/Protos/cqrs_services.proto index 4cab121..b84bd32 100644 --- a/Svrnty.CQRS.Grpc.Sample/Protos/cqrs_services.proto +++ b/Svrnty.Sample/Protos/cqrs_services.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -option csharp_namespace = "Svrnty.CQRS.Grpc.Sample.Grpc"; +option csharp_namespace = "Svrnty.Sample.Grpc"; package cqrs; diff --git a/Svrnty.CQRS.Grpc.Sample/RemoveUserCommand.cs b/Svrnty.Sample/RemoveUserCommand.cs similarity index 91% rename from Svrnty.CQRS.Grpc.Sample/RemoveUserCommand.cs rename to Svrnty.Sample/RemoveUserCommand.cs index d92b0b3..eb983a2 100644 --- a/Svrnty.CQRS.Grpc.Sample/RemoveUserCommand.cs +++ b/Svrnty.Sample/RemoveUserCommand.cs @@ -1,6 +1,6 @@ using Svrnty.CQRS.Abstractions; -namespace Svrnty.CQRS.Grpc.Sample; +namespace Svrnty.Sample; public record RemoveUserCommand { diff --git a/Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj b/Svrnty.Sample/Svrnty.Sample.csproj similarity index 100% rename from Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj rename to Svrnty.Sample/Svrnty.Sample.csproj diff --git a/Svrnty.CQRS.Grpc.Sample/appsettings.Development.json b/Svrnty.Sample/appsettings.Development.json similarity index 100% rename from Svrnty.CQRS.Grpc.Sample/appsettings.Development.json rename to Svrnty.Sample/appsettings.Development.json diff --git a/Svrnty.CQRS.Grpc.Sample/appsettings.json b/Svrnty.Sample/appsettings.json similarity index 100% rename from Svrnty.CQRS.Grpc.Sample/appsettings.json rename to Svrnty.Sample/appsettings.json