fix(dispatcher): throw on ambiguous handlers instead of silent .First()
Multiple handlers for the same request were previously resolved via .First(), with ordering depending on Type.GetInterfaces() order (non-deterministic). Now ScalarRequestInvoker/StreamRequestInvoker throw InvalidOperationException listing the candidate handlers when more than one matches.
This commit is contained in:
parent
15bf9a8e56
commit
a7d7e9fbaa
7 changed files with 134 additions and 9 deletions
|
|
@ -116,7 +116,7 @@ internal sealed class ScalarDispatcherTests
|
|||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_dispatch_a_request_async_with_an_interface_constrained_handler()
|
||||
public async Task I_can_see_it_throw_on_ambiguous_concrete_and_constrained_scalar_handlers()
|
||||
{
|
||||
var sc = new ServiceCollection();
|
||||
sc.AddRequestDispatcher(builder => builder
|
||||
|
|
@ -126,13 +126,18 @@ internal sealed class ScalarDispatcherTests
|
|||
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
|
||||
|
||||
var request = new InterfaceInheritedScalarRequest { Name = "Constrained" };
|
||||
var result = await dispatcher.DispatchAsync(request);
|
||||
|
||||
// Both InterfaceInheritedScalarHandler and InterfaceConstrainedScalarHandler could match.
|
||||
// InterfaceInheritedScalarHandler is a concrete match for InterfaceInheritedScalarRequest.
|
||||
// InterfaceConstrainedScalarHandler is an open generic match.
|
||||
// Currently Dispatcher.SendAsync checks concrete handlers first.
|
||||
await Assert.That(result).IsEquivalentTo("Constrained-InterfaceHandled");
|
||||
// Ambiguity is no longer resolved silently with .First(); it throws instead.
|
||||
var ex = await Assert.That(async () => await dispatcher.DispatchAsync(request)).Throws<InvalidOperationException>();
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(ex?.Message).Contains(nameof(InterfaceInheritedScalarHandler));
|
||||
await Assert.That(ex?.Message).Contains("InterfaceConstrainedScalarHandler");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
@ -234,6 +239,26 @@ internal sealed class ScalarDispatcherTests
|
|||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_see_it_throw_on_ambiguous_scalar_handlers()
|
||||
{
|
||||
var sc = new ServiceCollection();
|
||||
sc.AddRequestDispatcher(builder => builder
|
||||
.Add(typeof(DuplicateScalarHandlerA))
|
||||
.Add(typeof(DuplicateScalarHandlerB)));
|
||||
var provider = sc.BuildServiceProvider();
|
||||
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
|
||||
|
||||
var request = new DuplicateScalarRequest();
|
||||
var ex = await Assert.That(async () => await dispatcher.DispatchAsync(request)).Throws<InvalidOperationException>();
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(ex?.Message).Contains(nameof(DuplicateScalarHandlerA));
|
||||
await Assert.That(ex?.Message).Contains(nameof(DuplicateScalarHandlerB));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_see_it_throw_if_dispatcher_options_are_modified_after_build()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue