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.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<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>()
@ -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("?");
}
}
}

View File

@ -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<PropertyInfo> 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<PropertyInfo>();
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 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;
}
}

View File

@ -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<PropertyInfo> 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<PropertyInfo>();
IsResultPrimitiveType = false;
}
}
}

View File

@ -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

View File

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

View File

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

View File

@ -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]

View File

@ -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);

View File

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

View File

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