This commit is contained in:
Mathias Beaulieu-Duncan 2025-11-03 07:44:17 -05:00
parent d2a4639c0e
commit a0426aa0d1
13 changed files with 98 additions and 12 deletions

View File

@ -166,11 +166,15 @@ namespace Svrnty.CQRS.Grpc.Generators
{ {
commandInfo.ResultType = resultType.Name; commandInfo.ResultType = resultType.Name;
commandInfo.ResultFullyQualifiedName = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); 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 else
{ {
commandInfo.HandlerInterfaceName = $"ICommandHandler<{commandType.Name}>"; var commandTypeFullyQualified = commandType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
commandInfo.HandlerInterfaceName = $"ICommandHandler<{commandTypeFullyQualified}>";
} }
// Extract properties // Extract properties
@ -209,7 +213,34 @@ namespace Svrnty.CQRS.Grpc.Generators
// Set result type // Set result type
queryInfo.ResultType = resultType.Name; queryInfo.ResultType = resultType.Name;
queryInfo.ResultFullyQualifiedName = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); 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<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 // Extract properties
var properties = queryType.GetMembers().OfType<IPropertySymbol>() var properties = queryType.GetMembers().OfType<IPropertySymbol>()
@ -857,7 +888,27 @@ namespace Svrnty.CQRS.Grpc.Generators
} }
sb.AppendLine(" };"); sb.AppendLine(" };");
sb.AppendLine(" var result = await handler.HandleAsync(query, context.CancellationToken);"); 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 }};"); 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();
} }
@ -867,5 +918,30 @@ namespace Svrnty.CQRS.Grpc.Generators
return sb.ToString(); 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("?");
}
} }
} }

View File

@ -13,6 +13,8 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
public string? ResultFullyQualifiedName { get; set; } public string? ResultFullyQualifiedName { get; set; }
public bool HasResult => ResultType != null; public bool HasResult => ResultType != null;
public string HandlerInterfaceName { get; set; } public string HandlerInterfaceName { get; set; }
public List<PropertyInfo> ResultProperties { get; set; }
public bool IsResultPrimitiveType { get; set; }
public CommandInfo() public CommandInfo()
{ {
@ -21,6 +23,8 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
Namespace = string.Empty; Namespace = string.Empty;
Properties = new List<PropertyInfo>(); Properties = new List<PropertyInfo>();
HandlerInterfaceName = string.Empty; HandlerInterfaceName = string.Empty;
ResultProperties = new List<PropertyInfo>();
IsResultPrimitiveType = false;
} }
} }
@ -28,6 +32,7 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
{ {
public string Name { get; set; } public string Name { get; set; }
public string Type { get; set; } public string Type { get; set; }
public string FullyQualifiedType { get; set; }
public string ProtoType { get; set; } public string ProtoType { get; set; }
public int FieldNumber { get; set; } public int FieldNumber { get; set; }
@ -35,6 +40,7 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
{ {
Name = string.Empty; Name = string.Empty;
Type = string.Empty; Type = string.Empty;
FullyQualifiedType = string.Empty;
ProtoType = string.Empty; ProtoType = string.Empty;
} }
} }

View File

@ -11,6 +11,8 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
public string ResultType { get; set; } public string ResultType { get; set; }
public string ResultFullyQualifiedName { get; set; } public string ResultFullyQualifiedName { get; set; }
public string HandlerInterfaceName { get; set; } public string HandlerInterfaceName { get; set; }
public List<PropertyInfo> ResultProperties { get; set; }
public bool IsResultPrimitiveType { get; set; }
public QueryInfo() public QueryInfo()
{ {
@ -21,6 +23,8 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
ResultType = string.Empty; ResultType = string.Empty;
ResultFullyQualifiedName = string.Empty; ResultFullyQualifiedName = string.Empty;
HandlerInterfaceName = string.Empty; HandlerInterfaceName = string.Empty;
ResultProperties = new List<PropertyInfo>();
IsResultPrimitiveType = false;
} }
} }
} }

View File

@ -31,7 +31,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.Grpc.Abstractio
EndProject 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}" 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -1,7 +1,7 @@
using FluentValidation; using FluentValidation;
using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Abstractions;
namespace Svrnty.CQRS.Grpc.Sample; namespace Svrnty.Sample;
public record AddUserCommand public record AddUserCommand
{ {

View File

@ -1,6 +1,6 @@
using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Abstractions;
namespace Svrnty.CQRS.Grpc.Sample; namespace Svrnty.Sample;
public record User public record User
{ {

View File

@ -1,7 +1,7 @@
using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Abstractions;
using Svrnty.CQRS.Grpc.Abstractions.Attributes; 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 // This command is marked with [GrpcIgnore] so it won't be exposed via gRPC
[GrpcIgnore] [GrpcIgnore]

View File

@ -2,8 +2,8 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
using Svrnty.CQRS; using Svrnty.CQRS;
using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Abstractions;
using Svrnty.CQRS.FluentValidation; using Svrnty.CQRS.FluentValidation;
using Svrnty.CQRS.Grpc.Sample; using Svrnty.Sample;
using Svrnty.CQRS.Grpc.Sample.Grpc.Extensions; using Svrnty.Sample.Grpc.Extensions;
using Svrnty.CQRS.MinimalApi; using Svrnty.CQRS.MinimalApi;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);

View File

@ -1,6 +1,6 @@
syntax = "proto3"; syntax = "proto3";
option csharp_namespace = "Svrnty.CQRS.Grpc.Sample.Grpc"; option csharp_namespace = "Svrnty.Sample.Grpc";
package cqrs; package cqrs;

View File

@ -1,6 +1,6 @@
using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Abstractions;
namespace Svrnty.CQRS.Grpc.Sample; namespace Svrnty.Sample;
public record RemoveUserCommand public record RemoveUserCommand
{ {