Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions src/Mocha/Mocha.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<Project Path="src/Mocha.Inbox/Mocha.Inbox.csproj" />
<Project Path="src/Mocha.Outbox/Mocha.Outbox.csproj" />
<Project Path="src/Mocha.Threading/Mocha.Threading.csproj" />
<Project Path="src/Mocha.Mediator/Mocha.Mediator.csproj" />
<Project Path="src/Mocha.Analyzers/Mocha.Analyzers.csproj" />
<Project Path="src/Mocha.Transport.InMemory/Mocha.Transport.InMemory.csproj" />
<Project Path="src/Mocha.Transport.RabbitMQ/Mocha.Transport.RabbitMQ.csproj" />
<Project Path="src/Mocha.Transport.Postgres/Mocha.Transport.Postgres.csproj" />
Expand Down
3 changes: 3 additions & 0 deletions src/Mocha/benchmarks/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.15.4" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'debug'">
<PackageVersion Include="Roslynator.Analyzers" Version="4.15.0" />
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.15.0" />
Expand Down
2 changes: 1 addition & 1 deletion src/Mocha/benchmarks/Mocha.Mediator.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
var config = DefaultConfig.Instance
.AddJob(Job.Default.WithToolchain(
CsProjCoreToolchain.From(
new NetCoreAppSettings("net9.0", null, ".NET 9.0"))));
new NetCoreAppSettings("net10.0", null, ".NET 10.0"))));

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ MO0001 | Mediator | Warning | Message type has no registered handler
MO0002 | Mediator | Error | Message type has multiple handlers
MO0003 | Mediator | Warning | Handler is abstract and will not be registered
MO0004 | Mediator | Info | Open generic message type cannot be dispatched
MO0005 | Mediator | Error | Handler implements multiple mediator handler interfaces
16 changes: 16 additions & 0 deletions src/Mocha/src/Mocha.Analyzers/Errors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,20 @@ public static class Errors
category: "Mediator",
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true);

/// <summary>
/// Gets the descriptor for MO0005: a handler type implements multiple mediator handler interfaces.
/// </summary>
/// <remarks>
/// Reported as an error when a concrete class implements more than one of
/// <c>ICommandHandler</c>, <c>IQueryHandler</c>, or <c>INotificationHandler</c>.
/// A handler must implement exactly one mediator handler interface.
/// </remarks>
public static readonly DiagnosticDescriptor MultipleHandlerInterfaces = new(
id: "MO0005",
title: "Handler implements multiple mediator handler interfaces",
messageFormat: "Handler '{0}' must implement exactly one mediator handler interface",
category: "Mediator",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,112 +42,77 @@ public void WriteBeginRegistrationMethod()
Writer.DecreaseIndent();
Writer.WriteIndentedLine("{");
Writer.IncreaseIndent();

Writer.WriteIndentedLine("var services = builder.Services;");
Writer.WriteIndentedLine("var lifetime = builder.Options.ServiceLifetime;");
}

public void WriteHandlerRegistration(HandlerInfo handler)
{
switch (handler.Kind)
{
case HandlerKind.CommandVoid:
WriteServiceDescriptor(
"global::Mocha.Mediator.ICommandHandler<{0}>",
handler.HandlerTypeName,
handler.MessageTypeName);
break;
case HandlerKind.CommandResponse:
WriteServiceDescriptor(
"global::Mocha.Mediator.ICommandHandler<{0}, {1}>",
handler.HandlerTypeName,
handler.MessageTypeName,
handler.ResponseTypeName!);
break;
case HandlerKind.Query:
WriteServiceDescriptor(
"global::Mocha.Mediator.IQueryHandler<{0}, {1}>",
handler.HandlerTypeName,
handler.MessageTypeName,
handler.ResponseTypeName!);
break;
}
}

public void WriteNotificationHandlerRegistration(NotificationHandlerInfo handler)
{
// Use TryAddEnumerable to prevent duplicate handler registrations
// when the generated Add{Module} extension is called more than once
// (e.g. in tests or modular startup).
Writer.WriteIndentedLine(
"global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddEnumerable(services, new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor(typeof(global::Mocha.Mediator.INotificationHandler<{0}>), typeof({1}), lifetime));",
handler.NotificationTypeName,
handler.HandlerTypeName);
}

/// <summary>
/// Writes the opening of a ConfigureMediator lambda for deferred pipeline registrations.
/// </summary>
public void WriteBeginConfigureMediator()
{
Writer.WriteIndentedLine("global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator(builder, static b =>");
Writer.WriteIndentedLine("{");
Writer.IncreaseIndent();
}

/// <summary>
/// Writes the closing of a ConfigureMediator lambda.
/// </summary>
public void WriteEndConfigureMediator()
{
Writer.DecreaseIndent();
Writer.WriteIndentedLine("});");
}

/// <summary>
/// Writes a pipeline registration for a handler (inside ConfigureMediator lambda, using 'b').
/// Writes an AddHandlerConfiguration call for a command/query handler.
/// </summary>
public void WritePipelineRegistration(HandlerInfo handler)
public void WriteHandlerConfiguration(HandlerInfo handler)
{
var (terminalMethod, responseType) = handler.Kind switch
var (kindEnum, terminalCall) = handler.Kind switch
{
HandlerKind.CommandVoid => ($"BuildVoidCommandTerminal<{handler.MessageTypeName}>()", null),
HandlerKind.CommandResponse => ($"BuildCommandTerminal<{handler.MessageTypeName}, {handler.ResponseTypeName}>()", handler.ResponseTypeName),
HandlerKind.Query => ($"BuildQueryTerminal<{handler.MessageTypeName}, {handler.ResponseTypeName}>()", handler.ResponseTypeName),
HandlerKind.Command => (
"Command",
$"BuildCommandPipeline<{handler.HandlerTypeName}, {handler.MessageTypeName}>()"),
HandlerKind.CommandResponse => (
"CommandResponse",
$"BuildCommandResponsePipeline<{handler.HandlerTypeName}, {handler.MessageTypeName}, {handler.ResponseTypeName}>()"),
HandlerKind.Query => (
"Query",
$"BuildQueryPipeline<{handler.HandlerTypeName}, {handler.MessageTypeName}, {handler.ResponseTypeName}>()"),
_ => throw new ArgumentOutOfRangeException()
};

WritePipelineConfiguration(handler.MessageTypeName, responseType, terminalMethod);
WriteAddHandlerConfiguration(
handler.HandlerTypeName,
handler.MessageTypeName,
handler.ResponseTypeName,
kindEnum,
$"global::Mocha.Mediator.PipelineBuilder.{terminalCall}");
}

/// <summary>
/// Writes a pipeline registration for a notification group (inside ConfigureMediator lambda, using 'b').
/// Writes an AddHandlerConfiguration call for a notification handler.
/// </summary>
public void WriteNotificationPipelineRegistration(string notificationType,
List<NotificationHandlerInfo> groupHandlers)
public void WriteNotificationHandlerConfiguration(
string notificationType,
NotificationHandlerInfo handler)
{
var handlerTypeArgs = string.Join(", ",
groupHandlers.Select(h => $"typeof({h.HandlerTypeName})"));

var terminalMethod = $"BuildNotificationTerminal<{notificationType}>(new global::System.Type[] {{ {handlerTypeArgs} }})";
WritePipelineConfiguration(notificationType, null, terminalMethod);
WriteAddHandlerConfiguration(
handler.HandlerTypeName,
notificationType,
responseTypeName: null,
"Notification",
$"global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline<{handler.HandlerTypeName}, {notificationType}>()");
}

private void WritePipelineConfiguration(string messageTypeName, string? responseTypeName, string terminalMethod)
private void WriteAddHandlerConfiguration(
string handlerTypeName,
string messageTypeName,
string? responseTypeName,
string kindEnum,
string terminalCall)
{
Writer.WriteIndentedLine("b.RegisterPipeline(new global::Mocha.Mediator.MediatorPipelineConfiguration");
Writer.WriteIndentedLine(
"global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration<{0}>(builder,",
handlerTypeName);
Writer.IncreaseIndent();
Writer.WriteIndentedLine("new global::Mocha.Mediator.MediatorHandlerConfiguration");
Writer.WriteIndentedLine("{");
Writer.IncreaseIndent();
Writer.WriteIndentedLine("HandlerType = typeof({0}),", handlerTypeName);
Writer.WriteIndentedLine("MessageType = typeof({0}),", messageTypeName);

if (responseTypeName is not null)
{
Writer.WriteIndentedLine("ResponseType = typeof({0}),", responseTypeName);
}

Writer.WriteIndentedLine("Terminal = global::Mocha.Mediator.PipelineBuilder.{0}", terminalMethod);
Writer.WriteIndentedLine("Kind = global::Mocha.Mediator.MediatorHandlerKind.{0},", kindEnum);
Writer.WriteIndentedLine("Delegate = {0}", terminalCall);
Writer.DecreaseIndent();
Writer.WriteIndentedLine("});");
Writer.DecreaseIndent();
}

public void WriteSectionComment(string comment)
Expand All @@ -164,17 +129,6 @@ public void WriteEndRegistrationMethod()
Writer.WriteIndentedLine("}");
}

private void WriteServiceDescriptor(string serviceTypeFormat, string implementationType, params object[] typeArgs)
{
// Use TryAdd to prevent duplicate handler registrations
// when the generated Add{Module} extension is called more than once.
var serviceType = string.Format(serviceTypeFormat, typeArgs);
Writer.WriteIndentedLine(
"global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd(services, new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor(typeof({0}), typeof({1}), lifetime));",
serviceType,
implementationType);
}

#pragma warning disable CA5351 // MD5 is used for non-security hashing (file name salting)
private static readonly MD5 s_md5 = MD5.Create();
#pragma warning restore CA5351
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,57 +37,33 @@ public void Generate(

using var builder = new DependencyInjectionFileBuilder(moduleName, assemblyName);

builder.WriteHeader();
builder.WriteBeginNamespace();
builder.WriteBeginClass();
builder.WriteBeginRegistrationMethod();

// Register handlers
if (handlers.Count > 0)
{
builder.WriteSectionComment("Register handlers");

foreach (var handler in handlers)
{
builder.WriteHandlerRegistration(handler);
}
}

// Register notification handlers
if (notificationHandlers.Count > 0)
{
builder.WriteSectionComment("Register notification handlers");

foreach (var handler in notificationHandlers)
{
builder.WriteNotificationHandlerRegistration(handler);
}
}

// Register pipelines (all handlers + notifications) via deferred ConfigureMediator
var notificationGroups = notificationHandlers
.GroupBy(h => h.NotificationTypeName)
.OrderBy(g => g.Key)
.ToList();

builder.WriteHeader();
builder.WriteBeginNamespace();
builder.WriteBeginClass();
builder.WriteBeginRegistrationMethod();

// Register all handler configurations
if (handlers.Count > 0 || notificationGroups.Count > 0)
{
builder.WriteSectionComment("Register pipelines");
builder.WriteBeginConfigureMediator();
builder.WriteSectionComment("Register handler configurations");

foreach (var handler in handlers)
{
builder.WritePipelineRegistration(handler);
builder.WriteHandlerConfiguration(handler);
}

foreach (var group in notificationGroups)
{
builder.WriteNotificationPipelineRegistration(
group.Key,
group.OrderBy(h => h.HandlerTypeName).ToList());
foreach (var handler in group.OrderBy(h => h.HandlerTypeName))
{
builder.WriteNotificationHandlerConfiguration(group.Key, handler);
}
}

builder.WriteEndConfigureMediator();
}

builder.WriteEndRegistrationMethod();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public sealed class HandlerInspector : ISyntaxInspector

private static readonly HandlerKindDescriptor[] s_handlerKinds =
[
new(static s => s.ICommandHandlerVoid, HandlerKind.CommandVoid, HasResponse: false),
new(static s => s.ICommandHandlerVoid, HandlerKind.Command, HasResponse: false),
new(static s => s.ICommandHandlerResponse, HandlerKind.CommandResponse, HasResponse: true),
new(static s => s.IQueryHandler, HandlerKind.Query, HasResponse: true)
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public bool TryHandle(
return true;
}

syntaxInfo = new MessageTypeInfo(typeName, typeNamespace, MessageKind.CommandVoid, location);
syntaxInfo = new MessageTypeInfo(typeName, typeNamespace, MessageKind.Command, location);
return true;
}

Expand Down
Loading
Loading