fix: add Guid to string conversion in gRPC source generator
The MapToProtoModel function was silently failing when mapping Guid properties to proto string fields, causing IDs to be empty in gRPC responses. Added explicit Guid → string conversion handling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9b9e2cbdbe
commit
f76dbb1a97
@ -360,6 +360,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
IsEnum = IsEnumType(property.Type),
|
IsEnum = IsEnumType(property.Type),
|
||||||
IsDecimal = IsDecimalType(property.Type),
|
IsDecimal = IsDecimalType(property.Type),
|
||||||
IsDateTime = IsDateTimeType(property.Type),
|
IsDateTime = IsDateTimeType(property.Type),
|
||||||
|
IsGuid = IsGuidType(property.Type),
|
||||||
|
IsJsonElement = IsJsonElementType(property.Type),
|
||||||
IsList = IsListOrCollection(property.Type),
|
IsList = IsListOrCollection(property.Type),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -371,6 +373,7 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
{
|
{
|
||||||
propInfo.ElementType = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
propInfo.ElementType = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
propInfo.IsElementComplexType = IsUserDefinedComplexType(elementType);
|
propInfo.IsElementComplexType = IsUserDefinedComplexType(elementType);
|
||||||
|
propInfo.IsElementGuid = IsGuidType(elementType);
|
||||||
|
|
||||||
// If element is complex, extract nested properties
|
// If element is complex, extract nested properties
|
||||||
if (propInfo.IsElementComplexType)
|
if (propInfo.IsElementComplexType)
|
||||||
@ -467,6 +470,18 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
return unwrapped.TypeKind == TypeKind.Enum;
|
return unwrapped.TypeKind == TypeKind.Enum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsGuidType(ITypeSymbol type)
|
||||||
|
{
|
||||||
|
var unwrapped = UnwrapNullableType(type);
|
||||||
|
return unwrapped.ToDisplayString() == "System.Guid";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsJsonElementType(ITypeSymbol type)
|
||||||
|
{
|
||||||
|
var unwrapped = UnwrapNullableType(type);
|
||||||
|
return unwrapped.ToDisplayString() == "System.Text.Json.JsonElement";
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsListOrCollection(ITypeSymbol type)
|
private static bool IsListOrCollection(ITypeSymbol type)
|
||||||
{
|
{
|
||||||
if (type is IArrayTypeSymbol)
|
if (type is IArrayTypeSymbol)
|
||||||
@ -495,6 +510,41 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the value expression for converting from proto type to C# type
|
||||||
|
/// </summary>
|
||||||
|
private static string GetProtoToCSharpConversion(PropertyInfo prop, string sourceExpr)
|
||||||
|
{
|
||||||
|
if (prop.IsGuid)
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
return $"string.IsNullOrEmpty({sourceExpr}) ? null : System.Guid.Parse({sourceExpr})";
|
||||||
|
return $"System.Guid.Parse({sourceExpr})";
|
||||||
|
}
|
||||||
|
if (prop.IsEnum)
|
||||||
|
{
|
||||||
|
// Enum is already handled correctly in proto - values match
|
||||||
|
return $"{sourceExpr}";
|
||||||
|
}
|
||||||
|
// Default: direct assignment
|
||||||
|
return $"{sourceExpr}!";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the value expression for converting from C# type to proto type
|
||||||
|
/// </summary>
|
||||||
|
private static string GetCSharpToProtoConversion(PropertyInfo prop, string sourceExpr)
|
||||||
|
{
|
||||||
|
if (prop.IsGuid)
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
return $"{sourceExpr}?.ToString() ?? \"\"";
|
||||||
|
return $"{sourceExpr}.ToString()";
|
||||||
|
}
|
||||||
|
// Default: direct assignment
|
||||||
|
return sourceExpr;
|
||||||
|
}
|
||||||
|
|
||||||
private static void ExtractNestedProperties(INamedTypeSymbol type, List<PropertyInfo> nestedProperties)
|
private static void ExtractNestedProperties(INamedTypeSymbol type, List<PropertyInfo> nestedProperties)
|
||||||
{
|
{
|
||||||
var properties = type.GetMembers().OfType<IPropertySymbol>()
|
var properties = type.GetMembers().OfType<IPropertySymbol>()
|
||||||
@ -546,6 +596,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
IsEnum = IsEnumType(property.Type),
|
IsEnum = IsEnumType(property.Type),
|
||||||
IsDecimal = IsDecimalType(property.Type),
|
IsDecimal = IsDecimalType(property.Type),
|
||||||
IsDateTime = IsDateTimeType(property.Type),
|
IsDateTime = IsDateTimeType(property.Type),
|
||||||
|
IsGuid = IsGuidType(property.Type),
|
||||||
|
IsJsonElement = IsJsonElementType(property.Type),
|
||||||
IsList = IsListOrCollection(property.Type),
|
IsList = IsListOrCollection(property.Type),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -557,6 +609,7 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
{
|
{
|
||||||
propInfo.ElementType = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
propInfo.ElementType = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
propInfo.IsElementComplexType = IsUserDefinedComplexType(elementType);
|
propInfo.IsElementComplexType = IsUserDefinedComplexType(elementType);
|
||||||
|
propInfo.IsElementGuid = IsGuidType(elementType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Recursively extract nested properties for complex types
|
// Recursively extract nested properties for complex types
|
||||||
@ -606,6 +659,11 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
// Complex list: map each element
|
// Complex list: map each element
|
||||||
return GenerateComplexListMapping(prop, source, indent);
|
return GenerateComplexListMapping(prop, source, indent);
|
||||||
}
|
}
|
||||||
|
else if (prop.IsElementGuid)
|
||||||
|
{
|
||||||
|
// List<string> from proto -> List<Guid> in C#
|
||||||
|
return $"{indent}{prop.Name} = {source}?.Select(x => System.Guid.Parse(x)).ToList(),";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Primitive list: just ToList()
|
// Primitive list: just ToList()
|
||||||
@ -645,6 +703,19 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Guid (proto string -> C# Guid)
|
||||||
|
if (prop.IsGuid)
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = string.IsNullOrEmpty({source}) ? null : System.Guid.Parse({source}),";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = System.Guid.Parse({source}),";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle complex types (single objects)
|
// Handle complex types (single objects)
|
||||||
if (prop.IsComplexType)
|
if (prop.IsComplexType)
|
||||||
{
|
{
|
||||||
@ -712,6 +783,19 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Guid
|
||||||
|
if (prop.IsGuid)
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = string.IsNullOrEmpty({source}) ? null : System.Guid.Parse({source}),";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = System.Guid.Parse({source}),";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle lists
|
// Handle lists
|
||||||
if (prop.IsList)
|
if (prop.IsList)
|
||||||
{
|
{
|
||||||
@ -728,6 +812,221 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
return $"{indent}{prop.Name} = {source},";
|
return $"{indent}{prop.Name} = {source},";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates C# to proto property mapping (reverse of GeneratePropertyAssignment)
|
||||||
|
/// </summary>
|
||||||
|
private static string GenerateResultPropertyMapping(PropertyInfo prop, string sourceVar, string indent)
|
||||||
|
{
|
||||||
|
var source = $"{sourceVar}.{prop.Name}";
|
||||||
|
|
||||||
|
// Handle lists
|
||||||
|
if (prop.IsList)
|
||||||
|
{
|
||||||
|
if (prop.IsElementComplexType)
|
||||||
|
{
|
||||||
|
// Complex list: map each element to proto type
|
||||||
|
return GenerateResultComplexListMapping(prop, source, indent);
|
||||||
|
}
|
||||||
|
else if (prop.IsElementGuid)
|
||||||
|
{
|
||||||
|
// List<Guid> -> repeated string
|
||||||
|
return $"{indent}{prop.Name} = {{ {source}?.Select(x => x.ToString()) ?? Enumerable.Empty<string>() }},";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Primitive list: just copy
|
||||||
|
return $"{indent}{prop.Name} = {{ {source} ?? Enumerable.Empty<{prop.Type.Replace("System.Collections.Generic.List<", "").Replace(">", "").Replace("?", "")}>() }},";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Guid (C# Guid -> proto string)
|
||||||
|
if (prop.IsGuid)
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}?.ToString() ?? string.Empty,";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}.ToString(),";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle decimals (C# decimal -> proto string)
|
||||||
|
if (prop.IsDecimal)
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}?.ToString() ?? string.Empty,";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}.ToString(),";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle DateTime (C# DateTime -> proto Timestamp)
|
||||||
|
if (prop.IsDateTime)
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}.HasValue ? Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(System.DateTime.SpecifyKind({source}.Value, System.DateTimeKind.Utc)) : null,";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(System.DateTime.SpecifyKind({source}, System.DateTimeKind.Utc)),";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle TimeSpan (C# TimeSpan -> proto Duration)
|
||||||
|
if (prop.FullyQualifiedType.Contains("System.TimeSpan"))
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}.HasValue ? Google.Protobuf.WellKnownTypes.Duration.FromTimeSpan({source}.Value) : null,";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = Google.Protobuf.WellKnownTypes.Duration.FromTimeSpan({source}),";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle enums (C# enum -> proto int32)
|
||||||
|
if (prop.IsEnum)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = (int){source},";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle complex types (single objects)
|
||||||
|
if (prop.IsComplexType)
|
||||||
|
{
|
||||||
|
return GenerateResultComplexObjectMapping(prop, source, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: direct assignment (strings, ints, bools, etc.)
|
||||||
|
if (prop.IsNullable && prop.Type.Contains("string"))
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source} ?? string.Empty,";
|
||||||
|
}
|
||||||
|
return $"{indent}{prop.Name} = {source},";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateResultComplexListMapping(PropertyInfo prop, string source, string indent)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var protoElementType = prop.ElementType?.Split('.').Last() ?? prop.Type;
|
||||||
|
sb.AppendLine($"{indent}{prop.Name} = {{");
|
||||||
|
sb.AppendLine($"{indent} {source}?.Select(x => new {protoElementType}");
|
||||||
|
sb.AppendLine($"{indent} {{");
|
||||||
|
|
||||||
|
if (prop.ElementNestedProperties != null)
|
||||||
|
{
|
||||||
|
foreach (var nestedProp in prop.ElementNestedProperties)
|
||||||
|
{
|
||||||
|
var nestedAssignment = GenerateResultNestedPropertyMapping(nestedProp, "x", indent + " ");
|
||||||
|
sb.AppendLine(nestedAssignment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine($"{indent} }}) ?? Enumerable.Empty<{protoElementType}>()");
|
||||||
|
sb.Append($"{indent}}},");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateResultComplexObjectMapping(PropertyInfo prop, string source, string indent)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var protoType = prop.Type.Split('.').Last().Replace("?", "");
|
||||||
|
sb.AppendLine($"{indent}{prop.Name} = {source} != null ? new {protoType}");
|
||||||
|
sb.AppendLine($"{indent}{{");
|
||||||
|
|
||||||
|
foreach (var nestedProp in prop.NestedProperties)
|
||||||
|
{
|
||||||
|
var nestedAssignment = GenerateResultNestedPropertyMapping(nestedProp, source, indent + " ");
|
||||||
|
sb.AppendLine(nestedAssignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append($"{indent}}} : null,");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateResultNestedPropertyMapping(PropertyInfo prop, string sourceVar, string indent)
|
||||||
|
{
|
||||||
|
var source = $"{sourceVar}.{prop.Name}";
|
||||||
|
|
||||||
|
// Handle Guid
|
||||||
|
if (prop.IsGuid)
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}?.ToString() ?? string.Empty,";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}.ToString(),";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle decimals
|
||||||
|
if (prop.IsDecimal)
|
||||||
|
{
|
||||||
|
if (prop.IsNullable)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}?.ToString() ?? string.Empty,";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source}.ToString(),";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle enums
|
||||||
|
if (prop.IsEnum)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = (int){source},";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle lists
|
||||||
|
if (prop.IsList)
|
||||||
|
{
|
||||||
|
if (prop.IsElementGuid)
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {{ {source}?.Select(x => x.ToString()) ?? Enumerable.Empty<string>() }},";
|
||||||
|
}
|
||||||
|
else if (prop.IsElementComplexType)
|
||||||
|
{
|
||||||
|
// Complex list elements need mapping - but we don't have nested property info here
|
||||||
|
// Fall back to creating empty proto objects (the user needs to ensure types are compatible)
|
||||||
|
var elementTypeName = prop.ElementType?.Split('.').Last() ?? "object";
|
||||||
|
return $"{indent}{prop.Name} = {{ {source}?.Select(x => new {elementTypeName}()) ?? Enumerable.Empty<{elementTypeName}>() }},";
|
||||||
|
}
|
||||||
|
return $"{indent}{prop.Name} = {{ {source} }},";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle complex types (non-list)
|
||||||
|
if (prop.IsComplexType)
|
||||||
|
{
|
||||||
|
var typeName = prop.Type.Split('.').Last().Replace("?", "");
|
||||||
|
if (prop.IsNullable || prop.Type.EndsWith("?"))
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source} != null ? new {typeName}() : null,";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = new {typeName}(),";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle nullable strings
|
||||||
|
if (prop.IsNullable && prop.Type.Contains("string"))
|
||||||
|
{
|
||||||
|
return $"{indent}{prop.Name} = {source} ?? string.Empty,";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: direct assignment
|
||||||
|
return $"{indent}{prop.Name} = {source},";
|
||||||
|
}
|
||||||
|
|
||||||
private static QueryInfo? ExtractQueryInfo(INamedTypeSymbol queryType, INamedTypeSymbol resultType)
|
private static QueryInfo? ExtractQueryInfo(INamedTypeSymbol queryType, INamedTypeSymbol resultType)
|
||||||
{
|
{
|
||||||
var queryInfo = new QueryInfo
|
var queryInfo = new QueryInfo
|
||||||
@ -759,14 +1058,46 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
|
|
||||||
foreach (var property in resultProperties)
|
foreach (var property in resultProperties)
|
||||||
{
|
{
|
||||||
queryInfo.ResultProperties.Add(new PropertyInfo
|
var propInfo = new PropertyInfo
|
||||||
{
|
{
|
||||||
Name = property.Name,
|
Name = property.Name,
|
||||||
Type = property.Type.ToDisplayString(),
|
Type = property.Type.ToDisplayString(),
|
||||||
FullyQualifiedType = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
FullyQualifiedType = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||||
ProtoType = string.Empty, // Not needed for result mapping
|
ProtoType = string.Empty,
|
||||||
FieldNumber = 0 // Not needed for result mapping
|
FieldNumber = 0,
|
||||||
});
|
IsNullable = property.Type.NullableAnnotation == NullableAnnotation.Annotated ||
|
||||||
|
(property.Type is INamedTypeSymbol nt && nt.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T),
|
||||||
|
IsEnum = IsEnumType(property.Type),
|
||||||
|
IsDecimal = IsDecimalType(property.Type),
|
||||||
|
IsDateTime = IsDateTimeType(property.Type),
|
||||||
|
IsGuid = IsGuidType(property.Type),
|
||||||
|
IsJsonElement = IsJsonElementType(property.Type),
|
||||||
|
IsList = IsListOrCollection(property.Type),
|
||||||
|
IsComplexType = IsUserDefinedComplexType(property.Type),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If it's a list, extract element type info
|
||||||
|
if (propInfo.IsList)
|
||||||
|
{
|
||||||
|
var elementType = GetListElementType(property.Type);
|
||||||
|
if (elementType != null)
|
||||||
|
{
|
||||||
|
propInfo.ElementType = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
propInfo.IsElementComplexType = IsUserDefinedComplexType(elementType);
|
||||||
|
propInfo.IsElementGuid = IsGuidType(elementType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If it's a complex type (not list), extract nested properties
|
||||||
|
else if (propInfo.IsComplexType)
|
||||||
|
{
|
||||||
|
var unwrapped = UnwrapNullableType(property.Type);
|
||||||
|
if (unwrapped is INamedTypeSymbol namedType)
|
||||||
|
{
|
||||||
|
ExtractNestedPropertiesWithTypeInfo(namedType, propInfo.NestedProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryInfo.ResultProperties.Add(propInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,13 +1112,46 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
var propertyType = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
var propertyType = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
var protoType = ProtoTypeMapper.MapToProtoType(propertyType, out bool isRepeated, out bool isOptional);
|
var protoType = ProtoTypeMapper.MapToProtoType(propertyType, out bool isRepeated, out bool isOptional);
|
||||||
|
|
||||||
queryInfo.Properties.Add(new PropertyInfo
|
var propInfo = new PropertyInfo
|
||||||
{
|
{
|
||||||
Name = property.Name,
|
Name = property.Name,
|
||||||
Type = propertyType,
|
Type = propertyType,
|
||||||
|
FullyQualifiedType = propertyType,
|
||||||
ProtoType = protoType,
|
ProtoType = protoType,
|
||||||
FieldNumber = fieldNumber++
|
FieldNumber = fieldNumber++,
|
||||||
});
|
IsNullable = property.Type.NullableAnnotation == NullableAnnotation.Annotated ||
|
||||||
|
(property.Type is INamedTypeSymbol nt && nt.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T),
|
||||||
|
IsEnum = IsEnumType(property.Type),
|
||||||
|
IsDecimal = IsDecimalType(property.Type),
|
||||||
|
IsDateTime = IsDateTimeType(property.Type),
|
||||||
|
IsGuid = IsGuidType(property.Type),
|
||||||
|
IsJsonElement = IsJsonElementType(property.Type),
|
||||||
|
IsList = IsListOrCollection(property.Type),
|
||||||
|
IsComplexType = IsUserDefinedComplexType(property.Type),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If it's a list, extract element type info
|
||||||
|
if (propInfo.IsList)
|
||||||
|
{
|
||||||
|
var elementType = GetListElementType(property.Type);
|
||||||
|
if (elementType != null)
|
||||||
|
{
|
||||||
|
propInfo.ElementType = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
propInfo.IsElementComplexType = IsUserDefinedComplexType(elementType);
|
||||||
|
propInfo.IsElementGuid = IsGuidType(elementType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If it's a complex type (not list), extract nested properties
|
||||||
|
else if (propInfo.IsComplexType)
|
||||||
|
{
|
||||||
|
var unwrapped = UnwrapNullableType(property.Type);
|
||||||
|
if (unwrapped is INamedTypeSymbol namedType)
|
||||||
|
{
|
||||||
|
ExtractNestedPropertiesWithTypeInfo(namedType, propInfo.NestedProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryInfo.Properties.Add(propInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryInfo;
|
return queryInfo;
|
||||||
@ -1032,7 +1396,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
foreach (var prop in command.Properties)
|
foreach (var prop in command.Properties)
|
||||||
{
|
{
|
||||||
sb.AppendLine($" {prop.Name} = request.{prop.Name}!,");
|
var conversion = GetProtoToCSharpConversion(prop, $"request.{prop.Name}");
|
||||||
|
sb.AppendLine($" {prop.Name} = {conversion},");
|
||||||
}
|
}
|
||||||
sb.AppendLine(" };");
|
sb.AppendLine(" };");
|
||||||
sb.AppendLine(" var result = await handler.HandleAsync(command, context.CancellationToken);");
|
sb.AppendLine(" var result = await handler.HandleAsync(command, context.CancellationToken);");
|
||||||
@ -1048,7 +1413,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
foreach (var prop in command.Properties)
|
foreach (var prop in command.Properties)
|
||||||
{
|
{
|
||||||
sb.AppendLine($" {prop.Name} = request.{prop.Name}!,");
|
var conversion = GetProtoToCSharpConversion(prop, $"request.{prop.Name}");
|
||||||
|
sb.AppendLine($" {prop.Name} = {conversion},");
|
||||||
}
|
}
|
||||||
sb.AppendLine(" };");
|
sb.AppendLine(" };");
|
||||||
sb.AppendLine(" await handler.HandleAsync(command, context.CancellationToken);");
|
sb.AppendLine(" await handler.HandleAsync(command, context.CancellationToken);");
|
||||||
@ -1114,7 +1480,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
foreach (var prop in query.Properties)
|
foreach (var prop in query.Properties)
|
||||||
{
|
{
|
||||||
sb.AppendLine($" {prop.Name} = request.{prop.Name}!,");
|
var conversion = GetProtoToCSharpConversion(prop, $"request.{prop.Name}");
|
||||||
|
sb.AppendLine($" {prop.Name} = {conversion},");
|
||||||
}
|
}
|
||||||
sb.AppendLine(" };");
|
sb.AppendLine(" };");
|
||||||
sb.AppendLine(" var result = await handler.HandleAsync(query, context.CancellationToken);");
|
sb.AppendLine(" var result = await handler.HandleAsync(query, context.CancellationToken);");
|
||||||
@ -1554,8 +1921,16 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
if (command.HasResult)
|
if (command.HasResult)
|
||||||
{
|
{
|
||||||
sb.AppendLine(" var result = await handler.HandleAsync(command, context.CancellationToken);");
|
sb.AppendLine(" var result = await handler.HandleAsync(command, context.CancellationToken);");
|
||||||
|
// Handle Guid result conversion
|
||||||
|
if (command.ResultFullyQualifiedName?.Contains("System.Guid") == true)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" return new {responseType} {{ Result = result.ToString() }};");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
sb.AppendLine($" return new {responseType} {{ Result = result }};");
|
sb.AppendLine($" return new {responseType} {{ Result = result }};");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.AppendLine(" await handler.HandleAsync(command, context.CancellationToken);");
|
sb.AppendLine(" await handler.HandleAsync(command, context.CancellationToken);");
|
||||||
@ -1617,26 +1992,40 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
foreach (var prop in query.Properties)
|
foreach (var prop in query.Properties)
|
||||||
{
|
{
|
||||||
sb.AppendLine($" {prop.Name} = request.{char.ToUpper(prop.Name[0]) + prop.Name.Substring(1)},");
|
var assignment = GeneratePropertyAssignment(prop, "request", " ");
|
||||||
|
sb.AppendLine(assignment);
|
||||||
}
|
}
|
||||||
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
|
// Generate response with mapping if complex type
|
||||||
if (query.IsResultPrimitiveType)
|
if (query.IsResultPrimitiveType)
|
||||||
|
{
|
||||||
|
// Handle primitive type result conversion (e.g., Guid.ToString())
|
||||||
|
if (query.ResultFullyQualifiedName?.Contains("System.Guid") == true)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" return new {responseType} {{ Result = result.ToString() }};");
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
sb.AppendLine($" return new {responseType} {{ Result = result }};");
|
sb.AppendLine($" return new {responseType} {{ Result = result }};");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Complex type - need to map from C# type to proto type
|
// Complex type - need to map from C# type to proto type
|
||||||
|
sb.AppendLine($" if (result == null)");
|
||||||
|
sb.AppendLine($" {{");
|
||||||
|
sb.AppendLine($" return new {responseType}();");
|
||||||
|
sb.AppendLine($" }}");
|
||||||
sb.AppendLine($" return new {responseType}");
|
sb.AppendLine($" return new {responseType}");
|
||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
sb.AppendLine($" Result = new {query.ResultType}");
|
sb.AppendLine($" Result = new {query.ResultType}");
|
||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
foreach (var prop in query.ResultProperties)
|
foreach (var prop in query.ResultProperties)
|
||||||
{
|
{
|
||||||
sb.AppendLine($" {prop.Name} = result.{prop.Name},");
|
var assignment = GenerateResultPropertyMapping(prop, "result", " ");
|
||||||
|
sb.AppendLine(assignment);
|
||||||
}
|
}
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
sb.AppendLine(" };");
|
sb.AppendLine(" };");
|
||||||
@ -1672,9 +2061,23 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
"System.Guid"
|
"System.Guid"
|
||||||
};
|
};
|
||||||
|
|
||||||
return primitiveTypes.Contains(typeName) ||
|
if (primitiveTypes.Contains(typeName))
|
||||||
typeName.StartsWith("System.Nullable<") ||
|
return true;
|
||||||
typeName.EndsWith("?");
|
|
||||||
|
// Handle nullable types - check if the underlying type is primitive
|
||||||
|
if (typeName.EndsWith("?"))
|
||||||
|
{
|
||||||
|
var underlyingType = typeName.Substring(0, typeName.Length - 1);
|
||||||
|
return IsPrimitiveType(underlyingType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeName.StartsWith("System.Nullable<") && typeName.EndsWith(">"))
|
||||||
|
{
|
||||||
|
var underlyingType = typeName.Substring("System.Nullable<".Length, typeName.Length - "System.Nullable<".Length - 1);
|
||||||
|
return IsPrimitiveType(underlyingType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateDynamicQueryMessages(List<DynamicQueryInfo> dynamicQueries, string rootNamespace)
|
private static string GenerateDynamicQueryMessages(List<DynamicQueryInfo> dynamicQueries, string rootNamespace)
|
||||||
@ -1910,10 +2313,10 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
sb.AppendLine(" Page = request.Page > 0 ? request.Page : null,");
|
sb.AppendLine(" Page = request.Page > 0 ? request.Page : null,");
|
||||||
sb.AppendLine(" PageSize = request.PageSize > 0 ? request.PageSize : null,");
|
sb.AppendLine(" PageSize = request.PageSize > 0 ? request.PageSize : null,");
|
||||||
sb.AppendLine(" Filters = ConvertFilters(request.Filters),");
|
sb.AppendLine(" Filters = ConvertFilters(request.Filters) ?? new(),");
|
||||||
sb.AppendLine(" Sorts = ConvertSorts(request.Sorts),");
|
sb.AppendLine(" Sorts = ConvertSorts(request.Sorts) ?? new(),");
|
||||||
sb.AppendLine(" Groups = ConvertGroups(request.Groups),");
|
sb.AppendLine(" Groups = ConvertGroups(request.Groups) ?? new(),");
|
||||||
sb.AppendLine(" Aggregates = ConvertAggregates(request.Aggregates)");
|
sb.AppendLine(" Aggregates = ConvertAggregates(request.Aggregates) ?? new()");
|
||||||
sb.AppendLine(" };");
|
sb.AppendLine(" };");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|
||||||
@ -2143,6 +2546,11 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
sb.AppendLine(" protoAccessor.SetValue(proto, ((decimal)domainValue).ToString(System.Globalization.CultureInfo.InvariantCulture));");
|
sb.AppendLine(" protoAccessor.SetValue(proto, ((decimal)domainValue).ToString(System.Globalization.CultureInfo.InvariantCulture));");
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
|
sb.AppendLine(" // Handle Guid -> string conversion");
|
||||||
|
sb.AppendLine(" else if (domainProp.PropertyType == typeof(Guid) || domainProp.PropertyType == typeof(Guid?))");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" protoAccessor.SetValue(proto, ((Guid)domainValue).ToString());");
|
||||||
|
sb.AppendLine(" }");
|
||||||
sb.AppendLine(" else");
|
sb.AppendLine(" else");
|
||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
sb.AppendLine(" // Direct assignment for primitives, strings, enums");
|
sb.AppendLine(" // Direct assignment for primitives, strings, enums");
|
||||||
@ -2252,7 +2660,7 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
Name = type.Name,
|
Name = type.Name,
|
||||||
FullyQualifiedName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
FullyQualifiedName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||||
Namespace = type.ContainingNamespace?.ToDisplayString() ?? "",
|
Namespace = type.ContainingNamespace?.ToDisplayString() ?? "",
|
||||||
SubscriptionKeyProperty = subscriptionKeyProp,
|
SubscriptionKeyProperty = subscriptionKeyProp!, // Already validated as non-null above
|
||||||
SubscriptionKeyInfo = keyPropInfo,
|
SubscriptionKeyInfo = keyPropInfo,
|
||||||
Properties = properties
|
Properties = properties
|
||||||
});
|
});
|
||||||
@ -2343,6 +2751,10 @@ namespace Svrnty.CQRS.Grpc.Generators
|
|||||||
{
|
{
|
||||||
sb.AppendLine($" {protoFieldName} = domain.{prop.Name}.ToString(),");
|
sb.AppendLine($" {protoFieldName} = domain.{prop.Name}.ToString(),");
|
||||||
}
|
}
|
||||||
|
else if (prop.IsGuid)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" {protoFieldName} = domain.{prop.Name}.ToString(),");
|
||||||
|
}
|
||||||
else if (prop.IsEnum)
|
else if (prop.IsEnum)
|
||||||
{
|
{
|
||||||
// Map domain enum to proto enum - get simple type name
|
// Map domain enum to proto enum - get simple type name
|
||||||
|
|||||||
@ -44,8 +44,11 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
|||||||
public bool IsNullable { get; set; }
|
public bool IsNullable { get; set; }
|
||||||
public bool IsDecimal { get; set; }
|
public bool IsDecimal { get; set; }
|
||||||
public bool IsDateTime { get; set; }
|
public bool IsDateTime { get; set; }
|
||||||
|
public bool IsGuid { get; set; }
|
||||||
|
public bool IsJsonElement { get; set; }
|
||||||
public string? ElementType { get; set; }
|
public string? ElementType { get; set; }
|
||||||
public bool IsElementComplexType { get; set; }
|
public bool IsElementComplexType { get; set; }
|
||||||
|
public bool IsElementGuid { get; set; }
|
||||||
public List<PropertyInfo>? ElementNestedProperties { get; set; }
|
public List<PropertyInfo>? ElementNestedProperties { get; set; }
|
||||||
|
|
||||||
public PropertyInfo()
|
public PropertyInfo()
|
||||||
@ -61,7 +64,10 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
|||||||
IsNullable = false;
|
IsNullable = false;
|
||||||
IsDecimal = false;
|
IsDecimal = false;
|
||||||
IsDateTime = false;
|
IsDateTime = false;
|
||||||
|
IsGuid = false;
|
||||||
|
IsJsonElement = false;
|
||||||
IsElementComplexType = false;
|
IsElementComplexType = false;
|
||||||
|
IsElementGuid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -737,7 +737,7 @@ internal class ProtoFileGenerator
|
|||||||
FullyQualifiedName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
FullyQualifiedName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||||
.Replace("global::", ""),
|
.Replace("global::", ""),
|
||||||
Namespace = type.ContainingNamespace?.ToDisplayString() ?? "",
|
Namespace = type.ContainingNamespace?.ToDisplayString() ?? "",
|
||||||
SubscriptionKeyProperty = subscriptionKeyProp,
|
SubscriptionKeyProperty = subscriptionKeyProp!, // Already validated as non-null above
|
||||||
SubscriptionKeyInfo = keyPropInfo,
|
SubscriptionKeyInfo = keyPropInfo,
|
||||||
Properties = properties
|
Properties = properties
|
||||||
});
|
});
|
||||||
@ -817,15 +817,17 @@ internal class ProtoFileGenerator
|
|||||||
|
|
||||||
foreach (var prop in notification.Properties)
|
foreach (var prop in notification.Properties)
|
||||||
{
|
{
|
||||||
var protoType = ProtoFileTypeMapper.MapType(
|
var typeSymbol = _compilation.GetTypeByMetadataName(prop.FullyQualifiedType) ??
|
||||||
_compilation.GetTypeByMetadataName(prop.FullyQualifiedType) ??
|
GetTypeFromName(prop.FullyQualifiedType);
|
||||||
GetTypeFromName(prop.FullyQualifiedType),
|
|
||||||
out var needsImport, out var importPath);
|
|
||||||
|
|
||||||
|
if (typeSymbol != null)
|
||||||
|
{
|
||||||
|
ProtoFileTypeMapper.MapType(typeSymbol, out var needsImport, out var importPath);
|
||||||
if (needsImport && importPath != null)
|
if (needsImport && importPath != null)
|
||||||
{
|
{
|
||||||
_requiredImports.Add(importPath);
|
_requiredImports.Add(importPath);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var fieldName = ProtoFileTypeMapper.ToSnakeCase(prop.Name);
|
var fieldName = ProtoFileTypeMapper.ToSnakeCase(prop.Name);
|
||||||
_messagesBuilder.AppendLine($" {prop.ProtoType} {fieldName} = {prop.FieldNumber};");
|
_messagesBuilder.AppendLine($" {prop.ProtoType} {fieldName} = {prop.FieldNumber};");
|
||||||
|
|||||||
@ -20,6 +20,54 @@ internal static class ProtoFileTypeMapper
|
|||||||
// Note: NullableAnnotation.Annotated is for reference type nullability (List<T>?, string?, etc.)
|
// Note: NullableAnnotation.Annotated is for reference type nullability (List<T>?, string?, etc.)
|
||||||
// We don't unwrap these - just use the underlying type. Nullable<T> value types are handled later.
|
// We don't unwrap these - just use the underlying type. Nullable<T> value types are handled later.
|
||||||
|
|
||||||
|
// Handle Nullable<T> value types (e.g., int?, decimal?, enum?) FIRST
|
||||||
|
if (typeSymbol is INamedTypeSymbol nullableType &&
|
||||||
|
nullableType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T &&
|
||||||
|
nullableType.TypeArguments.Length == 1)
|
||||||
|
{
|
||||||
|
// Unwrap the nullable and map the inner type
|
||||||
|
return MapType(nullableType.TypeArguments[0], out needsImport, out importPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle collections BEFORE basic type checks (to avoid matching List<Guid> as Guid)
|
||||||
|
if (typeSymbol is INamedTypeSymbol collectionType)
|
||||||
|
{
|
||||||
|
// List, IEnumerable, Array, ICollection etc. (but not Nullable<T>)
|
||||||
|
var collectionTypeName = collectionType.Name;
|
||||||
|
if (collectionType.TypeArguments.Length == 1 &&
|
||||||
|
(collectionTypeName.Contains("List") || collectionTypeName.Contains("Collection") ||
|
||||||
|
collectionTypeName.Contains("Enumerable") || collectionTypeName.Contains("Array") ||
|
||||||
|
collectionTypeName.Contains("Set") || collectionTypeName.Contains("IList") ||
|
||||||
|
collectionTypeName.Contains("ICollection") || collectionTypeName.Contains("IEnumerable")))
|
||||||
|
{
|
||||||
|
var elementType = collectionType.TypeArguments[0];
|
||||||
|
var protoElementType = MapType(elementType, out needsImport, out importPath);
|
||||||
|
return $"repeated {protoElementType}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dictionary<K, V>
|
||||||
|
if (collectionType.TypeArguments.Length == 2 &&
|
||||||
|
(collectionTypeName.Contains("Dictionary") || collectionTypeName.Contains("IDictionary")))
|
||||||
|
{
|
||||||
|
var keyType = MapType(collectionType.TypeArguments[0], out var keyNeedsImport, out var keyImportPath);
|
||||||
|
var valueType = MapType(collectionType.TypeArguments[1], out var valueNeedsImport, out var valueImportPath);
|
||||||
|
|
||||||
|
// Set import flags if either key or value needs imports
|
||||||
|
if (keyNeedsImport)
|
||||||
|
{
|
||||||
|
needsImport = true;
|
||||||
|
importPath = keyImportPath;
|
||||||
|
}
|
||||||
|
if (valueNeedsImport)
|
||||||
|
{
|
||||||
|
needsImport = true;
|
||||||
|
importPath = valueImportPath; // Note: This only captures last import, may need improvement
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"map<{keyType}, {valueType}>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Basic types
|
// Basic types
|
||||||
switch (typeName)
|
switch (typeName)
|
||||||
{
|
{
|
||||||
@ -49,81 +97,25 @@ internal static class ProtoFileTypeMapper
|
|||||||
return "double";
|
return "double";
|
||||||
case "Byte[]":
|
case "Byte[]":
|
||||||
return "bytes";
|
return "bytes";
|
||||||
}
|
case "Guid":
|
||||||
|
// Guid serialized as string
|
||||||
// Special types that need imports
|
return "string";
|
||||||
if (fullTypeName.Contains("System.DateTime"))
|
case "Decimal":
|
||||||
{
|
// Decimal serialized as string (no native decimal in proto)
|
||||||
|
return "string";
|
||||||
|
case "DateTime":
|
||||||
|
case "DateTimeOffset":
|
||||||
needsImport = true;
|
needsImport = true;
|
||||||
importPath = "google/protobuf/timestamp.proto";
|
importPath = "google/protobuf/timestamp.proto";
|
||||||
return "google.protobuf.Timestamp";
|
return "google.protobuf.Timestamp";
|
||||||
}
|
case "TimeSpan":
|
||||||
|
|
||||||
if (fullTypeName.Contains("System.TimeSpan"))
|
|
||||||
{
|
|
||||||
needsImport = true;
|
needsImport = true;
|
||||||
importPath = "google/protobuf/duration.proto";
|
importPath = "google/protobuf/duration.proto";
|
||||||
return "google.protobuf.Duration";
|
return "google.protobuf.Duration";
|
||||||
}
|
case "JsonElement":
|
||||||
|
|
||||||
if (fullTypeName.Contains("System.Guid"))
|
|
||||||
{
|
|
||||||
// Guid serialized as string
|
|
||||||
return "string";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fullTypeName.Contains("System.Decimal") || typeName == "Decimal" || fullTypeName == "decimal")
|
|
||||||
{
|
|
||||||
// Decimal serialized as string (no native decimal in proto)
|
|
||||||
return "string";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Nullable<T> value types (e.g., int?, decimal?, enum?)
|
|
||||||
if (typeSymbol is INamedTypeSymbol nullableType &&
|
|
||||||
nullableType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T &&
|
|
||||||
nullableType.TypeArguments.Length == 1)
|
|
||||||
{
|
|
||||||
// Unwrap the nullable and map the inner type
|
|
||||||
return MapType(nullableType.TypeArguments[0], out needsImport, out importPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collections
|
|
||||||
if (typeSymbol is INamedTypeSymbol collectionType)
|
|
||||||
{
|
|
||||||
// List, IEnumerable, Array, ICollection etc. (but not Nullable<T>)
|
|
||||||
var typeName2 = collectionType.Name;
|
|
||||||
if (collectionType.TypeArguments.Length == 1 &&
|
|
||||||
(typeName2.Contains("List") || typeName2.Contains("Collection") ||
|
|
||||||
typeName2.Contains("Enumerable") || typeName2.Contains("Array") ||
|
|
||||||
typeName2.Contains("Set") || typeName2.Contains("IList") ||
|
|
||||||
typeName2.Contains("ICollection") || typeName2.Contains("IEnumerable")))
|
|
||||||
{
|
|
||||||
var elementType = collectionType.TypeArguments[0];
|
|
||||||
var protoElementType = MapType(elementType, out needsImport, out importPath);
|
|
||||||
return $"repeated {protoElementType}";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dictionary<K, V>
|
|
||||||
if (collectionType.TypeArguments.Length == 2 &&
|
|
||||||
(typeName.Contains("Dictionary") || typeName.Contains("IDictionary")))
|
|
||||||
{
|
|
||||||
var keyType = MapType(collectionType.TypeArguments[0], out var keyNeedsImport, out var keyImportPath);
|
|
||||||
var valueType = MapType(collectionType.TypeArguments[1], out var valueNeedsImport, out var valueImportPath);
|
|
||||||
|
|
||||||
// Set import flags if either key or value needs imports
|
|
||||||
if (keyNeedsImport)
|
|
||||||
{
|
|
||||||
needsImport = true;
|
needsImport = true;
|
||||||
importPath = keyImportPath;
|
importPath = "google/protobuf/struct.proto";
|
||||||
}
|
return "google.protobuf.Struct";
|
||||||
if (valueNeedsImport)
|
|
||||||
{
|
|
||||||
needsImport = true;
|
|
||||||
importPath = valueImportPath; // Note: This only captures last import, may need improvement
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"map<{keyType}, {valueType}>";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enums
|
// Enums
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user