Fix gRPC source generator for complex nested types
- Add DateTime/Timestamp conversion in nested property mapping - Add IsReadOnly property detection to skip computed properties - Extract ElementNestedProperties for complex list element types - Skip read-only properties in GenerateComplexObjectMapping and GenerateComplexListMapping 🤖 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
661f5b4b1c
commit
bd43bc9bde
@ -363,6 +363,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
IsGuid = IsGuidType(property.Type),
|
||||
IsJsonElement = IsJsonElementType(property.Type),
|
||||
IsList = IsListOrCollection(property.Type),
|
||||
IsBinaryType = IsBinaryType(property.Type),
|
||||
IsStream = IsStreamType(property.Type),
|
||||
};
|
||||
|
||||
// If it's a list, extract element type info
|
||||
@ -482,6 +484,24 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
return unwrapped.ToDisplayString() == "System.Text.Json.JsonElement";
|
||||
}
|
||||
|
||||
private static bool IsBinaryType(ITypeSymbol type)
|
||||
{
|
||||
return ProtoFileTypeMapper.IsBinaryType(type);
|
||||
}
|
||||
|
||||
private static bool IsStreamType(ITypeSymbol type)
|
||||
{
|
||||
var fullTypeName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
if (fullTypeName.Contains("System.IO.Stream") ||
|
||||
fullTypeName.Contains("System.IO.MemoryStream") ||
|
||||
fullTypeName.Contains("System.IO.FileStream"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var typeName = type.Name;
|
||||
return typeName == "Stream" || typeName == "MemoryStream" || typeName == "FileStream";
|
||||
}
|
||||
|
||||
private static bool IsListOrCollection(ITypeSymbol type)
|
||||
{
|
||||
if (type is IArrayTypeSymbol)
|
||||
@ -582,6 +602,9 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
// Skip read-only properties (no setter) - they are computed and can't be set
|
||||
var isReadOnly = property.IsReadOnly || property.SetMethod == null;
|
||||
|
||||
var propertyType = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
var propInfo = new PropertyInfo
|
||||
{
|
||||
@ -591,6 +614,7 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
ProtoType = string.Empty,
|
||||
FieldNumber = 0,
|
||||
IsComplexType = IsUserDefinedComplexType(property.Type),
|
||||
IsReadOnly = isReadOnly,
|
||||
// Type metadata
|
||||
IsNullable = IsNullableType(property.Type),
|
||||
IsEnum = IsEnumType(property.Type),
|
||||
@ -599,6 +623,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
IsGuid = IsGuidType(property.Type),
|
||||
IsJsonElement = IsJsonElementType(property.Type),
|
||||
IsList = IsListOrCollection(property.Type),
|
||||
IsBinaryType = IsBinaryType(property.Type),
|
||||
IsStream = IsStreamType(property.Type),
|
||||
};
|
||||
|
||||
// If it's a list, extract element type info
|
||||
@ -610,6 +636,13 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
propInfo.ElementType = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
propInfo.IsElementComplexType = IsUserDefinedComplexType(elementType);
|
||||
propInfo.IsElementGuid = IsGuidType(elementType);
|
||||
|
||||
// Extract nested properties for complex element types
|
||||
if (propInfo.IsElementComplexType && elementType is INamedTypeSymbol namedElementType)
|
||||
{
|
||||
propInfo.ElementNestedProperties = new List<PropertyInfo>();
|
||||
ExtractNestedPropertiesWithTypeInfo(namedElementType, propInfo.ElementNestedProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Recursively extract nested properties for complex types
|
||||
@ -716,6 +749,29 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
}
|
||||
}
|
||||
|
||||
// Handle binary types (proto ByteString -> C# byte[]/Stream)
|
||||
if (prop.IsBinaryType)
|
||||
{
|
||||
if (prop.IsStream)
|
||||
{
|
||||
// ByteString -> MemoryStream
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source}?.IsEmpty == false ? new System.IO.MemoryStream({source}.ToByteArray()) : null,";
|
||||
}
|
||||
return $"{indent}{prop.Name} = new System.IO.MemoryStream({source}.ToByteArray()),";
|
||||
}
|
||||
else
|
||||
{
|
||||
// ByteString -> byte[]
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source}?.IsEmpty == false ? {source}.ToByteArray() : null,";
|
||||
}
|
||||
return $"{indent}{prop.Name} = {source}.ToByteArray(),";
|
||||
}
|
||||
}
|
||||
|
||||
// Handle complex types (single objects)
|
||||
if (prop.IsComplexType)
|
||||
{
|
||||
@ -734,6 +790,9 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
|
||||
foreach (var nestedProp in prop.ElementNestedProperties!)
|
||||
{
|
||||
// Skip read-only properties - they can't be assigned
|
||||
if (nestedProp.IsReadOnly) continue;
|
||||
|
||||
var nestedSourcePropName = char.ToUpper(nestedProp.Name[0]) + nestedProp.Name.Substring(1);
|
||||
var nestedAssignment = GenerateNestedPropertyAssignment(nestedProp, "x", indent + " ");
|
||||
sb.AppendLine(nestedAssignment);
|
||||
@ -751,6 +810,9 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
|
||||
foreach (var nestedProp in prop.NestedProperties)
|
||||
{
|
||||
// Skip read-only properties - they can't be assigned
|
||||
if (nestedProp.IsReadOnly) continue;
|
||||
|
||||
var nestedAssignment = GenerateNestedPropertyAssignment(nestedProp, source, indent + " ");
|
||||
sb.AppendLine(nestedAssignment);
|
||||
}
|
||||
@ -783,6 +845,19 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
}
|
||||
}
|
||||
|
||||
// Handle DateTime (proto Timestamp -> C# DateTime)
|
||||
if (prop.IsDateTime)
|
||||
{
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source} == null ? (System.DateTime?)null : {source}.ToDateTime(),";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source}.ToDateTime(),";
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Guid
|
||||
if (prop.IsGuid)
|
||||
{
|
||||
@ -796,9 +871,13 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
}
|
||||
}
|
||||
|
||||
// Handle lists
|
||||
// Handle lists with complex element types
|
||||
if (prop.IsList)
|
||||
{
|
||||
if (prop.IsElementComplexType && prop.ElementNestedProperties != null && prop.ElementNestedProperties.Any())
|
||||
{
|
||||
return GenerateComplexListMapping(prop, source, indent);
|
||||
}
|
||||
return $"{indent}{prop.Name} = {source}?.ToList(),";
|
||||
}
|
||||
|
||||
@ -897,6 +976,29 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
return $"{indent}{prop.Name} = (int){source},";
|
||||
}
|
||||
|
||||
// Handle binary types (byte[], Stream -> ByteString)
|
||||
if (prop.IsBinaryType)
|
||||
{
|
||||
if (prop.IsStream)
|
||||
{
|
||||
// Stream -> ByteString: read stream to bytes first
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source} != null ? Google.Protobuf.ByteString.FromStream({source}) : Google.Protobuf.ByteString.Empty,";
|
||||
}
|
||||
return $"{indent}{prop.Name} = Google.Protobuf.ByteString.FromStream({source}),";
|
||||
}
|
||||
else
|
||||
{
|
||||
// byte[] -> ByteString
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source} != null ? Google.Protobuf.ByteString.CopyFrom({source}) : Google.Protobuf.ByteString.Empty,";
|
||||
}
|
||||
return $"{indent}{prop.Name} = Google.Protobuf.ByteString.CopyFrom({source}),";
|
||||
}
|
||||
}
|
||||
|
||||
// Handle complex types (single objects)
|
||||
if (prop.IsComplexType)
|
||||
{
|
||||
@ -959,6 +1061,32 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
{
|
||||
var source = $"{sourceVar}.{prop.Name}";
|
||||
|
||||
// Handle DateTime (C# DateTime -> proto Timestamp)
|
||||
if (prop.IsDateTime)
|
||||
{
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source} != null ? 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 DateOnly (C# DateOnly -> proto string)
|
||||
if (prop.Type.Contains("DateOnly"))
|
||||
{
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source}?.ToString(\"yyyy-MM-dd\") ?? string.Empty,";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source}.ToString(\"yyyy-MM-dd\"),";
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Guid
|
||||
if (prop.IsGuid)
|
||||
{
|
||||
@ -988,9 +1116,34 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
// Handle enums
|
||||
if (prop.IsEnum)
|
||||
{
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = (int)({source} ?? default),";
|
||||
}
|
||||
return $"{indent}{prop.Name} = (int){source},";
|
||||
}
|
||||
|
||||
// Handle binary types (C# byte[]/Stream -> proto ByteString)
|
||||
if (prop.IsBinaryType)
|
||||
{
|
||||
if (prop.IsStream)
|
||||
{
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source} != null ? Google.Protobuf.ByteString.FromStream({source}) : Google.Protobuf.ByteString.Empty,";
|
||||
}
|
||||
return $"{indent}{prop.Name} = Google.Protobuf.ByteString.FromStream({source}),";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source} != null ? Google.Protobuf.ByteString.CopyFrom({source}) : Google.Protobuf.ByteString.Empty,";
|
||||
}
|
||||
return $"{indent}{prop.Name} = Google.Protobuf.ByteString.CopyFrom({source}),";
|
||||
}
|
||||
}
|
||||
|
||||
// Handle lists
|
||||
if (prop.IsList)
|
||||
{
|
||||
@ -1000,10 +1153,18 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
}
|
||||
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}>() }},";
|
||||
// Complex list elements need mapping
|
||||
if (prop.ElementNestedProperties != null && prop.ElementNestedProperties.Any())
|
||||
{
|
||||
// Use recursive mapping for nested properties
|
||||
return GenerateResultComplexListMapping(prop, source, indent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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} }},";
|
||||
}
|
||||
@ -1012,7 +1173,12 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
if (prop.IsComplexType)
|
||||
{
|
||||
var typeName = prop.Type.Split('.').Last().Replace("?", "");
|
||||
if (prop.IsNullable || prop.Type.EndsWith("?"))
|
||||
if (prop.NestedProperties != null && prop.NestedProperties.Any())
|
||||
{
|
||||
// Use recursive mapping for nested properties
|
||||
return GenerateResultComplexObjectMapping(prop, source, indent);
|
||||
}
|
||||
else if (prop.IsNullable || prop.Type.EndsWith("?"))
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source} != null ? new {typeName}() : null,";
|
||||
}
|
||||
@ -1028,6 +1194,12 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
return $"{indent}{prop.Name} = {source} ?? string.Empty,";
|
||||
}
|
||||
|
||||
// Handle nullable value types (int?, long?, double?, etc.)
|
||||
if (prop.IsNullable)
|
||||
{
|
||||
return $"{indent}{prop.Name} = {source} ?? default,";
|
||||
}
|
||||
|
||||
// Default: direct assignment
|
||||
return $"{indent}{prop.Name} = {source},";
|
||||
}
|
||||
@ -1079,6 +1251,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
IsJsonElement = IsJsonElementType(property.Type),
|
||||
IsList = IsListOrCollection(property.Type),
|
||||
IsComplexType = IsUserDefinedComplexType(property.Type),
|
||||
IsBinaryType = IsBinaryType(property.Type),
|
||||
IsStream = IsStreamType(property.Type),
|
||||
};
|
||||
|
||||
// If it's a list, extract element type info
|
||||
@ -1090,6 +1264,13 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
propInfo.ElementType = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
propInfo.IsElementComplexType = IsUserDefinedComplexType(elementType);
|
||||
propInfo.IsElementGuid = IsGuidType(elementType);
|
||||
|
||||
// Extract nested properties for complex element types
|
||||
if (propInfo.IsElementComplexType && elementType is INamedTypeSymbol namedElementType)
|
||||
{
|
||||
propInfo.ElementNestedProperties = new List<PropertyInfo>();
|
||||
ExtractNestedPropertiesWithTypeInfo(namedElementType, propInfo.ElementNestedProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If it's a complex type (not list), extract nested properties
|
||||
@ -1133,6 +1314,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
IsJsonElement = IsJsonElementType(property.Type),
|
||||
IsList = IsListOrCollection(property.Type),
|
||||
IsComplexType = IsUserDefinedComplexType(property.Type),
|
||||
IsBinaryType = IsBinaryType(property.Type),
|
||||
IsStream = IsStreamType(property.Type),
|
||||
};
|
||||
|
||||
// If it's a list, extract element type info
|
||||
@ -2528,7 +2711,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
sb.AppendLine(" var domainElementType = domainProp.PropertyType.IsArray");
|
||||
sb.AppendLine(" ? domainProp.PropertyType.GetElementType()");
|
||||
sb.AppendLine(" : domainProp.PropertyType.IsGenericType ? domainProp.PropertyType.GetGenericArguments()[0] : null;");
|
||||
sb.AppendLine(" var protoElementType = protoField.MessageType?.ClrType;");
|
||||
sb.AppendLine(" // Only access MessageType for message fields (throws for primitives)");
|
||||
sb.AppendLine(" var protoElementType = protoField.FieldType == Google.Protobuf.Reflection.FieldType.Message ? protoField.MessageType?.ClrType : null;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" foreach (var item in enumerable)");
|
||||
sb.AppendLine(" {");
|
||||
@ -2552,14 +2736,66 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" // Handle enumerable value types that map to proto messages with repeated fields (e.g., NpgsqlPolygon -> proto message with items)");
|
||||
sb.AppendLine(" else if (domainProp.PropertyType.IsValueType && ");
|
||||
sb.AppendLine(" domainValue is System.Collections.IEnumerable valueTypeEnumerable &&");
|
||||
sb.AppendLine(" protoField.FieldType == Google.Protobuf.Reflection.FieldType.Message)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" // Create the proto message and look for its 'items' repeated field");
|
||||
sb.AppendLine(" var protoFieldType = protoField.MessageType?.ClrType;");
|
||||
sb.AppendLine(" if (protoFieldType != null && typeof(Google.Protobuf.IMessage).IsAssignableFrom(protoFieldType))");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var nestedProto = System.Activator.CreateInstance(protoFieldType) as Google.Protobuf.IMessage;");
|
||||
sb.AppendLine(" if (nestedProto != null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" // Find the 'items' field in the proto message");
|
||||
sb.AppendLine(" var itemsField = nestedProto.Descriptor.FindFieldByName(\"items\");");
|
||||
sb.AppendLine(" if (itemsField != null && itemsField.IsRepeated)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var repeatedField = itemsField.Accessor.GetValue(nestedProto);");
|
||||
sb.AppendLine(" var repeatedFieldType = repeatedField?.GetType();");
|
||||
sb.AppendLine(" var protoElementType = itemsField.MessageType?.ClrType;");
|
||||
sb.AppendLine(" ");
|
||||
sb.AppendLine(" if (repeatedFieldType != null && protoElementType != null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var addMethod = repeatedFieldType.GetMethod(\"Add\", new[] { protoElementType });");
|
||||
sb.AppendLine(" if (addMethod != null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" // Get element type from the enumerable");
|
||||
sb.AppendLine(" var enumerableInterface = domainProp.PropertyType.GetInterfaces()");
|
||||
sb.AppendLine(" .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IEnumerable<>));");
|
||||
sb.AppendLine(" var domainElementType = enumerableInterface?.GetGenericArguments()[0];");
|
||||
sb.AppendLine(" ");
|
||||
sb.AppendLine(" foreach (var item in valueTypeEnumerable)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (item == null) continue;");
|
||||
sb.AppendLine(" ");
|
||||
sb.AppendLine(" // Map each item to the proto element type");
|
||||
sb.AppendLine(" if (domainElementType != null && typeof(Google.Protobuf.IMessage).IsAssignableFrom(protoElementType))");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var mapMethod = typeof(DynamicQueryServiceImpl).GetMethod(\"MapToProtoModel\",");
|
||||
sb.AppendLine(" System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!");
|
||||
sb.AppendLine(" .MakeGenericMethod(domainElementType, protoElementType);");
|
||||
sb.AppendLine(" var mappedItem = mapMethod.Invoke(null, new[] { item });");
|
||||
sb.AppendLine(" if (mappedItem != null)");
|
||||
sb.AppendLine(" addMethod.Invoke(repeatedField, new[] { mappedItem });");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" protoAccessor.SetValue(proto, nestedProto);");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" // Handle nested complex types (non-primitive, non-enum, non-string, non-collection)");
|
||||
sb.AppendLine(" else if (!domainProp.PropertyType.IsPrimitive && ");
|
||||
sb.AppendLine(" domainProp.PropertyType != typeof(string) && ");
|
||||
sb.AppendLine(" !domainProp.PropertyType.IsEnum &&");
|
||||
sb.AppendLine(" !domainProp.PropertyType.IsValueType)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" // Get the proto field type and recursively map");
|
||||
sb.AppendLine(" var protoFieldType = protoAccessor.GetValue(proto)?.GetType() ?? protoField.MessageType?.ClrType;");
|
||||
sb.AppendLine(" // Get the proto field type and recursively map (only access MessageType for message fields)");
|
||||
sb.AppendLine(" var protoFieldType = protoAccessor.GetValue(proto)?.GetType() ?? (protoField.FieldType == Google.Protobuf.Reflection.FieldType.Message ? protoField.MessageType?.ClrType : null);");
|
||||
sb.AppendLine(" if (protoFieldType != null && typeof(Google.Protobuf.IMessage).IsAssignableFrom(protoFieldType))");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var mapMethod = typeof(DynamicQueryServiceImpl).GetMethod(\"MapToProtoModel\", ");
|
||||
|
||||
@ -46,6 +46,9 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
public bool IsDateTime { get; set; }
|
||||
public bool IsGuid { get; set; }
|
||||
public bool IsJsonElement { get; set; }
|
||||
public bool IsBinaryType { get; set; } // Stream, byte[], MemoryStream
|
||||
public bool IsStream { get; set; } // Specifically Stream types (not byte[])
|
||||
public bool IsReadOnly { get; set; } // Read-only/computed properties should be skipped
|
||||
public string? ElementType { get; set; }
|
||||
public bool IsElementComplexType { get; set; }
|
||||
public bool IsElementGuid { get; set; }
|
||||
@ -66,6 +69,9 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
IsDateTime = false;
|
||||
IsGuid = false;
|
||||
IsJsonElement = false;
|
||||
IsBinaryType = false;
|
||||
IsStream = false;
|
||||
IsReadOnly = false;
|
||||
IsElementComplexType = false;
|
||||
IsElementGuid = false;
|
||||
}
|
||||
|
||||
@ -68,6 +68,20 @@ internal static class ProtoFileTypeMapper
|
||||
}
|
||||
}
|
||||
|
||||
// Handle byte[] array type (check before switch since it's an array)
|
||||
if (typeSymbol is IArrayTypeSymbol arrayType && arrayType.ElementType.SpecialType == SpecialType.System_Byte)
|
||||
{
|
||||
return "bytes";
|
||||
}
|
||||
|
||||
// Handle Stream types -> bytes
|
||||
if (fullTypeName.Contains("System.IO.Stream") ||
|
||||
fullTypeName.Contains("System.IO.MemoryStream") ||
|
||||
fullTypeName.Contains("System.IO.FileStream"))
|
||||
{
|
||||
return "bytes";
|
||||
}
|
||||
|
||||
// Basic types
|
||||
switch (typeName)
|
||||
{
|
||||
@ -97,6 +111,10 @@ internal static class ProtoFileTypeMapper
|
||||
return "double";
|
||||
case "Byte[]":
|
||||
return "bytes";
|
||||
case "Stream":
|
||||
case "MemoryStream":
|
||||
case "FileStream":
|
||||
return "bytes";
|
||||
case "Guid":
|
||||
// Guid serialized as string
|
||||
return "string";
|
||||
@ -184,8 +202,8 @@ internal static class ProtoFileTypeMapper
|
||||
var fullTypeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
|
||||
// Skip these types - they should trigger a warning/error
|
||||
if (fullTypeName.Contains("System.IO.Stream") ||
|
||||
fullTypeName.Contains("System.Threading.CancellationToken") ||
|
||||
// Note: Stream types are now supported (mapped to bytes)
|
||||
if (fullTypeName.Contains("System.Threading.CancellationToken") ||
|
||||
fullTypeName.Contains("System.Threading.Tasks.Task") ||
|
||||
fullTypeName.Contains("System.Collections.Generic.IAsyncEnumerable") ||
|
||||
fullTypeName.Contains("System.Func") ||
|
||||
@ -198,6 +216,31 @@ internal static class ProtoFileTypeMapper
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a type is a Stream or byte array type (for special ByteString handling)
|
||||
/// </summary>
|
||||
public static bool IsBinaryType(ITypeSymbol typeSymbol)
|
||||
{
|
||||
var fullTypeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
|
||||
// Check for byte[]
|
||||
if (typeSymbol is IArrayTypeSymbol arrayType && arrayType.ElementType.SpecialType == SpecialType.System_Byte)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for Stream types
|
||||
if (fullTypeName.Contains("System.IO.Stream") ||
|
||||
fullTypeName.Contains("System.IO.MemoryStream") ||
|
||||
fullTypeName.Contains("System.IO.FileStream"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var typeName = typeSymbol.Name;
|
||||
return typeName == "Stream" || typeName == "MemoryStream" || typeName == "FileStream";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element type from a collection type, or returns the type itself if not a collection.
|
||||
/// Also unwraps Nullable types.
|
||||
|
||||
@ -1,111 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "Svrnty.Sample.Grpc";
|
||||
option csharp_namespace = "Generated.Grpc";
|
||||
|
||||
package cqrs;
|
||||
|
||||
// Command service for CQRS operations
|
||||
service CommandService {
|
||||
// AddUserCommand operation
|
||||
rpc AddUser (AddUserCommandRequest) returns (AddUserCommandResponse);
|
||||
|
||||
// RemoveUserCommand operation
|
||||
rpc RemoveUser (RemoveUserCommandRequest) returns (RemoveUserCommandResponse);
|
||||
|
||||
}
|
||||
|
||||
// Query service for CQRS operations
|
||||
service QueryService {
|
||||
// FetchUserQuery operation
|
||||
rpc FetchUser (FetchUserQueryRequest) returns (FetchUserQueryResponse);
|
||||
|
||||
}
|
||||
|
||||
// DynamicQuery service for CQRS operations
|
||||
service DynamicQueryService {
|
||||
// Dynamic query for User
|
||||
rpc QueryUsers (DynamicQueryUsersRequest) returns (DynamicQueryUsersResponse);
|
||||
|
||||
}
|
||||
|
||||
// Request message for AddUserCommand
|
||||
message AddUserCommandRequest {
|
||||
string name = 1;
|
||||
string email = 2;
|
||||
int32 age = 3;
|
||||
}
|
||||
|
||||
// Response message for AddUserCommand
|
||||
message AddUserCommandResponse {
|
||||
int32 result = 1;
|
||||
}
|
||||
|
||||
// Request message for RemoveUserCommand
|
||||
message RemoveUserCommandRequest {
|
||||
int32 user_id = 1;
|
||||
}
|
||||
|
||||
// Response message for RemoveUserCommand
|
||||
message RemoveUserCommandResponse {
|
||||
}
|
||||
|
||||
// Request message for FetchUserQuery
|
||||
message FetchUserQueryRequest {
|
||||
int32 user_id = 1;
|
||||
}
|
||||
|
||||
// Response message for FetchUserQuery
|
||||
message FetchUserQueryResponse {
|
||||
User result = 1;
|
||||
}
|
||||
|
||||
// User entity
|
||||
message User {
|
||||
int32 id = 1;
|
||||
string name = 2;
|
||||
string email = 3;
|
||||
}
|
||||
|
||||
// Dynamic query filter with AND/OR support
|
||||
message DynamicQueryFilter {
|
||||
string path = 1;
|
||||
int32 type = 2; // PoweredSoft.DynamicQuery.Core.FilterType
|
||||
string value = 3;
|
||||
repeated DynamicQueryFilter and = 4;
|
||||
repeated DynamicQueryFilter or = 5;
|
||||
}
|
||||
|
||||
// Dynamic query sort
|
||||
message DynamicQuerySort {
|
||||
string path = 1;
|
||||
bool ascending = 2;
|
||||
}
|
||||
|
||||
// Dynamic query group
|
||||
message DynamicQueryGroup {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
// Dynamic query aggregate
|
||||
message DynamicQueryAggregate {
|
||||
string path = 1;
|
||||
int32 type = 2; // PoweredSoft.DynamicQuery.Core.AggregateType
|
||||
}
|
||||
|
||||
// Dynamic query request for User
|
||||
message DynamicQueryUsersRequest {
|
||||
int32 page = 1;
|
||||
int32 page_size = 2;
|
||||
repeated DynamicQueryFilter filters = 3;
|
||||
repeated DynamicQuerySort sorts = 4;
|
||||
repeated DynamicQueryGroup groups = 5;
|
||||
repeated DynamicQueryAggregate aggregates = 6;
|
||||
}
|
||||
|
||||
// Dynamic query response for User
|
||||
message DynamicQueryUsersResponse {
|
||||
repeated User data = 1;
|
||||
int64 total_records = 2;
|
||||
int32 number_of_pages = 3;
|
||||
}
|
||||
|
||||
// Placeholder proto file - will be regenerated on next build
|
||||
|
||||
Loading…
Reference in New Issue
Block a user