Compare commits
5 Commits
feat/nutri
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 227be70f95 | |||
| bd43bc9bde | |||
| 661f5b4b1c | |||
| 99aebcf314 | |||
|
|
f76dbb1a97 |
@ -23,7 +23,6 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
public static IEndpointRouteBuilder MapSvrntyDynamicQueries(this IEndpointRouteBuilder endpoints, string routePrefix = "api/query")
|
public static IEndpointRouteBuilder MapSvrntyDynamicQueries(this IEndpointRouteBuilder endpoints, string routePrefix = "api/query")
|
||||||
{
|
{
|
||||||
var queryDiscovery = endpoints.ServiceProvider.GetRequiredService<IQueryDiscovery>();
|
var queryDiscovery = endpoints.ServiceProvider.GetRequiredService<IQueryDiscovery>();
|
||||||
var authorizationService = endpoints.ServiceProvider.GetService<IQueryAuthorizationService>();
|
|
||||||
|
|
||||||
foreach (var queryMeta in queryDiscovery.GetQueries())
|
foreach (var queryMeta in queryDiscovery.GetQueries())
|
||||||
{
|
{
|
||||||
@ -43,14 +42,14 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
if (dynamicQueryMeta.ParamsType == null)
|
if (dynamicQueryMeta.ParamsType == null)
|
||||||
{
|
{
|
||||||
// DynamicQuery<TSource, TDestination>
|
// DynamicQuery<TSource, TDestination>
|
||||||
MapDynamicQueryPost(endpoints, route, dynamicQueryMeta, authorizationService);
|
MapDynamicQueryPost(endpoints, route, dynamicQueryMeta);
|
||||||
MapDynamicQueryGet(endpoints, route, dynamicQueryMeta, authorizationService);
|
MapDynamicQueryGet(endpoints, route, dynamicQueryMeta);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// DynamicQuery<TSource, TDestination, TParams>
|
// DynamicQuery<TSource, TDestination, TParams>
|
||||||
MapDynamicQueryWithParamsPost(endpoints, route, dynamicQueryMeta, authorizationService);
|
MapDynamicQueryWithParamsPost(endpoints, route, dynamicQueryMeta);
|
||||||
MapDynamicQueryWithParamsGet(endpoints, route, dynamicQueryMeta, authorizationService);
|
MapDynamicQueryWithParamsGet(endpoints, route, dynamicQueryMeta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,8 +59,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
private static void MapDynamicQueryPost(
|
private static void MapDynamicQueryPost(
|
||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
DynamicQueryMeta dynamicQueryMeta,
|
DynamicQueryMeta dynamicQueryMeta)
|
||||||
IQueryAuthorizationService? authorizationService)
|
|
||||||
{
|
{
|
||||||
var sourceType = dynamicQueryMeta.SourceType;
|
var sourceType = dynamicQueryMeta.SourceType;
|
||||||
var destinationType = dynamicQueryMeta.DestinationType;
|
var destinationType = dynamicQueryMeta.DestinationType;
|
||||||
@ -75,7 +73,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
.GetMethod(nameof(MapDynamicQueryPostTyped), BindingFlags.NonPublic | BindingFlags.Static)!
|
.GetMethod(nameof(MapDynamicQueryPostTyped), BindingFlags.NonPublic | BindingFlags.Static)!
|
||||||
.MakeGenericMethod(sourceType, destinationType);
|
.MakeGenericMethod(sourceType, destinationType);
|
||||||
|
|
||||||
var endpoint = (RouteHandlerBuilder)mapPostMethod.Invoke(null, [endpoints, route, queryType, handlerType, authorizationService])!;
|
var endpoint = (RouteHandlerBuilder)mapPostMethod.Invoke(null, [endpoints, route, queryType, handlerType])!;
|
||||||
|
|
||||||
endpoint
|
endpoint
|
||||||
.WithName($"DynamicQuery_{dynamicQueryMeta.LowerCamelCaseName}_Post")
|
.WithName($"DynamicQuery_{dynamicQueryMeta.LowerCamelCaseName}_Post")
|
||||||
@ -91,8 +89,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
Type queryType,
|
Type queryType,
|
||||||
Type handlerType,
|
Type handlerType)
|
||||||
IQueryAuthorizationService? authorizationService)
|
|
||||||
where TSource : class
|
where TSource : class
|
||||||
where TDestination : class
|
where TDestination : class
|
||||||
{
|
{
|
||||||
@ -102,6 +99,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
var authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();
|
||||||
if (authorizationService != null)
|
if (authorizationService != null)
|
||||||
{
|
{
|
||||||
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
||||||
@ -129,8 +127,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
private static void MapDynamicQueryGet(
|
private static void MapDynamicQueryGet(
|
||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
DynamicQueryMeta dynamicQueryMeta,
|
DynamicQueryMeta dynamicQueryMeta)
|
||||||
IQueryAuthorizationService? authorizationService)
|
|
||||||
{
|
{
|
||||||
var sourceType = dynamicQueryMeta.SourceType;
|
var sourceType = dynamicQueryMeta.SourceType;
|
||||||
var destinationType = dynamicQueryMeta.DestinationType;
|
var destinationType = dynamicQueryMeta.DestinationType;
|
||||||
@ -141,6 +138,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
|
|
||||||
endpoints.MapGet(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
endpoints.MapGet(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
var authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();
|
||||||
if (authorizationService != null)
|
if (authorizationService != null)
|
||||||
{
|
{
|
||||||
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
||||||
@ -199,8 +197,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
private static void MapDynamicQueryWithParamsPost(
|
private static void MapDynamicQueryWithParamsPost(
|
||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
DynamicQueryMeta dynamicQueryMeta,
|
DynamicQueryMeta dynamicQueryMeta)
|
||||||
IQueryAuthorizationService? authorizationService)
|
|
||||||
{
|
{
|
||||||
var sourceType = dynamicQueryMeta.SourceType;
|
var sourceType = dynamicQueryMeta.SourceType;
|
||||||
var destinationType = dynamicQueryMeta.DestinationType;
|
var destinationType = dynamicQueryMeta.DestinationType;
|
||||||
@ -214,7 +211,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
.GetMethod(nameof(MapDynamicQueryWithParamsPostTyped), BindingFlags.NonPublic | BindingFlags.Static)!
|
.GetMethod(nameof(MapDynamicQueryWithParamsPostTyped), BindingFlags.NonPublic | BindingFlags.Static)!
|
||||||
.MakeGenericMethod(sourceType, destinationType, paramsType);
|
.MakeGenericMethod(sourceType, destinationType, paramsType);
|
||||||
|
|
||||||
var endpoint = (RouteHandlerBuilder)mapPostMethod.Invoke(null, [endpoints, route, queryType, handlerType, authorizationService])!;
|
var endpoint = (RouteHandlerBuilder)mapPostMethod.Invoke(null, [endpoints, route, queryType, handlerType])!;
|
||||||
|
|
||||||
endpoint
|
endpoint
|
||||||
.WithName($"DynamicQuery_{dynamicQueryMeta.LowerCamelCaseName}_WithParams_Post")
|
.WithName($"DynamicQuery_{dynamicQueryMeta.LowerCamelCaseName}_WithParams_Post")
|
||||||
@ -230,8 +227,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
Type queryType,
|
Type queryType,
|
||||||
Type handlerType,
|
Type handlerType)
|
||||||
IQueryAuthorizationService? authorizationService)
|
|
||||||
where TSource : class
|
where TSource : class
|
||||||
where TDestination : class
|
where TDestination : class
|
||||||
where TParams : class
|
where TParams : class
|
||||||
@ -242,6 +238,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
var authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();
|
||||||
if (authorizationService != null)
|
if (authorizationService != null)
|
||||||
{
|
{
|
||||||
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
||||||
@ -269,8 +266,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
private static void MapDynamicQueryWithParamsGet(
|
private static void MapDynamicQueryWithParamsGet(
|
||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
DynamicQueryMeta dynamicQueryMeta,
|
DynamicQueryMeta dynamicQueryMeta)
|
||||||
IQueryAuthorizationService? authorizationService)
|
|
||||||
{
|
{
|
||||||
var sourceType = dynamicQueryMeta.SourceType;
|
var sourceType = dynamicQueryMeta.SourceType;
|
||||||
var destinationType = dynamicQueryMeta.DestinationType;
|
var destinationType = dynamicQueryMeta.DestinationType;
|
||||||
@ -282,6 +278,7 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
|
|
||||||
endpoints.MapGet(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
endpoints.MapGet(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
var authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();
|
||||||
if (authorizationService != null)
|
if (authorizationService != null)
|
||||||
{
|
{
|
||||||
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
||||||
|
|||||||
@ -35,7 +35,11 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
|||||||
protected virtual Task<IQueryable<TSource>> GetQueryableAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
|
protected virtual Task<IQueryable<TSource>> GetQueryableAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (_queryableProviders.Any())
|
if (_queryableProviders.Any())
|
||||||
return _queryableProviders.ElementAt(0).GetQueryableAsync(query, cancellationToken);
|
{
|
||||||
|
// Use Last() to prefer closed generic registrations (overrides) over open generic (default)
|
||||||
|
// Registration order: open generic first, closed generic (override) last
|
||||||
|
return _queryableProviders.Last().GetQueryableAsync(query, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
throw new Exception($"You must provide a QueryableProvider<TSource> for {typeof(TSource).Name}");
|
throw new Exception($"You must provide a QueryableProvider<TSource> for {typeof(TSource).Name}");
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -49,6 +49,12 @@ namespace Svrnty.CQRS.Grpc.Generators.Helpers
|
|||||||
isRepeated = false;
|
isRepeated = false;
|
||||||
isOptional = false;
|
isOptional = false;
|
||||||
|
|
||||||
|
// Handle byte[] as bytes proto type (NOT repeated uint32)
|
||||||
|
if (csharpType == "System.Byte[]" || csharpType == "byte[]" || csharpType == "Byte[]")
|
||||||
|
{
|
||||||
|
return "bytes";
|
||||||
|
}
|
||||||
|
|
||||||
// Handle arrays
|
// Handle arrays
|
||||||
if (csharpType.EndsWith("[]"))
|
if (csharpType.EndsWith("[]"))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -44,8 +44,16 @@ 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 IsDateTimeOffset { 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 bool IsValueTypeCollection { get; set; } // Value types that implement IList<T> (like NpgsqlPolygon)
|
||||||
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 +69,15 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
|||||||
IsNullable = false;
|
IsNullable = false;
|
||||||
IsDecimal = false;
|
IsDecimal = false;
|
||||||
IsDateTime = false;
|
IsDateTime = false;
|
||||||
|
IsDateTimeOffset = false;
|
||||||
|
IsGuid = false;
|
||||||
|
IsJsonElement = false;
|
||||||
|
IsBinaryType = false;
|
||||||
|
IsStream = false;
|
||||||
|
IsReadOnly = false;
|
||||||
|
IsValueTypeCollection = false;
|
||||||
IsElementComplexType = false;
|
IsElementComplexType = false;
|
||||||
|
IsElementGuid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -320,7 +320,9 @@ internal class ProtoFileGenerator
|
|||||||
|
|
||||||
var properties = type.GetMembers()
|
var properties = type.GetMembers()
|
||||||
.OfType<IPropertySymbol>()
|
.OfType<IPropertySymbol>()
|
||||||
.Where(p => p.DeclaredAccessibility == Accessibility.Public)
|
.Where(p => p.DeclaredAccessibility == Accessibility.Public &&
|
||||||
|
!p.IsIndexer &&
|
||||||
|
!ProtoFileTypeMapper.IsCollectionInternalProperty(p.Name))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Collect nested complex types to generate after closing this message
|
// Collect nested complex types to generate after closing this message
|
||||||
@ -423,48 +425,73 @@ internal class ProtoFileGenerator
|
|||||||
_messagesBuilder.AppendLine($"// {type.Name} entity");
|
_messagesBuilder.AppendLine($"// {type.Name} entity");
|
||||||
_messagesBuilder.AppendLine($"message {type.Name} {{");
|
_messagesBuilder.AppendLine($"message {type.Name} {{");
|
||||||
|
|
||||||
var properties = type.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(p => p.DeclaredAccessibility == Accessibility.Public)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Collect nested complex types to generate after closing this message
|
// Collect nested complex types to generate after closing this message
|
||||||
var nestedComplexTypes = new List<INamedTypeSymbol>();
|
var nestedComplexTypes = new List<INamedTypeSymbol>();
|
||||||
|
|
||||||
int fieldNumber = 1;
|
// Check if this type is a collection (implements IList<T>, ICollection<T>, etc.)
|
||||||
foreach (var prop in properties)
|
var collectionElementType = ProtoFileTypeMapper.GetCollectionElementTypeByInterface(type);
|
||||||
|
if (collectionElementType != null)
|
||||||
{
|
{
|
||||||
if (ProtoFileTypeMapper.IsUnsupportedType(prop.Type))
|
// This type is a collection - generate a single repeated field for items
|
||||||
{
|
var protoElementType = ProtoFileTypeMapper.MapType(collectionElementType, out var needsImport, out var importPath);
|
||||||
_messagesBuilder.AppendLine($" // Skipped: {prop.Name} - unsupported type {prop.Type.Name}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var protoType = ProtoFileTypeMapper.MapType(prop.Type, 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);
|
_messagesBuilder.AppendLine($" repeated {protoElementType} items = 1;");
|
||||||
_messagesBuilder.AppendLine($" {protoType} {fieldName} = {fieldNumber};");
|
|
||||||
|
|
||||||
// Track enums for later generation
|
// Track the element type for nested generation
|
||||||
var enumType = ProtoFileTypeMapper.GetEnumType(prop.Type);
|
if (IsComplexType(collectionElementType) && collectionElementType is INamedTypeSymbol elementNamedType)
|
||||||
if (enumType != null)
|
|
||||||
{
|
{
|
||||||
TrackEnumType(enumType);
|
nestedComplexTypes.Add(elementNamedType);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not a collection - generate properties as usual
|
||||||
|
var properties = type.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(p => p.DeclaredAccessibility == Accessibility.Public &&
|
||||||
|
!p.IsIndexer &&
|
||||||
|
!ProtoFileTypeMapper.IsCollectionInternalProperty(p.Name))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
// Collect complex types to generate after this message is closed
|
int fieldNumber = 1;
|
||||||
// Use GetElementOrUnderlyingType to extract element type from collections
|
foreach (var prop in properties)
|
||||||
var underlyingType = ProtoFileTypeMapper.GetElementOrUnderlyingType(prop.Type);
|
|
||||||
if (IsComplexType(underlyingType) && underlyingType is INamedTypeSymbol namedType)
|
|
||||||
{
|
{
|
||||||
nestedComplexTypes.Add(namedType);
|
if (ProtoFileTypeMapper.IsUnsupportedType(prop.Type))
|
||||||
}
|
{
|
||||||
|
_messagesBuilder.AppendLine($" // Skipped: {prop.Name} - unsupported type {prop.Type.Name}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
fieldNumber++;
|
var protoType = ProtoFileTypeMapper.MapType(prop.Type, out var needsImport, out var importPath);
|
||||||
|
if (needsImport && importPath != null)
|
||||||
|
{
|
||||||
|
_requiredImports.Add(importPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldName = ProtoFileTypeMapper.ToSnakeCase(prop.Name);
|
||||||
|
_messagesBuilder.AppendLine($" {protoType} {fieldName} = {fieldNumber};");
|
||||||
|
|
||||||
|
// Track enums for later generation
|
||||||
|
var enumType = ProtoFileTypeMapper.GetEnumType(prop.Type);
|
||||||
|
if (enumType != null)
|
||||||
|
{
|
||||||
|
TrackEnumType(enumType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect complex types to generate after this message is closed
|
||||||
|
// Use GetElementOrUnderlyingType to extract element type from collections
|
||||||
|
var underlyingType = ProtoFileTypeMapper.GetElementOrUnderlyingType(prop.Type);
|
||||||
|
if (IsComplexType(underlyingType) && underlyingType is INamedTypeSymbol namedType)
|
||||||
|
{
|
||||||
|
nestedComplexTypes.Add(namedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldNumber++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_messagesBuilder.AppendLine("}");
|
_messagesBuilder.AppendLine("}");
|
||||||
@ -737,7 +764,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
|
||||||
});
|
});
|
||||||
@ -755,7 +782,9 @@ internal class ProtoFileGenerator
|
|||||||
int fieldNumber = 1;
|
int fieldNumber = 1;
|
||||||
|
|
||||||
foreach (var prop in type.GetMembers().OfType<IPropertySymbol>()
|
foreach (var prop in type.GetMembers().OfType<IPropertySymbol>()
|
||||||
.Where(p => p.DeclaredAccessibility == Accessibility.Public))
|
.Where(p => p.DeclaredAccessibility == Accessibility.Public &&
|
||||||
|
!p.IsIndexer &&
|
||||||
|
!ProtoFileTypeMapper.IsCollectionInternalProperty(p.Name)))
|
||||||
{
|
{
|
||||||
if (ProtoFileTypeMapper.IsUnsupportedType(prop.Type))
|
if (ProtoFileTypeMapper.IsUnsupportedType(prop.Type))
|
||||||
continue;
|
continue;
|
||||||
@ -817,14 +846,16 @@ 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 (needsImport && importPath != null)
|
if (typeSymbol != null)
|
||||||
{
|
{
|
||||||
_requiredImports.Add(importPath);
|
ProtoFileTypeMapper.MapType(typeSymbol, out var needsImport, out var importPath);
|
||||||
|
if (needsImport && importPath != null)
|
||||||
|
{
|
||||||
|
_requiredImports.Add(importPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var fieldName = ProtoFileTypeMapper.ToSnakeCase(prop.Name);
|
var fieldName = ProtoFileTypeMapper.ToSnakeCase(prop.Name);
|
||||||
|
|||||||
@ -20,6 +20,68 @@ 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}>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Basic types
|
||||||
switch (typeName)
|
switch (typeName)
|
||||||
{
|
{
|
||||||
@ -49,81 +111,35 @@ internal static class ProtoFileTypeMapper
|
|||||||
return "double";
|
return "double";
|
||||||
case "Byte[]":
|
case "Byte[]":
|
||||||
return "bytes";
|
return "bytes";
|
||||||
}
|
case "Stream":
|
||||||
|
case "MemoryStream":
|
||||||
// Special types that need imports
|
case "FileStream":
|
||||||
if (fullTypeName.Contains("System.DateTime"))
|
return "bytes";
|
||||||
{
|
case "Guid":
|
||||||
needsImport = true;
|
// Guid serialized as string
|
||||||
importPath = "google/protobuf/timestamp.proto";
|
return "string";
|
||||||
return "google.protobuf.Timestamp";
|
case "Decimal":
|
||||||
}
|
// Decimal serialized as string (no native decimal in proto)
|
||||||
|
return "string";
|
||||||
if (fullTypeName.Contains("System.TimeSpan"))
|
case "DateTime":
|
||||||
{
|
case "DateTimeOffset":
|
||||||
needsImport = true;
|
needsImport = true;
|
||||||
importPath = "google/protobuf/duration.proto";
|
importPath = "google/protobuf/timestamp.proto";
|
||||||
return "google.protobuf.Duration";
|
return "google.protobuf.Timestamp";
|
||||||
}
|
case "DateOnly":
|
||||||
|
// DateOnly serialized as string (YYYY-MM-DD format)
|
||||||
if (fullTypeName.Contains("System.Guid"))
|
return "string";
|
||||||
{
|
case "TimeOnly":
|
||||||
// Guid serialized as string
|
// TimeOnly serialized as string (HH:mm:ss format)
|
||||||
return "string";
|
return "string";
|
||||||
}
|
case "TimeSpan":
|
||||||
|
needsImport = true;
|
||||||
if (fullTypeName.Contains("System.Decimal") || typeName == "Decimal" || fullTypeName == "decimal")
|
importPath = "google/protobuf/duration.proto";
|
||||||
{
|
return "google.protobuf.Duration";
|
||||||
// Decimal serialized as string (no native decimal in proto)
|
case "JsonElement":
|
||||||
return "string";
|
needsImport = true;
|
||||||
}
|
importPath = "google/protobuf/struct.proto";
|
||||||
|
return "google.protobuf.Struct";
|
||||||
// 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;
|
|
||||||
importPath = keyImportPath;
|
|
||||||
}
|
|
||||||
if (valueNeedsImport)
|
|
||||||
{
|
|
||||||
needsImport = true;
|
|
||||||
importPath = valueImportPath; // Note: This only captures last import, may need improvement
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"map<{keyType}, {valueType}>";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enums
|
// Enums
|
||||||
@ -143,7 +159,10 @@ internal static class ProtoFileTypeMapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts C# PascalCase property name to proto snake_case field name
|
/// Converts C# PascalCase property name to proto snake_case field name.
|
||||||
|
/// Uses simple conversion: add underscore before each uppercase letter (except first).
|
||||||
|
/// This matches protobuf's C# codegen expectations for PascalCase conversion.
|
||||||
|
/// Example: TotalADeduire -> total_a_deduire -> TotalADeduire (in generated C#)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ToSnakeCase(string pascalCase)
|
public static string ToSnakeCase(string pascalCase)
|
||||||
{
|
{
|
||||||
@ -158,16 +177,8 @@ internal static class ProtoFileTypeMapper
|
|||||||
var c = pascalCase[i];
|
var c = pascalCase[i];
|
||||||
if (char.IsUpper(c))
|
if (char.IsUpper(c))
|
||||||
{
|
{
|
||||||
// Handle sequences of uppercase letters (e.g., "APIKey" -> "api_key")
|
result.Append('_');
|
||||||
if (i + 1 < pascalCase.Length && char.IsUpper(pascalCase[i + 1]))
|
result.Append(char.ToLowerInvariant(c));
|
||||||
{
|
|
||||||
result.Append(char.ToLowerInvariant(c));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.Append('_');
|
|
||||||
result.Append(char.ToLowerInvariant(c));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -186,8 +197,8 @@ internal static class ProtoFileTypeMapper
|
|||||||
var fullTypeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
var fullTypeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
|
||||||
// Skip these types - they should trigger a warning/error
|
// Skip these types - they should trigger a warning/error
|
||||||
if (fullTypeName.Contains("System.IO.Stream") ||
|
// Note: Stream types are now supported (mapped to bytes)
|
||||||
fullTypeName.Contains("System.Threading.CancellationToken") ||
|
if (fullTypeName.Contains("System.Threading.CancellationToken") ||
|
||||||
fullTypeName.Contains("System.Threading.Tasks.Task") ||
|
fullTypeName.Contains("System.Threading.Tasks.Task") ||
|
||||||
fullTypeName.Contains("System.Collections.Generic.IAsyncEnumerable") ||
|
fullTypeName.Contains("System.Collections.Generic.IAsyncEnumerable") ||
|
||||||
fullTypeName.Contains("System.Func") ||
|
fullTypeName.Contains("System.Func") ||
|
||||||
@ -200,6 +211,31 @@ internal static class ProtoFileTypeMapper
|
|||||||
return false;
|
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>
|
/// <summary>
|
||||||
/// Gets the element type from a collection type, or returns the type itself if not a collection.
|
/// Gets the element type from a collection type, or returns the type itself if not a collection.
|
||||||
/// Also unwraps Nullable types.
|
/// Also unwraps Nullable types.
|
||||||
@ -251,4 +287,97 @@ internal static class ProtoFileTypeMapper
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a type is a collection by checking if it implements IList{T}, ICollection{T}, or IEnumerable{T}
|
||||||
|
/// This handles types like NpgsqlPolygon that implement IList{NpgsqlPoint} but aren't named "List"
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsCollectionTypeByInterface(ITypeSymbol typeSymbol)
|
||||||
|
{
|
||||||
|
if (typeSymbol is not INamedTypeSymbol namedType)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Skip string (implements IEnumerable<char>)
|
||||||
|
if (namedType.SpecialType == SpecialType.System_String)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check all interfaces for IList<T>, ICollection<T>, or IEnumerable<T>
|
||||||
|
foreach (var iface in namedType.AllInterfaces)
|
||||||
|
{
|
||||||
|
if (iface.IsGenericType && iface.TypeArguments.Length == 1)
|
||||||
|
{
|
||||||
|
var ifaceName = iface.OriginalDefinition.ToDisplayString();
|
||||||
|
if (ifaceName == "System.Collections.Generic.IList<T>" ||
|
||||||
|
ifaceName == "System.Collections.Generic.ICollection<T>" ||
|
||||||
|
ifaceName == "System.Collections.Generic.IEnumerable<T>" ||
|
||||||
|
ifaceName == "System.Collections.Generic.IReadOnlyList<T>" ||
|
||||||
|
ifaceName == "System.Collections.Generic.IReadOnlyCollection<T>")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the element type from a collection that implements IList{T}, ICollection{T}, or IEnumerable{T}
|
||||||
|
/// Returns null if the type is not a collection
|
||||||
|
/// </summary>
|
||||||
|
public static ITypeSymbol? GetCollectionElementTypeByInterface(ITypeSymbol typeSymbol)
|
||||||
|
{
|
||||||
|
if (typeSymbol is not INamedTypeSymbol namedType)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Skip string
|
||||||
|
if (namedType.SpecialType == SpecialType.System_String)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Prefer IList<T> over ICollection<T> over IEnumerable<T>
|
||||||
|
ITypeSymbol? elementType = null;
|
||||||
|
int priority = 0;
|
||||||
|
|
||||||
|
foreach (var iface in namedType.AllInterfaces)
|
||||||
|
{
|
||||||
|
if (iface.IsGenericType && iface.TypeArguments.Length == 1)
|
||||||
|
{
|
||||||
|
var ifaceName = iface.OriginalDefinition.ToDisplayString();
|
||||||
|
int currentPriority = 0;
|
||||||
|
|
||||||
|
if (ifaceName == "System.Collections.Generic.IList<T>" ||
|
||||||
|
ifaceName == "System.Collections.Generic.IReadOnlyList<T>")
|
||||||
|
currentPriority = 3;
|
||||||
|
else if (ifaceName == "System.Collections.Generic.ICollection<T>" ||
|
||||||
|
ifaceName == "System.Collections.Generic.IReadOnlyCollection<T>")
|
||||||
|
currentPriority = 2;
|
||||||
|
else if (ifaceName == "System.Collections.Generic.IEnumerable<T>")
|
||||||
|
currentPriority = 1;
|
||||||
|
|
||||||
|
if (currentPriority > priority)
|
||||||
|
{
|
||||||
|
priority = currentPriority;
|
||||||
|
elementType = iface.TypeArguments[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elementType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Collection-internal properties that should be skipped when generating proto messages
|
||||||
|
/// </summary>
|
||||||
|
private static readonly System.Collections.Generic.HashSet<string> CollectionInternalProperties = new()
|
||||||
|
{
|
||||||
|
"Count", "Capacity", "IsReadOnly", "IsSynchronized", "SyncRoot", "Keys", "Values"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a property name is a collection-internal property that should be skipped
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsCollectionInternalProperty(string propertyName)
|
||||||
|
{
|
||||||
|
return CollectionInternalProperties.Contains(propertyName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
public static IEndpointRouteBuilder MapSvrntyQueries(this IEndpointRouteBuilder endpoints, string routePrefix = "api/query")
|
public static IEndpointRouteBuilder MapSvrntyQueries(this IEndpointRouteBuilder endpoints, string routePrefix = "api/query")
|
||||||
{
|
{
|
||||||
var queryDiscovery = endpoints.ServiceProvider.GetRequiredService<IQueryDiscovery>();
|
var queryDiscovery = endpoints.ServiceProvider.GetRequiredService<IQueryDiscovery>();
|
||||||
var authorizationService = endpoints.ServiceProvider.GetService<IQueryAuthorizationService>();
|
|
||||||
|
|
||||||
foreach (var queryMeta in queryDiscovery.GetQueries())
|
foreach (var queryMeta in queryDiscovery.GetQueries())
|
||||||
{
|
{
|
||||||
@ -33,8 +32,8 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
|
|
||||||
var route = $"{routePrefix}/{queryMeta.LowerCamelCaseName}";
|
var route = $"{routePrefix}/{queryMeta.LowerCamelCaseName}";
|
||||||
|
|
||||||
MapQueryPost(endpoints, route, queryMeta, authorizationService);
|
MapQueryPost(endpoints, route, queryMeta);
|
||||||
MapQueryGet(endpoints, route, queryMeta, authorizationService);
|
MapQueryGet(endpoints, route, queryMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoints;
|
return endpoints;
|
||||||
@ -43,13 +42,13 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
private static void MapQueryPost(
|
private static void MapQueryPost(
|
||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
IQueryMeta queryMeta,
|
IQueryMeta queryMeta)
|
||||||
IQueryAuthorizationService? authorizationService)
|
|
||||||
{
|
{
|
||||||
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryMeta.QueryType, queryMeta.QueryResultType);
|
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryMeta.QueryType, queryMeta.QueryResultType);
|
||||||
|
|
||||||
endpoints.MapPost(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
endpoints.MapPost(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
var authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();
|
||||||
if (authorizationService != null)
|
if (authorizationService != null)
|
||||||
{
|
{
|
||||||
var authorizationResult = await authorizationService.IsAllowedAsync(queryMeta.QueryType, cancellationToken);
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryMeta.QueryType, cancellationToken);
|
||||||
@ -90,13 +89,13 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
private static void MapQueryGet(
|
private static void MapQueryGet(
|
||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
IQueryMeta queryMeta,
|
IQueryMeta queryMeta)
|
||||||
IQueryAuthorizationService? authorizationService)
|
|
||||||
{
|
{
|
||||||
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryMeta.QueryType, queryMeta.QueryResultType);
|
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryMeta.QueryType, queryMeta.QueryResultType);
|
||||||
|
|
||||||
endpoints.MapGet(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
endpoints.MapGet(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
var authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();
|
||||||
if (authorizationService != null)
|
if (authorizationService != null)
|
||||||
{
|
{
|
||||||
var authorizationResult = await authorizationService.IsAllowedAsync(queryMeta.QueryType, cancellationToken);
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryMeta.QueryType, cancellationToken);
|
||||||
@ -153,7 +152,6 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
public static IEndpointRouteBuilder MapSvrntyCommands(this IEndpointRouteBuilder endpoints, string routePrefix = "api/command")
|
public static IEndpointRouteBuilder MapSvrntyCommands(this IEndpointRouteBuilder endpoints, string routePrefix = "api/command")
|
||||||
{
|
{
|
||||||
var commandDiscovery = endpoints.ServiceProvider.GetRequiredService<ICommandDiscovery>();
|
var commandDiscovery = endpoints.ServiceProvider.GetRequiredService<ICommandDiscovery>();
|
||||||
var authorizationService = endpoints.ServiceProvider.GetService<ICommandAuthorizationService>();
|
|
||||||
|
|
||||||
foreach (var commandMeta in commandDiscovery.GetCommands())
|
foreach (var commandMeta in commandDiscovery.GetCommands())
|
||||||
{
|
{
|
||||||
@ -165,11 +163,11 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
|
|
||||||
if (commandMeta.CommandResultType == null)
|
if (commandMeta.CommandResultType == null)
|
||||||
{
|
{
|
||||||
MapCommandWithoutResult(endpoints, route, commandMeta, authorizationService);
|
MapCommandWithoutResult(endpoints, route, commandMeta);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MapCommandWithResult(endpoints, route, commandMeta, authorizationService);
|
MapCommandWithResult(endpoints, route, commandMeta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,13 +177,13 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
private static void MapCommandWithoutResult(
|
private static void MapCommandWithoutResult(
|
||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
ICommandMeta commandMeta,
|
ICommandMeta commandMeta)
|
||||||
ICommandAuthorizationService? authorizationService)
|
|
||||||
{
|
{
|
||||||
var handlerType = typeof(ICommandHandler<>).MakeGenericType(commandMeta.CommandType);
|
var handlerType = typeof(ICommandHandler<>).MakeGenericType(commandMeta.CommandType);
|
||||||
|
|
||||||
endpoints.MapPost(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
endpoints.MapPost(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
var authorizationService = serviceProvider.GetService<ICommandAuthorizationService>();
|
||||||
if (authorizationService != null)
|
if (authorizationService != null)
|
||||||
{
|
{
|
||||||
var authorizationResult = await authorizationService.IsAllowedAsync(commandMeta.CommandType, cancellationToken);
|
var authorizationResult = await authorizationService.IsAllowedAsync(commandMeta.CommandType, cancellationToken);
|
||||||
@ -221,13 +219,13 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
private static void MapCommandWithResult(
|
private static void MapCommandWithResult(
|
||||||
IEndpointRouteBuilder endpoints,
|
IEndpointRouteBuilder endpoints,
|
||||||
string route,
|
string route,
|
||||||
ICommandMeta commandMeta,
|
ICommandMeta commandMeta)
|
||||||
ICommandAuthorizationService? authorizationService)
|
|
||||||
{
|
{
|
||||||
var handlerType = typeof(ICommandHandler<,>).MakeGenericType(commandMeta.CommandType, commandMeta.CommandResultType!);
|
var handlerType = typeof(ICommandHandler<,>).MakeGenericType(commandMeta.CommandType, commandMeta.CommandResultType!);
|
||||||
|
|
||||||
endpoints.MapPost(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
endpoints.MapPost(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
var authorizationService = serviceProvider.GetService<ICommandAuthorizationService>();
|
||||||
if (authorizationService != null)
|
if (authorizationService != null)
|
||||||
{
|
{
|
||||||
var authorizationResult = await authorizationService.IsAllowedAsync(commandMeta.CommandType, cancellationToken);
|
var authorizationResult = await authorizationService.IsAllowedAsync(commandMeta.CommandType, cancellationToken);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user