website/src/docs/mocha/v16/diagnostics.md
Mocha uses a Roslyn source generator to validate your message handlers, consumers, and sagas at compile time. When the generator detects a problem - a missing handler, a duplicate registration, an invalid type - it emits a diagnostic that appears as a compiler warning or error in your IDE and build output. You can fix these issues before your code ever runs.
| Code | Description | Severity |
|---|---|---|
| MO0001 | Missing handler for message type | Warning |
| MO0002 | Duplicate handler for message type | Error |
| MO0003 | Handler is abstract | Warning |
| MO0004 | Open generic message type cannot be dispatched | Info |
| MO0005 | Handler implements multiple mediator handler interfaces | Error |
| MO0011 | Duplicate handler for request type | Error |
| MO0012 | Open generic messaging handler cannot be auto-registered | Info |
| MO0013 | Messaging handler is abstract | Warning |
| MO0014 | Saga must have a public parameterless constructor | Error |
These diagnostics apply to the in-process mediator - commands, queries, and notifications dispatched within a single process.
Missing handler for message type
| Severity | Warning |
| Message | Message type '{0}' has no registered handler |
A command or query type is declared but no corresponding handler implementation exists. The mediator requires exactly one handler for each command and query type. This diagnostic does not apply to notifications, which can have zero handlers.
using Mocha.Mediator;
// Command with no handler - triggers MO0001
public record PlaceOrder(Guid OrderId, decimal Total) : ICommand;
Implement a handler for the message type.
using Mocha.Mediator;
public record PlaceOrder(Guid OrderId, decimal Total) : ICommand;
public class PlaceOrderHandler : ICommandHandler<PlaceOrder>
{
public ValueTask HandleAsync(
PlaceOrder command,
CancellationToken cancellationToken)
{
// process the order
return ValueTask.CompletedTask;
}
}
Duplicate handler for message type
| Severity | Error |
| Message | Message type '{0}' has multiple handlers: {1} |
A command or query type has more than one handler implementation. Commands and queries require exactly one handler - the mediator cannot decide which one to call. This diagnostic does not apply to notifications, which support multiple handlers by design.
using Mocha.Mediator;
public record PlaceOrder(Guid OrderId, decimal Total) : ICommand;
// Two handlers for the same command - triggers MO0002
public class PlaceOrderHandler : ICommandHandler<PlaceOrder>
{
public ValueTask HandleAsync(PlaceOrder command, CancellationToken ct)
=> ValueTask.CompletedTask;
}
public class DuplicateOrderHandler : ICommandHandler<PlaceOrder>
{
public ValueTask HandleAsync(PlaceOrder command, CancellationToken ct)
=> ValueTask.CompletedTask;
}
Remove all but one handler. If you need multiple side effects for the same action, consider publishing a notification from the single handler and reacting to it with separate notification handlers.
using Mocha.Mediator;
public record PlaceOrder(Guid OrderId, decimal Total) : ICommand;
public class PlaceOrderHandler : ICommandHandler<PlaceOrder>
{
public ValueTask HandleAsync(PlaceOrder command, CancellationToken ct)
=> ValueTask.CompletedTask;
}
Handler is abstract
| Severity | Warning |
| Message | Handler '{0}' is abstract and will not be registered |
A class implements a handler interface (ICommandHandler, IQueryHandler, or INotificationHandler) but is declared abstract. The source generator skips abstract types because they cannot be instantiated.
using Mocha.Mediator;
public record GetOrderTotal(Guid OrderId) : IQuery<decimal>;
// Abstract handler - triggers MO0003
public abstract class GetOrderTotalHandler : IQueryHandler<GetOrderTotal, decimal>
{
public abstract ValueTask<decimal> HandleAsync(
GetOrderTotal query,
CancellationToken cancellationToken);
}
Make the handler concrete. If you want shared base logic, move it to a base class that does not implement the handler interface, and have the concrete handler extend it.
using Mocha.Mediator;
public record GetOrderTotal(Guid OrderId) : IQuery<decimal>;
public class GetOrderTotalHandler : IQueryHandler<GetOrderTotal, decimal>
{
public ValueTask<decimal> HandleAsync(
GetOrderTotal query,
CancellationToken cancellationToken)
{
return ValueTask.FromResult(99.99m);
}
}
Open generic message type cannot be dispatched
| Severity | Info |
| Message | Message type '{0}' is an open generic and cannot be dispatched at runtime |
A command or query type has unbound type parameters. The mediator dispatches concrete types at runtime and cannot resolve an open generic like MyCommand<T>.
using Mocha.Mediator;
// Open generic command - triggers MO0004
public record ProcessItem<T>(T Item) : ICommand;
Use concrete message types instead.
using Mocha.Mediator;
public record ProcessOrder(Guid OrderId) : ICommand;
public record ProcessPayment(decimal Amount) : ICommand;
Handler implements multiple mediator handler interfaces
| Severity | Error |
| Message | Handler '{0}' must implement exactly one mediator handler interface |
A single class implements more than one of ICommandHandler, IQueryHandler, or INotificationHandler. Each handler class must implement exactly one mediator handler interface so the generator can produce unambiguous registrations.
using Mocha.Mediator;
public record PlaceOrder(Guid OrderId) : ICommand;
public record GetOrder(Guid OrderId) : IQuery<Order>;
// Implements both command and query handler - triggers MO0005
public class OrderHandler
: ICommandHandler<PlaceOrder>,
IQueryHandler<GetOrder, Order>
{
public ValueTask HandleAsync(PlaceOrder command, CancellationToken ct)
=> ValueTask.CompletedTask;
public ValueTask<Order> HandleAsync(GetOrder query, CancellationToken ct)
=> ValueTask.FromResult(new Order());
}
Split into separate handler classes, one per interface.
using Mocha.Mediator;
public record PlaceOrder(Guid OrderId) : ICommand;
public record GetOrder(Guid OrderId) : IQuery<Order>;
public class PlaceOrderHandler : ICommandHandler<PlaceOrder>
{
public ValueTask HandleAsync(PlaceOrder command, CancellationToken ct)
=> ValueTask.CompletedTask;
}
public class GetOrderHandler : IQueryHandler<GetOrder, Order>
{
public ValueTask<Order> HandleAsync(GetOrder query, CancellationToken ct)
=> ValueTask.FromResult(new Order());
}
These diagnostics apply to the message bus - event handlers, request handlers, batch handlers, consumers, and sagas that communicate across service boundaries.
Duplicate handler for request type
| Severity | Error |
| Message | Request type '{0}' has multiple handlers: {1} |
A request type (used with SendAsync or RequestAsync) has more than one handler implementation. Request types require exactly one handler - the bus cannot route to multiple targets.
using Mocha;
public record ProcessPayment(decimal Amount);
// Two handlers for the same request type - triggers MO0011
public class PaymentHandlerA : IEventRequestHandler<ProcessPayment>
{
public ValueTask HandleAsync(
ProcessPayment request,
CancellationToken ct)
=> ValueTask.CompletedTask;
}
public class PaymentHandlerB : IEventRequestHandler<ProcessPayment>
{
public ValueTask HandleAsync(
ProcessPayment request,
CancellationToken ct)
=> ValueTask.CompletedTask;
}
Keep one handler per request type.
using Mocha;
public record ProcessPayment(decimal Amount);
public class ProcessPaymentHandler : IEventRequestHandler<ProcessPayment>
{
public ValueTask HandleAsync(
ProcessPayment request,
CancellationToken ct)
=> ValueTask.CompletedTask;
}
Open generic messaging handler cannot be auto-registered
| Severity | Info |
| Message | Handler '{0}' is an open generic and cannot be auto-registered |
A messaging handler (IEventHandler<T>, IEventRequestHandler<T>, IBatchEventHandler<T>, or IConsumer<T>) has unbound type parameters. The source generator cannot produce registration code for open generic types.
using Mocha;
// Open generic handler - triggers MO0012
public class GenericEventHandler<T> : IEventHandler<T>
{
public ValueTask HandleAsync(
T message,
CancellationToken ct)
=> ValueTask.CompletedTask;
}
Make the handler concrete. If you need to handle multiple event types with shared logic, create a concrete handler for each type and extract the shared logic into a base class or shared service.
If you need to register an open generic handler, register it manually through DI instead of relying on auto-registration.
using Mocha;
public record OrderPlaced(Guid OrderId);
public class OrderPlacedHandler : IEventHandler<OrderPlaced>
{
public ValueTask HandleAsync(
OrderPlaced message,
CancellationToken ct)
=> ValueTask.CompletedTask;
}
Messaging handler is abstract
| Severity | Warning |
| Message | Handler '{0}' is abstract and will not be registered |
A class implements a messaging handler interface but is declared abstract. The source generator skips abstract types because they cannot be instantiated.
using Mocha;
public record OrderPlaced(Guid OrderId);
// Abstract handler - triggers MO0013
public abstract class OrderEventHandler : IEventHandler<OrderPlaced>
{
public abstract ValueTask HandleAsync(
OrderPlaced message,
CancellationToken ct);
}
Make the handler concrete. If you need shared base logic, move it to a base class that does not implement the handler interface.
using Mocha;
public record OrderPlaced(Guid OrderId);
public class OrderPlacedHandler : IEventHandler<OrderPlaced>
{
public ValueTask HandleAsync(
OrderPlaced message,
CancellationToken ct)
=> ValueTask.CompletedTask;
}
Saga must have a public parameterless constructor
| Severity | Error |
| Message | Saga '{0}' must have a public parameterless constructor |
A Saga<TState> subclass does not have a public parameterless constructor. The saga infrastructure requires this constructor to instantiate the saga type. This is enforced by the new() constraint on the AddSaga<T> registration method.
using Mocha.Sagas;
public class RefundSagaState : SagaStateBase
{
public Guid OrderId { get; set; }
}
// Constructor requires a parameter - triggers MO0014
public class RefundSaga : Saga<RefundSagaState>
{
private readonly ILogger _logger;
public RefundSaga(ILogger logger)
{
_logger = logger;
}
}
Add a public parameterless constructor. Sagas are configured through their state machine definition, not through constructor injection. If you need dependencies, access them through the saga's built-in service resolution.
using Mocha.Sagas;
public class RefundSagaState : SagaStateBase
{
public Guid OrderId { get; set; }
}
public class RefundSaga : Saga<RefundSagaState>
{
public RefundSaga()
{
}
}