Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Support shared Behaviors list
  • Loading branch information
viceroypenguin committed May 20, 2025
commit 788b4cfdc1e51f5cc8e1d2ea00c4580c0a442d4b
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,25 @@ private static void AddBaseTypes(ITypeSymbol type, List<string> implements)

file static class Extensions
{
public static AttributeData? GetBehaviorsAttribute(this INamedTypeSymbol symbol) =>
symbol
.GetAttributes()
.FirstOrDefault(a => a.AttributeClass.IsBehaviorsAttribute());
public static AttributeData? GetBehaviorsAttribute(this INamedTypeSymbol symbol)
{
foreach (var a in symbol.GetAttributes())
{
if (a.AttributeClass is null)
continue;

if (a.AttributeClass.IsBehaviorsAttribute())
return a;

foreach (var aa in a.AttributeClass.GetAttributes())
{
if (aa.AttributeClass.IsBehaviorsAttribute())
return aa;
}
}

return null;
}

public static string? GetAttributesString(this IParameterSymbol parameter)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//HintName: IH.Dummy.GetUsersQuery.g.cs
using Microsoft.Extensions.DependencyInjection;

#pragma warning disable CS1591

namespace Dummy;

partial class GetUsersQuery
{
public sealed partial class Handler : global::Immediate.Handlers.Shared.IHandler<global::Dummy.GetUsersQuery.Query, global::System.Collections.Generic.IEnumerable<global::Dummy.User>>
{
private readonly global::Dummy.GetUsersQuery.HandleBehavior _handleBehavior;
private readonly global::Dummy.LoggingBehavior<global::Dummy.GetUsersQuery.Query, global::System.Collections.Generic.IEnumerable<global::Dummy.User>> _loggingBehavior;

public Handler(
global::Dummy.GetUsersQuery.HandleBehavior handleBehavior,
global::Dummy.LoggingBehavior<global::Dummy.GetUsersQuery.Query, global::System.Collections.Generic.IEnumerable<global::Dummy.User>> loggingBehavior
)
{
var handlerType = typeof(GetUsersQuery);

_handleBehavior = handleBehavior;

_loggingBehavior = loggingBehavior;
_loggingBehavior.HandlerType = handlerType;

_loggingBehavior.SetInnerHandler(_handleBehavior);
}

public async global::System.Threading.Tasks.ValueTask<global::System.Collections.Generic.IEnumerable<global::Dummy.User>> HandleAsync(
global::Dummy.GetUsersQuery.Query request,
global::System.Threading.CancellationToken cancellationToken = default
)
{
return await _loggingBehavior
.HandleAsync(request, cancellationToken)
.ConfigureAwait(false);
}
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class HandleBehavior : global::Immediate.Handlers.Shared.Behavior<global::Dummy.GetUsersQuery.Query, global::System.Collections.Generic.IEnumerable<global::Dummy.User>>
{
private readonly global::Dummy.UsersService _usersService;

public HandleBehavior(
global::Dummy.UsersService usersService
)
{
_usersService = usersService;
}

public override async global::System.Threading.Tasks.ValueTask<global::System.Collections.Generic.IEnumerable<global::Dummy.User>> HandleAsync(
global::Dummy.GetUsersQuery.Query request,
global::System.Threading.CancellationToken cancellationToken
)
{
return await global::Dummy.GetUsersQuery
.HandleAsync(
request
, _usersService
, cancellationToken
)
.ConfigureAwait(false);
}
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public static IServiceCollection AddHandlers(
IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped
)
{
services.Add(new(typeof(global::Dummy.GetUsersQuery.Handler), typeof(global::Dummy.GetUsersQuery.Handler), lifetime));
services.Add(new(typeof(global::Immediate.Handlers.Shared.IHandler<global::Dummy.GetUsersQuery.Query, global::System.Collections.Generic.IEnumerable<global::Dummy.User>>), typeof(global::Dummy.GetUsersQuery.Handler), lifetime));
services.Add(new(typeof(global::Dummy.GetUsersQuery.HandleBehavior), typeof(global::Dummy.GetUsersQuery.HandleBehavior), lifetime));
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//HintName: IH.ServiceCollectionExtensions.g.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

#pragma warning disable CS1591

public static class HandlerServiceCollectionExtensions
{
public static IServiceCollection AddTestsBehaviors(
this IServiceCollection services)
{
services.TryAddTransient(typeof(global::Dummy.LoggingBehavior<,>));

return services;
}

public static IServiceCollection AddTestsHandlers(
this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped
)
{
global::Dummy.GetUsersQuery.AddHandlers(services, lifetime);

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//HintName: IH.Dummy.GetUsersQuery.g.cs
#pragma warning disable CS1591

namespace Dummy;

partial class GetUsersQuery
{
public sealed partial class Handler : global::Immediate.Handlers.Shared.IHandler<global::Dummy.GetUsersQuery.Query, global::System.Collections.Generic.IEnumerable<global::Dummy.User>>
{
private readonly global::Dummy.GetUsersQuery.HandleBehavior _handleBehavior;
private readonly global::Dummy.LoggingBehavior<global::Dummy.GetUsersQuery.Query, global::System.Collections.Generic.IEnumerable<global::Dummy.User>> _loggingBehavior;

public Handler(
global::Dummy.GetUsersQuery.HandleBehavior handleBehavior,
global::Dummy.LoggingBehavior<global::Dummy.GetUsersQuery.Query, global::System.Collections.Generic.IEnumerable<global::Dummy.User>> loggingBehavior
)
{
var handlerType = typeof(GetUsersQuery);

_handleBehavior = handleBehavior;

_loggingBehavior = loggingBehavior;
_loggingBehavior.HandlerType = handlerType;

_loggingBehavior.SetInnerHandler(_handleBehavior);
}

public async global::System.Threading.Tasks.ValueTask<global::System.Collections.Generic.IEnumerable<global::Dummy.User>> HandleAsync(
global::Dummy.GetUsersQuery.Query request,
global::System.Threading.CancellationToken cancellationToken = default
)
{
return await _loggingBehavior
.HandleAsync(request, cancellationToken)
.ConfigureAwait(false);
}
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class HandleBehavior : global::Immediate.Handlers.Shared.Behavior<global::Dummy.GetUsersQuery.Query, global::System.Collections.Generic.IEnumerable<global::Dummy.User>>
{
private readonly global::Dummy.UsersService _usersService;

public HandleBehavior(
global::Dummy.UsersService usersService
)
{
_usersService = usersService;
}

public override async global::System.Threading.Tasks.ValueTask<global::System.Collections.Generic.IEnumerable<global::Dummy.User>> HandleAsync(
global::Dummy.GetUsersQuery.Query request,
global::System.Threading.CancellationToken cancellationToken
)
{
return await global::Dummy.GetUsersQuery
.HandleAsync(
request
, _usersService
, cancellationToken
)
.ConfigureAwait(false);
}
}
}
88 changes: 88 additions & 0 deletions tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,92 @@ CancellationToken __
_ = await Verify(result)
.UseParameters(string.Join('_', assemblies));
}

[Test]
[Arguments(DriverReferenceAssemblies.Normal)]
[Arguments(DriverReferenceAssemblies.Msdi)]
public async Task NestedBehavior(DriverReferenceAssemblies assemblies)
{
var result = GeneratorTestHelper.RunGenerator(
"""
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dummy;
using Immediate.Handlers.Shared;

#pragma warning disable CS9113

namespace Dummy;

[Behaviors(
typeof(LoggingBehavior<,>)
)]
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
public sealed class DefaultBehaviorsAttribute : Attribute;

public class GetUsersEndpoint(GetUsersQuery.Handler handler)
{
public ValueTask<IEnumerable<User>> GetUsers() =>
handler.HandleAsync(new GetUsersQuery.Query());
}

[Handler]
[DefaultBehaviors]
public static partial class GetUsersQuery
{
public record Query;

private static ValueTask<IEnumerable<User>> HandleAsync(
Query _,
UsersService usersService,
CancellationToken token)
{
return usersService.GetUsers();
}
}

public class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
: Behavior<TRequest, TResponse>
{
public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
{
var response = await Next(request, cancellationToken);

return response;
}
}

public class User { }
public class UsersService
{
public ValueTask<IEnumerable<User>> GetUsers() =>
ValueTask.FromResult(Enumerable.Empty<User>());
}

public interface ILogger<T>;
""",
assemblies
);

Assert.Equal(
[
"Immediate.Handlers.Generators/Immediate.Handlers.Generators.ImmediateHandlers.ImmediateHandlersGenerator/IH.Dummy.GetUsersQuery.g.cs",
.. assemblies switch
{
DriverReferenceAssemblies.Normal => Enumerable.Empty<string>(),
DriverReferenceAssemblies.Msdi =>
["Immediate.Handlers.Generators/Immediate.Handlers.Generators.ImmediateHandlers.ImmediateHandlersGenerator/IH.ServiceCollectionExtensions.g.cs"],

DriverReferenceAssemblies.None or _ => throw new UnreachableException(),
},
],
result.GeneratedTrees.Select(t => t.FilePath.Replace('\\', '/'))
);

_ = await Verify(result)
.UseParameters(string.Join('_', assemblies));
}
}