1
0
mirror of synced 2024-11-14 14:33:49 +00:00

Fixed queue arguments error due to wrong value types

Added test for publish overflows
Removed support for Unity Container
Changed third party package references to ranges
Fixed XML documentation
This commit is contained in:
Mark van Renswoude 2022-11-22 13:20:47 +01:00
parent 35bd5e920d
commit bcdb376256
88 changed files with 257 additions and 440 deletions

View File

@ -11,7 +11,6 @@
<PackageReference Include="Castle.Windsor" Version="5.1.2" /> <PackageReference Include="Castle.Windsor" Version="5.1.2" />
<PackageReference Include="Ninject" Version="3.3.6" /> <PackageReference Include="Ninject" Version="3.3.6" />
<PackageReference Include="SimpleInjector" Version="5.4.1" /> <PackageReference Include="SimpleInjector" Version="5.4.1" />
<PackageReference Include="Unity" Version="5.11.10" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -20,7 +19,6 @@
<ProjectReference Include="..\..\Tapeti.DataAnnotations\Tapeti.DataAnnotations.csproj" /> <ProjectReference Include="..\..\Tapeti.DataAnnotations\Tapeti.DataAnnotations.csproj" />
<ProjectReference Include="..\..\Tapeti.Ninject\Tapeti.Ninject.csproj" /> <ProjectReference Include="..\..\Tapeti.Ninject\Tapeti.Ninject.csproj" />
<ProjectReference Include="..\..\Tapeti.SimpleInjector\Tapeti.SimpleInjector.csproj" /> <ProjectReference Include="..\..\Tapeti.SimpleInjector\Tapeti.SimpleInjector.csproj" />
<ProjectReference Include="..\..\Tapeti.UnityContainer\Tapeti.UnityContainer.csproj" />
<ProjectReference Include="..\..\Tapeti\Tapeti.csproj" /> <ProjectReference Include="..\..\Tapeti\Tapeti.csproj" />
<ProjectReference Include="..\ExampleLib\ExampleLib.csproj" /> <ProjectReference Include="..\ExampleLib\ExampleLib.csproj" />
<ProjectReference Include="..\Messaging.TapetiExample\Messaging.TapetiExample.csproj" /> <ProjectReference Include="..\Messaging.TapetiExample\Messaging.TapetiExample.csproj" />

View File

@ -13,8 +13,6 @@ using Tapeti.DataAnnotations;
using Tapeti.Default; using Tapeti.Default;
using Tapeti.Ninject; using Tapeti.Ninject;
using Tapeti.SimpleInjector; using Tapeti.SimpleInjector;
using Tapeti.UnityContainer;
using Unity;
using Container = SimpleInjector.Container; using Container = SimpleInjector.Container;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
@ -30,7 +28,6 @@ namespace _01_PublishSubscribe
// or use your IoC container of choice: // or use your IoC container of choice:
//var dependencyResolver = GetAutofacDependencyResolver(); //var dependencyResolver = GetAutofacDependencyResolver();
//var dependencyResolver = GetCastleWindsorDependencyResolver(); //var dependencyResolver = GetCastleWindsorDependencyResolver();
//var dependencyResolver = GetUnityDependencyResolver();
//var dependencyResolver = GetNinjectDependencyResolver(); //var dependencyResolver = GetNinjectDependencyResolver();
// This helper is used because this example is not run as a service. You do not // This helper is used because this example is not run as a service. You do not
@ -131,17 +128,6 @@ namespace _01_PublishSubscribe
} }
internal static IDependencyContainer GetUnityDependencyResolver()
{
var container = new UnityContainer();
container.RegisterType<ILogger, ConsoleLogger>();
container.RegisterType<ExamplePublisher>();
return new UnityDependencyResolver(container);
}
internal static IDependencyContainer GetNinjectDependencyResolver() internal static IDependencyContainer GetNinjectDependencyResolver()
{ {
var kernel = new StandardKernel(); var kernel = new StandardKernel();

View File

@ -79,7 +79,7 @@ namespace ExampleLib
{ {
while (true) while (true)
{ {
if (!(e is AggregateException aggregateException)) if (e is not AggregateException aggregateException)
return e; return e;
if (aggregateException.InnerExceptions.Count != 1) if (aggregateException.InnerExceptions.Count != 1)

View File

@ -6,7 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="Tapeti.Annotations" Version="3.0.0" /> <PackageReference Include="Tapeti.Annotations" Version="3.*-*" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -2,9 +2,10 @@
using Autofac; using Autofac;
using Autofac.Builder; using Autofac.Builder;
// ReSharper disable UnusedMember.Global
namespace Tapeti.Autofac namespace Tapeti.Autofac
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Dependency resolver and container implementation for Autofac. /// Dependency resolver and container implementation for Autofac.
/// Since this class needs access to both the ContainerBuilder and the built IContainer, /// Since this class needs access to both the ContainerBuilder and the built IContainer,
@ -83,7 +84,7 @@ namespace Tapeti.Autofac
{ {
CheckContainerBuilder(); CheckContainerBuilder();
containerBuilder containerBuilder
.Register(context => factory()) .Register(_ => factory())
.As<TService>() .As<TService>()
.PreserveExistingDefaults(); .PreserveExistingDefaults();
} }
@ -116,7 +117,7 @@ namespace Tapeti.Autofac
{ {
CheckContainerBuilder(); CheckContainerBuilder();
containerBuilder containerBuilder
.Register(context => factory()) .Register(_ => factory())
.As<TService>() .As<TService>()
.SingleInstance() .SingleInstance()
.PreserveExistingDefaults(); .PreserveExistingDefaults();

View File

@ -15,7 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="6.5.0" /> <PackageReference Include="Autofac" Version="6.*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -15,7 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Castle.Windsor" Version="5.1.2" /> <PackageReference Include="Castle.Windsor" Version="5.*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -4,7 +4,6 @@ using Castle.Windsor;
namespace Tapeti.CastleWindsor namespace Tapeti.CastleWindsor
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Dependency resolver and container implementation for Castle Windsor. /// Dependency resolver and container implementation for Castle Windsor.
/// </summary> /// </summary>

View File

@ -3,7 +3,6 @@ using Tapeti.Config;
namespace Tapeti.DataAnnotations namespace Tapeti.DataAnnotations
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Provides the DataAnnotations validation middleware. /// Provides the DataAnnotations validation middleware.
/// </summary> /// </summary>

View File

@ -5,7 +5,6 @@ using Tapeti.Config;
namespace Tapeti.DataAnnotations namespace Tapeti.DataAnnotations
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Validates consumed messages using System.ComponentModel.DataAnnotations /// Validates consumed messages using System.ComponentModel.DataAnnotations
/// </summary> /// </summary>

View File

@ -5,7 +5,6 @@ using Tapeti.Config;
namespace Tapeti.DataAnnotations namespace Tapeti.DataAnnotations
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Validates published messages using System.ComponentModel.DataAnnotations /// Validates published messages using System.ComponentModel.DataAnnotations
/// </summary> /// </summary>

View File

@ -11,7 +11,6 @@ using Newtonsoft.Json;
namespace Tapeti.Flow.SQL namespace Tapeti.Flow.SQL
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// IFlowRepository implementation for SQL server. /// IFlowRepository implementation for SQL server.
/// </summary> /// </summary>

View File

@ -13,7 +13,7 @@ namespace Tapeti.Flow.SQL
// 2627: Violation of %ls constraint '%.*ls'. Cannot insert duplicate key in object '%.*ls'. The duplicate key value is %ls. // 2627: Violation of %ls constraint '%.*ls'. Cannot insert duplicate key in object '%.*ls'. The duplicate key value is %ls.
public static bool IsDuplicateKey(SqlException e) public static bool IsDuplicateKey(SqlException e)
{ {
return e != null && (e.Number == 2601 || e.Number == 2627); return e is { Number: 2601 or 2627 };
} }
@ -21,12 +21,12 @@ namespace Tapeti.Flow.SQL
{ {
switch (e) switch (e)
{ {
case TimeoutException _: case TimeoutException:
return true; return true;
case Exception exception: case not null:
{ {
var sqlExceptions = ExtractSqlExceptions(exception); var sqlExceptions = ExtractSqlExceptions(e);
return sqlExceptions.Select(UnwrapSqlErrors).Any(IsRecoverableSQLError); return sqlExceptions.Select(UnwrapSqlErrors).Any(IsRecoverableSQLError);
} }

View File

@ -2,7 +2,6 @@
namespace Tapeti.Flow.Annotations namespace Tapeti.Flow.Annotations
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Marks a message handler as a response message handler which continues a Tapeti Flow. /// Marks a message handler as a response message handler which continues a Tapeti Flow.
/// The method only receives direct messages, and does not create a routing key based binding to the queue. /// The method only receives direct messages, and does not create a routing key based binding to the queue.

View File

@ -3,7 +3,6 @@ using JetBrains.Annotations;
namespace Tapeti.Flow.Annotations namespace Tapeti.Flow.Annotations
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Marks this method as the start of a Tapeti Flow. Use IFlowStarter.Start to begin a new flow and /// Marks this method as the start of a Tapeti Flow. Use IFlowStarter.Start to begin a new flow and
/// call this method. Must return an IYieldPoint. /// call this method. Must return an IYieldPoint.

View File

@ -1,5 +1,7 @@
using Tapeti.Config; using Tapeti.Config;
// ReSharper disable UnusedMember.Global
namespace Tapeti.Flow namespace Tapeti.Flow
{ {
/// <summary> /// <summary>

View File

@ -60,7 +60,7 @@ namespace Tapeti.Flow.Default
} }
else if (context.Result.Info.ParameterType == typeof(void)) else if (context.Result.Info.ParameterType == typeof(void))
{ {
context.Result.SetHandler((messageContext, value) => HandleParallelResponse(messageContext)); context.Result.SetHandler((messageContext, _) => HandleParallelResponse(messageContext));
} }
else else
throw new ArgumentException($"Result type must be IYieldPoint, Task or void in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}"); throw new ArgumentException($"Result type must be IYieldPoint, Task or void in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}");

View File

@ -3,7 +3,6 @@ using Tapeti.Config;
namespace Tapeti.Flow.Default namespace Tapeti.Flow.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Default implementation for IFlowHandlerContext /// Default implementation for IFlowHandlerContext
/// </summary> /// </summary>

View File

@ -206,7 +206,7 @@ namespace Tapeti.Flow.Default
/// <inheritdoc /> /// <inheritdoc />
public async ValueTask Execute(IFlowHandlerContext context, IYieldPoint yieldPoint) public async ValueTask Execute(IFlowHandlerContext context, IYieldPoint yieldPoint)
{ {
if (!(yieldPoint is DelegateYieldPoint executableYieldPoint)) if (yieldPoint is not DelegateYieldPoint executableYieldPoint)
throw new YieldPointException($"Yield point is required in controller {context.Controller.GetType().Name} for method {context.Method.Name}"); throw new YieldPointException($"Yield point is required in controller {context.Controller.GetType().Name} for method {context.Method.Name}");
FlowContext flowContext = null; FlowContext flowContext = null;
@ -297,8 +297,8 @@ namespace Tapeti.Flow.Default
{ {
private class RequestInfo private class RequestInfo
{ {
public object Message { get; set; } public object Message { get; init; }
public ResponseHandlerInfo ResponseHandlerInfo { get; set; } public ResponseHandlerInfo ResponseHandlerInfo { get; init; }
} }
@ -372,21 +372,13 @@ namespace Tapeti.Flow.Default
{ {
if (requests.Count == 0) if (requests.Count == 0)
{ {
switch (noRequestsBehaviour) return noRequestsBehaviour switch
{ {
case FlowNoRequestsBehaviour.Exception: FlowNoRequestsBehaviour.Exception => throw new YieldPointException("At least one request must be added before yielding a parallel request"),
throw new YieldPointException("At least one request must be added before yielding a parallel request"); FlowNoRequestsBehaviour.Converge => new DelegateYieldPoint(context => flowProvider.Converge(context, convergeMethod.Method.Name, convergeMethodSync)),
FlowNoRequestsBehaviour.EndFlow => new DelegateYieldPoint(EndFlow),
case FlowNoRequestsBehaviour.Converge: _ => throw new ArgumentOutOfRangeException(nameof(noRequestsBehaviour), noRequestsBehaviour, null)
return new DelegateYieldPoint(context => };
flowProvider.Converge(context, convergeMethod.Method.Name, convergeMethodSync));
case FlowNoRequestsBehaviour.EndFlow:
return new DelegateYieldPoint(EndFlow);
default:
throw new ArgumentOutOfRangeException(nameof(noRequestsBehaviour), noRequestsBehaviour, null);
}
} }
if (convergeMethod?.Method == null) if (convergeMethod?.Method == null)

View File

@ -6,7 +6,6 @@ using Tapeti.Config;
namespace Tapeti.Flow.Default namespace Tapeti.Flow.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Default implementation for IFlowStarter. /// Default implementation for IFlowStarter.
/// </summary> /// </summary>

View File

@ -18,7 +18,7 @@ namespace Tapeti.Flow.Default
/// </summary> /// </summary>
public FlowMetadata Metadata public FlowMetadata Metadata
{ {
get => metadata ?? (metadata = new FlowMetadata()); get => metadata ??= new FlowMetadata();
set => metadata = value; set => metadata = value;
} }
@ -34,7 +34,7 @@ namespace Tapeti.Flow.Default
/// </summary> /// </summary>
public Dictionary<Guid, ContinuationMetadata> Continuations public Dictionary<Guid, ContinuationMetadata> Continuations
{ {
get => continuations ?? (continuations = new Dictionary<Guid, ContinuationMetadata>()); get => continuations ??= new Dictionary<Guid, ContinuationMetadata>();
set => continuations = value; set => continuations = value;
} }

View File

@ -9,7 +9,6 @@ using Tapeti.Flow.FlowHelpers;
namespace Tapeti.Flow.Default namespace Tapeti.Flow.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Default implementation of IFlowStore. /// Default implementation of IFlowStore.
/// </summary> /// </summary>
@ -119,7 +118,7 @@ namespace Tapeti.Flow.Default
if (!loaded) if (!loaded)
throw new InvalidOperationException("Flow store is not yet loaded."); throw new InvalidOperationException("Flow store is not yet loaded.");
return new ValueTask<Guid?>(continuationLookup.TryGetValue(continuationID, out var result) ? result : (Guid?)null); return new ValueTask<Guid?>(continuationLookup.TryGetValue(continuationID, out var result) ? result : null);
} }

View File

@ -5,7 +5,6 @@ using System.Threading.Tasks;
namespace Tapeti.Flow.Default namespace Tapeti.Flow.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Default implementation for IFlowRepository. Does not persist any state, relying on the FlowStore's cache instead. /// Default implementation for IFlowRepository. Does not persist any state, relying on the FlowStore's cache instead.
/// </summary> /// </summary>

View File

@ -4,7 +4,6 @@ using Tapeti.Flow.Default;
namespace Tapeti.Flow namespace Tapeti.Flow
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Provides the Flow middleware. /// Provides the Flow middleware.
/// </summary> /// </summary>

View File

@ -35,8 +35,6 @@ namespace Tapeti.Flow.FlowHelpers
try try
{ {
assembly = Assembly.Load(match.Groups["assembly"].Value); assembly = Assembly.Load(match.Groups["assembly"].Value);
if (assembly == null)
return null;
} }
catch catch
{ {

View File

@ -4,7 +4,6 @@ using Tapeti.Config;
namespace Tapeti.Flow namespace Tapeti.Flow
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Provides information about the handler for the flow. /// Provides information about the handler for the flow.
/// </summary> /// </summary>

View File

@ -42,7 +42,6 @@ namespace Tapeti.Flow
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Represents a lock on the flow state, to provide thread safety. /// Represents a lock on the flow state, to provide thread safety.
/// </summary> /// </summary>

View File

@ -2,7 +2,6 @@
namespace Tapeti.Flow namespace Tapeti.Flow
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Raised when a response is expected to end a flow, but none was provided. /// Raised when a response is expected to end a flow, but none was provided.
/// </summary> /// </summary>

View File

@ -36,6 +36,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Tapeti.Annotations" Version="3.0.0" /> <PackageReference Include="Tapeti.Annotations" Version="3.*-*" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -2,7 +2,6 @@
namespace Tapeti.Flow namespace Tapeti.Flow
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Raised when an invalid yield point is returned. /// Raised when an invalid yield point is returned.
/// </summary> /// </summary>

View File

@ -4,7 +4,6 @@ using Ninject;
namespace Tapeti.Ninject namespace Tapeti.Ninject
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Dependency resolver and container implementation for Ninject. /// Dependency resolver and container implementation for Ninject.
/// </summary> /// </summary>
@ -49,7 +48,7 @@ namespace Tapeti.Ninject
if (kernel.GetBindings(typeof(TService)).Any()) if (kernel.GetBindings(typeof(TService)).Any())
return; return;
kernel.Bind<TService>().ToMethod(context => factory()); kernel.Bind<TService>().ToMethod(_ => factory());
} }
@ -77,7 +76,7 @@ namespace Tapeti.Ninject
if (kernel.GetBindings(typeof(TService)).Any()) if (kernel.GetBindings(typeof(TService)).Any())
return; return;
kernel.Bind<TService>().ToMethod(context => factory()).InSingletonScope(); kernel.Bind<TService>().ToMethod(_ => factory()).InSingletonScope();
} }

View File

@ -15,7 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Ninject" Version="3.3.6" /> <PackageReference Include="Ninject" Version="3.*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -19,7 +19,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Serilog" Version="2.12.0" /> <PackageReference Include="Serilog" Version="2.*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Tapeti.Config; using Tapeti.Config;
using Tapeti.Connection;
using ISerilogLogger = Serilog.ILogger; using ISerilogLogger = Serilog.ILogger;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
@ -129,7 +130,7 @@ namespace Tapeti.Serilog
} }
/// <inheritdoc /> /// <inheritdoc />
public void QueueExistsWarning(string queueName, IReadOnlyDictionary<string, string> existingArguments, IReadOnlyDictionary<string, string> arguments) public void QueueExistsWarning(string queueName, IRabbitMQArguments existingArguments, IRabbitMQArguments arguments)
{ {
seriLogger.Warning("Tapeti: durable queue {queueName} exists with incompatible x-arguments ({existingArguments} vs. {arguments}) and will not be redeclared, queue will be consumed as-is", seriLogger.Warning("Tapeti: durable queue {queueName} exists with incompatible x-arguments ({existingArguments} vs. {arguments}) and will not be redeclared, queue will be consumed as-is",
queueName, queueName,

View File

@ -4,7 +4,6 @@ using SimpleInjector;
namespace Tapeti.SimpleInjector namespace Tapeti.SimpleInjector
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Dependency resolver and container implementation for SimpleInjector. /// Dependency resolver and container implementation for SimpleInjector.
/// </summary> /// </summary>

View File

@ -19,7 +19,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SimpleInjector" Version="5.4.1" /> <PackageReference Include="SimpleInjector" Version="5.*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,9 +1,12 @@
// Do not include in the Release build for AppVeyor due to the Docker requirement // Do not include in the Release build for AppVeyor due to the Docker requirement
#if DEBUG #if DEBUG
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Tapeti.Connection; using Tapeti.Connection;
using Tapeti.Default;
using Tapeti.Exceptions;
using Tapeti.Tests.Mock; using Tapeti.Tests.Mock;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -11,11 +14,14 @@ using Xunit.Abstractions;
namespace Tapeti.Tests.Client namespace Tapeti.Tests.Client
{ {
[Collection(RabbitMQCollection.Name)] [Collection(RabbitMQCollection.Name)]
public class TapetiClientTests public class TapetiClientTests : IAsyncLifetime
{ {
private readonly RabbitMQFixture fixture; private readonly RabbitMQFixture fixture;
private readonly MockDependencyResolver dependencyResolver = new(); private readonly MockDependencyResolver dependencyResolver = new();
private TapetiClient client;
public TapetiClientTests(RabbitMQFixture fixture, ITestOutputHelper testOutputHelper) public TapetiClientTests(RabbitMQFixture fixture, ITestOutputHelper testOutputHelper)
{ {
this.fixture = fixture; this.fixture = fixture;
@ -24,6 +30,22 @@ namespace Tapeti.Tests.Client
} }
public Task InitializeAsync()
{
client = CreateClient();
return Task.CompletedTask;
}
public async Task DisposeAsync()
{
await client.Close();
client = null;
}
[Fact] [Fact]
public void Fixture() public void Fixture()
{ {
@ -35,31 +57,50 @@ namespace Tapeti.Tests.Client
[Fact] [Fact]
public async Task DynamicQueueDeclareNoPrefix() public async Task DynamicQueueDeclareNoPrefix()
{ {
var client = CreateCilent();
var queueName = await client.DynamicQueueDeclare(null, null, CancellationToken.None); var queueName = await client.DynamicQueueDeclare(null, null, CancellationToken.None);
queueName.Should().NotBeNullOrEmpty(); queueName.Should().NotBeNullOrEmpty();
await client.Close();
} }
[Fact] [Fact]
public async Task DynamicQueueDeclarePrefix() public async Task DynamicQueueDeclarePrefix()
{ {
var client = CreateCilent();
var queueName = await client.DynamicQueueDeclare("dynamicprefix", null, CancellationToken.None); var queueName = await client.DynamicQueueDeclare("dynamicprefix", null, CancellationToken.None);
queueName.Should().StartWith("dynamicprefix"); queueName.Should().StartWith("dynamicprefix");
}
await client.Close();
[Fact]
public async Task PublishHandleOverflow()
{
var queue1 = await client.DynamicQueueDeclare(null, new RabbitMQArguments
{
{ "x-max-length", 5 },
{ "x-overflow", "reject-publish" }
}, CancellationToken.None);
var queue2 = await client.DynamicQueueDeclare(null, null, CancellationToken.None);
var body = Encoding.UTF8.GetBytes("Hello world!");
var properties = new MessageProperties();
for (var i = 0; i < 5; i++)
await client.Publish(body, properties, null, queue1, true);
var publishOverMaxLength = () => client.Publish(body, properties, null, queue1, true);
await publishOverMaxLength.Should().ThrowAsync<NackException>();
// The channel should recover and allow further publishing
await client.Publish(body, properties, null, queue2, true);
} }
// TODO test the other methods // TODO test the other methods
private TapetiClient CreateCilent() private TapetiClient CreateClient()
{ {
return new TapetiClient( return new TapetiClient(
new TapetiConfig.Config(dependencyResolver), new TapetiConfig.Config(dependencyResolver),

View File

@ -18,7 +18,7 @@ namespace Tapeti.Tests.Config
private static readonly MockRepository MoqRepository = new(MockBehavior.Strict); private static readonly MockRepository MoqRepository = new(MockBehavior.Strict);
private readonly Mock<ITapetiClient> client; private readonly Mock<ITapetiClient> client;
private readonly Dictionary<string, IReadOnlyDictionary<string, string>> declaredQueues = new(); private readonly Dictionary<string, IRabbitMQArguments> declaredQueues = new();
public QueueArgumentsTest() public QueueArgumentsTest()
@ -45,8 +45,8 @@ namespace Tapeti.Tests.Config
var queue = 0; var queue = 0;
client client
.Setup(c => c.DynamicQueueDeclare(null, It.IsAny<IReadOnlyDictionary<string, string>>(), It.IsAny<CancellationToken>())) .Setup(c => c.DynamicQueueDeclare(null, It.IsAny<IRabbitMQArguments>(), It.IsAny<CancellationToken>()))
.Callback((string _, IReadOnlyDictionary<string, string> arguments, CancellationToken _) => .Callback((string _, IRabbitMQArguments arguments, CancellationToken _) =>
{ {
queue++; queue++;
declaredQueues.Add($"queue-{queue}", arguments); declaredQueues.Add($"queue-{queue}", arguments);
@ -54,8 +54,8 @@ namespace Tapeti.Tests.Config
.ReturnsAsync(() => $"queue-{queue}"); .ReturnsAsync(() => $"queue-{queue}");
client client
.Setup(c => c.DurableQueueDeclare(It.IsAny<string>(), It.IsAny<IEnumerable<QueueBinding>>(), It.IsAny<IReadOnlyDictionary<string, string>>(), It.IsAny<CancellationToken>())) .Setup(c => c.DurableQueueDeclare(It.IsAny<string>(), It.IsAny<IEnumerable<QueueBinding>>(), It.IsAny<IRabbitMQArguments>(), It.IsAny<CancellationToken>()))
.Callback((string queueName, IEnumerable<QueueBinding> _, IReadOnlyDictionary<string, string> arguments, CancellationToken _) => .Callback((string queueName, IEnumerable<QueueBinding> _, IRabbitMQArguments arguments, CancellationToken _) =>
{ {
declaredQueues.Add(queueName, arguments); declaredQueues.Add(queueName, arguments);
}) })
@ -89,10 +89,10 @@ namespace Tapeti.Tests.Config
var arguments = declaredQueues["queue-1"]; var arguments = declaredQueues["queue-1"];
arguments.Should().ContainKey("x-custom").WhoseValue.Should().Be("custom value"); arguments.Should().ContainKey("x-custom").WhoseValue.Should().Be("custom value");
arguments.Should().ContainKey("x-another").WhoseValue.Should().Be("another value"); arguments.Should().ContainKey("x-another").WhoseValue.Should().Be(true);
arguments.Should().ContainKey("x-max-length").WhoseValue.Should().Be("100"); arguments.Should().ContainKey("x-max-length").WhoseValue.Should().Be(100);
arguments.Should().ContainKey("x-max-length-bytes").WhoseValue.Should().Be("100000"); arguments.Should().ContainKey("x-max-length-bytes").WhoseValue.Should().Be(100000);
arguments.Should().ContainKey("x-message-ttl").WhoseValue.Should().Be("4269"); arguments.Should().ContainKey("x-message-ttl").WhoseValue.Should().Be(4269);
arguments.Should().ContainKey("x-overflow").WhoseValue.Should().Be("reject-publish"); arguments.Should().ContainKey("x-overflow").WhoseValue.Should().Be("reject-publish");
} }
@ -108,10 +108,10 @@ namespace Tapeti.Tests.Config
declaredQueues.Should().HaveCount(2); declaredQueues.Should().HaveCount(2);
var arguments1 = declaredQueues["queue-1"]; var arguments1 = declaredQueues["queue-1"];
arguments1.Should().ContainKey("x-max-length").WhoseValue.Should().Be("100"); arguments1.Should().ContainKey("x-max-length").WhoseValue.Should().Be(100);
var arguments2 = declaredQueues["queue-2"]; var arguments2 = declaredQueues["queue-2"];
arguments2.Should().ContainKey("x-max-length-bytes").WhoseValue.Should().Be("100000"); arguments2.Should().ContainKey("x-max-length-bytes").WhoseValue.Should().Be(100000);
} }
@ -148,7 +148,7 @@ namespace Tapeti.Tests.Config
[DynamicQueue] [DynamicQueue]
[QueueArguments("x-custom", "custom value", "x-another", "another value", MaxLength = 100, MaxLengthBytes = 100000, MessageTTL = 4269, Overflow = RabbitMQOverflow.RejectPublish)] [QueueArguments("x-custom", "custom value", "x-another", true, MaxLength = 100, MaxLengthBytes = 100000, MessageTTL = 4269, Overflow = RabbitMQOverflow.RejectPublish)]
private class TestController private class TestController
{ {
public void HandleMessage1(TestMessage1 message) public void HandleMessage1(TestMessage1 message)

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using Tapeti.Config; using Tapeti.Config;
using Tapeti.Connection;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace Tapeti.Tests.Mock namespace Tapeti.Tests.Mock
@ -49,7 +49,7 @@ namespace Tapeti.Tests.Mock
: $"Declaring {(durable ? "durable" : "dynamic")} queue {queueName}"); : $"Declaring {(durable ? "durable" : "dynamic")} queue {queueName}");
} }
public void QueueExistsWarning(string queueName, IReadOnlyDictionary<string, string> existingArguments, IReadOnlyDictionary<string, string> arguments) public void QueueExistsWarning(string queueName, IRabbitMQArguments existingArguments, IRabbitMQArguments arguments)
{ {
var argumentsText = new StringBuilder(); var argumentsText = new StringBuilder();
foreach (var pair in arguments) foreach (var pair in arguments)

View File

@ -1,6 +1,8 @@
using System; using System;
using Tapeti.Config; using Tapeti.Config;
// ReSharper disable UnusedMember.Global
namespace Tapeti.Transient namespace Tapeti.Transient
{ {
/// <summary> /// <summary>

View File

@ -4,7 +4,6 @@ using Tapeti.Config;
namespace Tapeti.Transient namespace Tapeti.Transient
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Implements a binding for transient request response messages. /// Implements a binding for transient request response messages.
/// Register this binding using the WithTransient config extension method. /// Register this binding using the WithTransient config extension method.

View File

@ -2,7 +2,6 @@
namespace Tapeti.Transient namespace Tapeti.Transient
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Default implementation of ITransientPublisher /// Default implementation of ITransientPublisher
/// </summary> /// </summary>

View File

@ -55,7 +55,7 @@ namespace Tapeti.Transient
public async Task<object> RequestResponse(IPublisher publisher, object request) public async Task<object> RequestResponse(IPublisher publisher, object request)
{ {
var correlation = Guid.NewGuid(); var correlation = Guid.NewGuid();
var tcs = map.GetOrAdd(correlation, c => new TaskCompletionSource<object>()); var tcs = map.GetOrAdd(correlation, _ => new TaskCompletionSource<object>());
try try
{ {
@ -77,7 +77,7 @@ namespace Tapeti.Transient
throw; throw;
} }
using (new Timer(TimeoutResponse, tcs, defaultTimeoutMs, -1)) await using (new Timer(TimeoutResponse, tcs, defaultTimeoutMs, -1))
{ {
return await tcs.Task; return await tcs.Task;
} }

View File

@ -1,35 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Authors>Mark van Renswoude</Authors>
<Company />
<Description>Unity container integration package for Tapeti</Description>
<PackageTags>rabbitmq tapeti unity</PackageTags>
<PackageLicenseExpression>Unlicense</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/MvRens/Tapeti</PackageProjectUrl>
<PackageIcon>Tapeti.SimpleInjector.png</PackageIcon>
<Version>2.0.0</Version>
<LangVersion>9</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Unity" Version="5.11.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Tapeti\Tapeti.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\resources\icons\Tapeti.SimpleInjector.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -1,90 +0,0 @@
using System;
using Unity;
using Unity.Lifetime;
namespace Tapeti.UnityContainer
{
/// <inheritdoc />
/// <summary>
/// Dependency resolver and container implementation for SimpleInjector.
/// </summary>
public class UnityDependencyResolver : IDependencyContainer
{
private readonly IUnityContainer container;
/// <summary>
/// </summary>
public UnityDependencyResolver(IUnityContainer container)
{
this.container = container;
}
/// <inheritdoc />
public T Resolve<T>() where T : class
{
return container.Resolve<T>();
}
/// <inheritdoc />
public object Resolve(Type type)
{
return container.Resolve(type);
}
/// <inheritdoc />
public void RegisterDefault<TService, TImplementation>() where TService : class where TImplementation : class, TService
{
if (container.IsRegistered(typeof(TService)))
return;
container.RegisterType<TService, TImplementation>();
}
/// <inheritdoc />
public void RegisterDefault<TService>(Func<TService> factory) where TService : class
{
if (container.IsRegistered(typeof(TService)))
return;
container.RegisterFactory<TService>(c => factory());
}
/// <inheritdoc />
public void RegisterDefaultSingleton<TService, TImplementation>() where TService : class where TImplementation : class, TService
{
if (container.IsRegistered(typeof(TService)))
return;
container.RegisterSingleton<TService, TImplementation>();
}
/// <inheritdoc />
public void RegisterDefaultSingleton<TService>(TService instance) where TService : class
{
if (container.IsRegistered(typeof(TService)))
return;
container.RegisterInstance(instance);
}
/// <inheritdoc />
public void RegisterDefaultSingleton<TService>(Func<TService> factory) where TService : class
{
if (container.IsRegistered(typeof(TService)))
return;
container.RegisterFactory<TService>(c => factory(), new SingletonLifetimeManager());
}
/// <inheritdoc />
public void RegisterController(Type type)
{
container.RegisterType(type);
}
}
}

View File

@ -45,8 +45,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.CastleWindsor", "Tap
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Autofac", "Tapeti.Autofac\Tapeti.Autofac.csproj", "{B3802005-C941-41B6-A9A5-20573A7C24AE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Autofac", "Tapeti.Autofac\Tapeti.Autofac.csproj", "{B3802005-C941-41B6-A9A5-20573A7C24AE}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.UnityContainer", "Tapeti.UnityContainer\Tapeti.UnityContainer.csproj", "{BA8CA9A2-BAFF-42BB-8439-3DD9D1F6C32E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Ninject", "Tapeti.Ninject\Tapeti.Ninject.csproj", "{29478B10-FC53-4E93-ADEF-A775D9408131}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Ninject", "Tapeti.Ninject\Tapeti.Ninject.csproj", "{29478B10-FC53-4E93-ADEF-A775D9408131}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "06-StatelessRequestResponse", "Examples\06-StatelessRequestResponse\06-StatelessRequestResponse.csproj", "{152227AA-3165-4550-8997-6EA80C84516E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "06-StatelessRequestResponse", "Examples\06-StatelessRequestResponse\06-StatelessRequestResponse.csproj", "{152227AA-3165-4550-8997-6EA80C84516E}"
@ -55,7 +53,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "07-ParallelizationTest", "E
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "08-MessageHandlerLogging", "Examples\08-MessageHandlerLogging\08-MessageHandlerLogging.csproj", "{906605A6-2CAB-4B29-B0DD-B735BF265E39}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "08-MessageHandlerLogging", "Examples\08-MessageHandlerLogging\08-MessageHandlerLogging.csproj", "{906605A6-2CAB-4B29-B0DD-B735BF265E39}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tapeti.Benchmarks", "Tapeti.Benchmarks\Tapeti.Benchmarks.csproj", "{DBE56131-9207-4CEA-BA3E-031351677C48}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Benchmarks", "Tapeti.Benchmarks\Tapeti.Benchmarks.csproj", "{DBE56131-9207-4CEA-BA3E-031351677C48}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -131,10 +129,6 @@ Global
{B3802005-C941-41B6-A9A5-20573A7C24AE}.Debug|Any CPU.Build.0 = Debug|Any CPU {B3802005-C941-41B6-A9A5-20573A7C24AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3802005-C941-41B6-A9A5-20573A7C24AE}.Release|Any CPU.ActiveCfg = Release|Any CPU {B3802005-C941-41B6-A9A5-20573A7C24AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3802005-C941-41B6-A9A5-20573A7C24AE}.Release|Any CPU.Build.0 = Release|Any CPU {B3802005-C941-41B6-A9A5-20573A7C24AE}.Release|Any CPU.Build.0 = Release|Any CPU
{BA8CA9A2-BAFF-42BB-8439-3DD9D1F6C32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA8CA9A2-BAFF-42BB-8439-3DD9D1F6C32E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA8CA9A2-BAFF-42BB-8439-3DD9D1F6C32E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA8CA9A2-BAFF-42BB-8439-3DD9D1F6C32E}.Release|Any CPU.Build.0 = Release|Any CPU
{29478B10-FC53-4E93-ADEF-A775D9408131}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {29478B10-FC53-4E93-ADEF-A775D9408131}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29478B10-FC53-4E93-ADEF-A775D9408131}.Debug|Any CPU.Build.0 = Debug|Any CPU {29478B10-FC53-4E93-ADEF-A775D9408131}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29478B10-FC53-4E93-ADEF-A775D9408131}.Release|Any CPU.ActiveCfg = Release|Any CPU {29478B10-FC53-4E93-ADEF-A775D9408131}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -176,7 +170,6 @@ Global
{330D05CE-5321-4C7D-8017-2070B891289E} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56} {330D05CE-5321-4C7D-8017-2070B891289E} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
{374AAE64-598B-4F67-8870-4A05168FF987} = {99380F97-AD1A-459F-8AB3-D404E1E6AD4F} {374AAE64-598B-4F67-8870-4A05168FF987} = {99380F97-AD1A-459F-8AB3-D404E1E6AD4F}
{B3802005-C941-41B6-A9A5-20573A7C24AE} = {99380F97-AD1A-459F-8AB3-D404E1E6AD4F} {B3802005-C941-41B6-A9A5-20573A7C24AE} = {99380F97-AD1A-459F-8AB3-D404E1E6AD4F}
{BA8CA9A2-BAFF-42BB-8439-3DD9D1F6C32E} = {99380F97-AD1A-459F-8AB3-D404E1E6AD4F}
{29478B10-FC53-4E93-ADEF-A775D9408131} = {99380F97-AD1A-459F-8AB3-D404E1E6AD4F} {29478B10-FC53-4E93-ADEF-A775D9408131} = {99380F97-AD1A-459F-8AB3-D404E1E6AD4F}
{152227AA-3165-4550-8997-6EA80C84516E} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56} {152227AA-3165-4550-8997-6EA80C84516E} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
{E69E6BA5-68E7-4A4D-A38C-B2526AA66E96} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56} {E69E6BA5-68E7-4A4D-A38C-B2526AA66E96} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}

View File

@ -4,7 +4,9 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=JSON/@EntryIndexedValue">JSON</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=JSON/@EntryIndexedValue">JSON</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KV/@EntryIndexedValue">KV</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KV/@EntryIndexedValue">KV</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MQ/@EntryIndexedValue">MQ</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UTF/@EntryIndexedValue">UTF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>

View File

@ -1,6 +1,5 @@
namespace Tapeti.Config namespace Tapeti.Config
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Extends the message context with information about the controller. /// Extends the message context with information about the controller.
/// </summary> /// </summary>

View File

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Tapeti.Connection;
namespace Tapeti.Config namespace Tapeti.Config
{ {
@ -82,7 +82,7 @@ namespace Tapeti.Config
/// <param name="messageClass">The message class to be bound to the queue</param> /// <param name="messageClass">The message class to be bound to the queue</param>
/// <param name="queueName">The name of the durable queue</param> /// <param name="queueName">The name of the durable queue</param>
/// <param name="arguments">Optional arguments</param> /// <param name="arguments">Optional arguments</param>
ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments); ValueTask BindDurable(Type messageClass, string queueName, IRabbitMQArguments arguments);
/// <summary> /// <summary>
/// Binds the messageClass to a dynamic auto-delete queue. /// Binds the messageClass to a dynamic auto-delete queue.
@ -95,7 +95,7 @@ namespace Tapeti.Config
/// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param> /// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param>
/// <param name="arguments">Optional arguments</param> /// <param name="arguments">Optional arguments</param>
/// <returns>The generated name of the dynamic queue</returns> /// <returns>The generated name of the dynamic queue</returns>
ValueTask<string> BindDynamic(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments); ValueTask<string> BindDynamic(Type messageClass, string queuePrefix, IRabbitMQArguments arguments);
/// <summary> /// <summary>
/// Declares a durable queue but does not add a binding for a messageClass' routing key. /// Declares a durable queue but does not add a binding for a messageClass' routing key.
@ -103,7 +103,7 @@ namespace Tapeti.Config
/// </summary> /// </summary>
/// <param name="queueName">The name of the durable queue</param> /// <param name="queueName">The name of the durable queue</param>
/// <param name="arguments">Optional arguments</param> /// <param name="arguments">Optional arguments</param>
ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments); ValueTask BindDurableDirect(string queueName, IRabbitMQArguments arguments);
/// <summary> /// <summary>
/// Declares a dynamic queue but does not add a binding for a messageClass' routing key. /// Declares a dynamic queue but does not add a binding for a messageClass' routing key.
@ -113,7 +113,7 @@ namespace Tapeti.Config
/// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param> /// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param>
/// <param name="arguments">Optional arguments</param> /// <param name="arguments">Optional arguments</param>
/// <returns>The generated name of the dynamic queue</returns> /// <returns>The generated name of the dynamic queue</returns>
ValueTask<string> BindDynamicDirect(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments); ValueTask<string> BindDynamicDirect(Type messageClass, string queuePrefix, IRabbitMQArguments arguments);
/// <summary> /// <summary>
/// Declares a dynamic queue but does not add a binding for a messageClass' routing key. /// Declares a dynamic queue but does not add a binding for a messageClass' routing key.
@ -122,7 +122,7 @@ namespace Tapeti.Config
/// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param> /// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param>
/// <param name="arguments">Optional arguments</param> /// <param name="arguments">Optional arguments</param>
/// <returns>The generated name of the dynamic queue</returns> /// <returns>The generated name of the dynamic queue</returns>
ValueTask<string> BindDynamicDirect(string queuePrefix, IReadOnlyDictionary<string, string> arguments); ValueTask<string> BindDynamicDirect(string queuePrefix, IRabbitMQArguments arguments);
/// <summary> /// <summary>
/// Marks the specified durable queue as having an obsolete binding. If after all bindings have subscribed, the queue only contains obsolete /// Marks the specified durable queue as having an obsolete binding. If after all bindings have subscribed, the queue only contains obsolete

View File

@ -2,7 +2,6 @@
namespace Tapeti.Config namespace Tapeti.Config
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Called when a Controller method is registered. /// Called when a Controller method is registered.
/// </summary> /// </summary>

View File

@ -3,7 +3,6 @@ using System.Threading.Tasks;
namespace Tapeti.Config namespace Tapeti.Config
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Denotes middleware that runs before the controller is instantiated. /// Denotes middleware that runs before the controller is instantiated.
/// </summary> /// </summary>

View File

@ -3,7 +3,6 @@ using System.Reflection;
namespace Tapeti.Config namespace Tapeti.Config
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Represents a binding to a method in a controller class to handle incoming messages. /// Represents a binding to a method in a controller class to handle incoming messages.
/// </summary> /// </summary>

View File

@ -77,7 +77,6 @@ namespace Tapeti.Config
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Contains a list of registered bindings, with a few added helpers. /// Contains a list of registered bindings, with a few added helpers.
/// </summary> /// </summary>

View File

@ -2,7 +2,6 @@
namespace Tapeti.Config namespace Tapeti.Config
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Provides a way for Tapeti extensions to register custom bindings. /// Provides a way for Tapeti extensions to register custom bindings.
/// </summary> /// </summary>

View File

@ -6,7 +6,6 @@ using Tapeti.Config;
namespace Tapeti.Connection namespace Tapeti.Connection
{ {
/// <inheritdoc cref="IEquatable{T}" />
/// <summary> /// <summary>
/// Defines a queue binding to an exchange using a routing key /// Defines a queue binding to an exchange using a routing key
/// </summary> /// </summary>
@ -52,6 +51,18 @@ namespace Tapeti.Connection
return ((Exchange != null ? Exchange.GetHashCode() : 0) * 397) ^ (RoutingKey != null ? RoutingKey.GetHashCode() : 0); return ((Exchange != null ? Exchange.GetHashCode() : 0) * 397) ^ (RoutingKey != null ? RoutingKey.GetHashCode() : 0);
} }
} }
/// <summary></summary>
public static bool operator ==(QueueBinding left, QueueBinding right)
{
return left.Equals(right);
}
/// <summary></summary>
public static bool operator !=(QueueBinding left, QueueBinding right)
{
return !left.Equals(right);
}
} }
@ -93,7 +104,7 @@ namespace Tapeti.Connection
/// <param name="bindings">A list of bindings. Any bindings already on the queue which are not in this list will be removed</param> /// <param name="bindings">A list of bindings. Any bindings already on the queue which are not in this list will be removed</param>
/// <param name="arguments">Optional arguments</param> /// <param name="arguments">Optional arguments</param>
/// <param name="cancellationToken">Cancelled when the connection is lost</param> /// <param name="cancellationToken">Cancelled when the connection is lost</param>
Task DurableQueueDeclare(string queueName, IEnumerable<QueueBinding> bindings, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken); Task DurableQueueDeclare(string queueName, IEnumerable<QueueBinding> bindings, IRabbitMQArguments arguments, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Verifies a durable queue exists. Will raise an exception if it does not. /// Verifies a durable queue exists. Will raise an exception if it does not.
@ -101,7 +112,7 @@ namespace Tapeti.Connection
/// <param name="queueName">The name of the queue to verify</param> /// <param name="queueName">The name of the queue to verify</param>
/// <param name="arguments">Optional arguments</param> /// <param name="arguments">Optional arguments</param>
/// <param name="cancellationToken">Cancelled when the connection is lost</param> /// <param name="cancellationToken">Cancelled when the connection is lost</param>
Task DurableQueueVerify(string queueName, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken); Task DurableQueueVerify(string queueName, IRabbitMQArguments arguments, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Deletes a durable queue. /// Deletes a durable queue.
@ -117,7 +128,7 @@ namespace Tapeti.Connection
/// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param> /// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param>
/// <param name="arguments">Optional arguments</param> /// <param name="arguments">Optional arguments</param>
/// <param name="cancellationToken">Cancelled when the connection is lost</param> /// <param name="cancellationToken">Cancelled when the connection is lost</param>
Task<string> DynamicQueueDeclare(string queuePrefix, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken); Task<string> DynamicQueueDeclare(string queuePrefix, IRabbitMQArguments arguments, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Add a binding to a dynamic queue. /// Add a binding to a dynamic queue.

View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Text;
namespace Tapeti.Connection
{
/// <inheritdoc />
public interface IRabbitMQArguments : IReadOnlyDictionary<string, object>
{
}
internal class RabbitMQArguments : Dictionary<string, object>, IRabbitMQArguments
{
public RabbitMQArguments()
{
}
#if NETSTANDARD2_1_OR_GREATER
public RabbitMQArguments(IReadOnlyDictionary<string, object> values) : base(values)
{
}
#else
public RabbitMQArguments(IReadOnlyDictionary<string, object> values)
{
foreach (var pair in values)
Add(pair.Key, pair.Value);
}
#endif
public void AddUTF8(string key, string value)
{
Add(key, Encoding.UTF8.GetBytes(value));
}
}
}

View File

@ -14,7 +14,6 @@ namespace Tapeti.Connection
public delegate Task ResponseFunc(long expectedConnectionReference, ulong deliveryTag, ConsumeResult result); public delegate Task ResponseFunc(long expectedConnectionReference, ulong deliveryTag, ConsumeResult result);
/// <inheritdoc />
/// <summary> /// <summary>
/// Implements the bridge between the RabbitMQ Client consumer and a Tapeti Consumer /// Implements the bridge between the RabbitMQ Client consumer and a Tapeti Consumer
/// </summary> /// </summary>

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions; using RabbitMQ.Client.Exceptions;
@ -24,7 +25,6 @@ namespace Tapeti.Connection
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Implementation of ITapetiClient for the RabbitMQ Client library /// Implementation of ITapetiClient for the RabbitMQ Client library
/// </summary> /// </summary>
@ -185,15 +185,18 @@ namespace Tapeti.Connection
var replyCode = publishResultTask.Result; var replyCode = publishResultTask.Result;
// There is no RabbitMQ.Client.Framing.Constants value for this "No route" reply code switch (replyCode)
// at the time of writing... {
if (replyCode == 312) // There is no RabbitMQ.Client.Framing.Constants value for this "No route" reply code
throw new NoRouteException( // at the time of writing...
$"Mandatory message with exchange '{exchange}' and routing key '{routingKey}' does not have a route"); case 312:
throw new NoRouteException(
$"Mandatory message with exchange '{exchange}' and routing key '{routingKey}' does not have a route");
if (replyCode > 0) case > 0:
throw new NoRouteException( throw new NoRouteException(
$"Mandatory message with exchange '{exchange}' and routing key '{routingKey}' could not be delivered, reply code: {replyCode}"); $"Mandatory message with exchange '{exchange}' and routing key '{routingKey}' could not be delivered, reply code: {replyCode}");
}
}); });
} }
@ -286,7 +289,7 @@ namespace Tapeti.Connection
} }
private async Task<bool> GetDurableQueueDeclareRequired(string queueName, IReadOnlyDictionary<string, string> arguments) private async Task<bool> GetDurableQueueDeclareRequired(string queueName, IRabbitMQArguments arguments)
{ {
var existingQueue = await GetQueueInfo(queueName); var existingQueue = await GetQueueInfo(queueName);
if (existingQueue == null) if (existingQueue == null)
@ -298,17 +301,44 @@ namespace Tapeti.Connection
if (arguments == null && existingQueue.Arguments.Count == 0) if (arguments == null && existingQueue.Arguments.Count == 0)
return true; return true;
if (existingQueue.Arguments.NullSafeSameValues(arguments)) var existingArguments = ConvertJsonArguments(existingQueue.Arguments);
if (existingArguments.NullSafeSameValues(arguments))
return true; return true;
(logger as IBindingLogger)?.QueueExistsWarning(queueName, existingQueue.Arguments, arguments); (logger as IBindingLogger)?.QueueExistsWarning(queueName, existingArguments, arguments);
return false; return false;
} }
private static RabbitMQArguments ConvertJsonArguments(IReadOnlyDictionary<string, JObject> arguments)
{
if (arguments == null)
return null;
var result = new RabbitMQArguments();
foreach (var pair in arguments)
{
// ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault - by design
object value = pair.Value.Type switch
{
JTokenType.Integer => pair.Value.Value<int>(),
JTokenType.Float => pair.Value.Value<double>(),
JTokenType.String => Encoding.UTF8.GetBytes(pair.Value.Value<string>() ?? string.Empty),
JTokenType.Boolean => pair.Value.Value<bool>(),
JTokenType.Null => null,
_ => throw new ArgumentOutOfRangeException(nameof(arguments))
};
result.Add(pair.Key, value);
}
return result;
}
/// <inheritdoc /> /// <inheritdoc />
public async Task DurableQueueDeclare(string queueName, IEnumerable<QueueBinding> bindings, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken) public async Task DurableQueueDeclare(string queueName, IEnumerable<QueueBinding> bindings, IRabbitMQArguments arguments, CancellationToken cancellationToken)
{ {
var declareRequired = await GetDurableQueueDeclareRequired(queueName, arguments); var declareRequired = await GetDurableQueueDeclareRequired(queueName, arguments);
@ -343,17 +373,16 @@ namespace Tapeti.Connection
} }
private static IDictionary<string, object> GetDeclareArguments(IReadOnlyDictionary<string, string> arguments) private static IDictionary<string, object> GetDeclareArguments(IRabbitMQArguments arguments)
{ {
if (arguments == null || arguments.Count == 0) return arguments == null || arguments.Count == 0
return null; ? null
: arguments.ToDictionary(p => p.Key, p => p.Value);
return arguments.ToDictionary(p => p.Key, p => (object)Encoding.UTF8.GetBytes(p.Value));
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task DurableQueueVerify(string queueName, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken) public async Task DurableQueueVerify(string queueName, IRabbitMQArguments arguments, CancellationToken cancellationToken)
{ {
if (!await GetDurableQueueDeclareRequired(queueName, arguments)) if (!await GetDurableQueueDeclareRequired(queueName, arguments))
return; return;
@ -455,7 +484,7 @@ namespace Tapeti.Connection
/// <inheritdoc /> /// <inheritdoc />
public async Task<string> DynamicQueueDeclare(string queuePrefix, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken) public async Task<string> DynamicQueueDeclare(string queuePrefix, IRabbitMQArguments arguments, CancellationToken cancellationToken)
{ {
string queueName = null; string queueName = null;
var bindingLogger = logger as IBindingLogger; var bindingLogger = logger as IBindingLogger;
@ -564,7 +593,7 @@ namespace Tapeti.Connection
public bool Exclusive { get; set; } public bool Exclusive { get; set; }
[JsonProperty("arguments")] [JsonProperty("arguments")]
public Dictionary<string, string> Arguments { get; set; } public Dictionary<string, JObject> Arguments { get; set; }
[JsonProperty("messages")] [JsonProperty("messages")]
public uint Messages { get; set; } public uint Messages { get; set; }
@ -675,7 +704,7 @@ namespace Tapeti.Connection
} }
catch (WebException e) catch (WebException e)
{ {
if (!(e.Response is HttpWebResponse response)) if (e.Response is not HttpWebResponse response)
throw; throw;
if (!TransientStatusCodes.Contains(response.StatusCode)) if (!TransientStatusCodes.Contains(response.StatusCode))

View File

@ -9,7 +9,6 @@ using Tapeti.Helpers;
namespace Tapeti.Connection namespace Tapeti.Connection
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Implements a RabbitMQ consumer to pass messages to the Tapeti middleware. /// Implements a RabbitMQ consumer to pass messages to the Tapeti middleware.
/// </summary> /// </summary>
@ -172,8 +171,7 @@ namespace Tapeti.Connection
return e switch return e switch
{ {
AggregateException aggregateException => aggregateException.InnerExceptions.Any(IgnoreExceptionDuringShutdown), AggregateException aggregateException => aggregateException.InnerExceptions.Any(IgnoreExceptionDuringShutdown),
TaskCanceledException _ => true, OperationCanceledException => true,
OperationCanceledException _ => true,
_ => e.InnerException != null && IgnoreExceptionDuringShutdown(e.InnerException) _ => e.InnerException != null && IgnoreExceptionDuringShutdown(e.InnerException)
}; };
} }

View File

@ -151,11 +151,11 @@ namespace Tapeti.Connection
private class PublishContext : IPublishContext private class PublishContext : IPublishContext
{ {
public ITapetiConfig Config { get; set; } public ITapetiConfig Config { get; init; }
public string Exchange { get; set; } public string Exchange { get; set; }
public string RoutingKey { get; set; } public string RoutingKey { get; init; }
public object Message { get; set; } public object Message { get; init; }
public IMessageProperties Properties { get; set; } public IMessageProperties Properties { get; init; }
} }
} }
} }

View File

@ -72,14 +72,12 @@ namespace Tapeti.Connection
/// </summary> /// </summary>
public void Reconnect() public void Reconnect()
{ {
CancellationToken cancellationToken;
initializeCancellationTokenSource?.Cancel(); initializeCancellationTokenSource?.Cancel();
initializeCancellationTokenSource = new CancellationTokenSource(); initializeCancellationTokenSource = new CancellationTokenSource();
consumerTags.Clear(); consumerTags.Clear();
cancellationToken = initializeCancellationTokenSource.Token; var cancellationToken = initializeCancellationTokenSource.Token;
Task.Run(async () => Task.Run(async () =>
{ {
@ -166,7 +164,7 @@ namespace Tapeti.Connection
{ {
public string QueueName; public string QueueName;
public List<Type> MessageClasses; public List<Type> MessageClasses;
public IReadOnlyDictionary<string, string> Arguments; public IRabbitMQArguments Arguments;
} }
private readonly Dictionary<string, List<DynamicQueueInfo>> dynamicQueues = new(); private readonly Dictionary<string, List<DynamicQueueInfo>> dynamicQueues = new();
@ -187,12 +185,12 @@ namespace Tapeti.Connection
} }
public abstract ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments); public abstract ValueTask BindDurable(Type messageClass, string queueName, IRabbitMQArguments arguments);
public abstract ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments); public abstract ValueTask BindDurableDirect(string queueName, IRabbitMQArguments arguments);
public abstract ValueTask BindDurableObsolete(string queueName); public abstract ValueTask BindDurableObsolete(string queueName);
public async ValueTask<string> BindDynamic(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments) public async ValueTask<string> BindDynamic(Type messageClass, string queuePrefix, IRabbitMQArguments arguments)
{ {
var result = await DeclareDynamicQueue(messageClass, queuePrefix, arguments); var result = await DeclareDynamicQueue(messageClass, queuePrefix, arguments);
if (!result.IsNewMessageClass) if (!result.IsNewMessageClass)
@ -207,14 +205,14 @@ namespace Tapeti.Connection
} }
public async ValueTask<string> BindDynamicDirect(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments) public async ValueTask<string> BindDynamicDirect(Type messageClass, string queuePrefix, IRabbitMQArguments arguments)
{ {
var result = await DeclareDynamicQueue(messageClass, queuePrefix, arguments); var result = await DeclareDynamicQueue(messageClass, queuePrefix, arguments);
return result.QueueName; return result.QueueName;
} }
public async ValueTask<string> BindDynamicDirect(string queuePrefix, IReadOnlyDictionary<string, string> arguments) public async ValueTask<string> BindDynamicDirect(string queuePrefix, IRabbitMQArguments arguments)
{ {
// If we don't know the routing key, always create a new queue to ensure there is no overlap. // If we don't know the routing key, always create a new queue to ensure there is no overlap.
// Keep it out of the dynamicQueues dictionary, so it can't be re-used later on either. // Keep it out of the dynamicQueues dictionary, so it can't be re-used later on either.
@ -228,7 +226,7 @@ namespace Tapeti.Connection
public bool IsNewMessageClass; public bool IsNewMessageClass;
} }
private async Task<DeclareDynamicQueueResult> DeclareDynamicQueue(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments) private async Task<DeclareDynamicQueueResult> DeclareDynamicQueue(Type messageClass, string queuePrefix, IRabbitMQArguments arguments)
{ {
// Group by prefix // Group by prefix
var key = queuePrefix ?? ""; var key = queuePrefix ?? "";
@ -284,7 +282,7 @@ namespace Tapeti.Connection
private struct DurableQueueInfo private struct DurableQueueInfo
{ {
public List<Type> MessageClasses; public List<Type> MessageClasses;
public IReadOnlyDictionary<string, string> Arguments; public IRabbitMQArguments Arguments;
} }
@ -297,7 +295,7 @@ namespace Tapeti.Connection
} }
public override ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments) public override ValueTask BindDurable(Type messageClass, string queueName, IRabbitMQArguments arguments)
{ {
// Collect the message classes per queue so we can determine afterwards // Collect the message classes per queue so we can determine afterwards
// if any of the bindings currently set on the durable queue are no // if any of the bindings currently set on the durable queue are no
@ -326,7 +324,7 @@ namespace Tapeti.Connection
} }
public override ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments) public override ValueTask BindDurableDirect(string queueName, IRabbitMQArguments arguments)
{ {
if (!durableQueues.TryGetValue(queueName, out var durableQueueInfo)) if (!durableQueues.TryGetValue(queueName, out var durableQueueInfo))
{ {
@ -398,12 +396,12 @@ namespace Tapeti.Connection
} }
public override async ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments) public override async ValueTask BindDurable(Type messageClass, string queueName, IRabbitMQArguments arguments)
{ {
await VerifyDurableQueue(queueName, arguments); await VerifyDurableQueue(queueName, arguments);
} }
public override async ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments) public override async ValueTask BindDurableDirect(string queueName, IRabbitMQArguments arguments)
{ {
await VerifyDurableQueue(queueName, arguments); await VerifyDurableQueue(queueName, arguments);
} }
@ -414,7 +412,7 @@ namespace Tapeti.Connection
} }
private async Task VerifyDurableQueue(string queueName, IReadOnlyDictionary<string, string> arguments) private async Task VerifyDurableQueue(string queueName, IRabbitMQArguments arguments)
{ {
if (!durableQueues.Add(queueName)) if (!durableQueues.Add(queueName))
return; return;
@ -431,12 +429,12 @@ namespace Tapeti.Connection
} }
public override ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments) public override ValueTask BindDurable(Type messageClass, string queueName, IRabbitMQArguments arguments)
{ {
return default; return default;
} }
public override ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments) public override ValueTask BindDurableDirect(string queueName, IRabbitMQArguments arguments)
{ {
return default; return default;
} }

View File

@ -5,7 +5,6 @@ using Tapeti.Config;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Binds a parameter of type CancellationToken to a token which is cancelled when the RabbitMQ connection is closed. /// Binds a parameter of type CancellationToken to a token which is cancelled when the RabbitMQ connection is closed.
/// Similar to and very much inspired by ASP.NET's RequestAborted CancellationToken. /// Similar to and very much inspired by ASP.NET's RequestAborted CancellationToken.

View File

@ -1,13 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using Tapeti.Config; using Tapeti.Config;
using Tapeti.Connection;
// ReSharper disable UnusedMember.Global - public API // ReSharper disable UnusedMember.Global - public API
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Default ILogger implementation for console applications. /// Default ILogger implementation for console applications.
/// </summary> /// </summary>
@ -81,13 +80,13 @@ namespace Tapeti.Default
} }
/// <inheritdoc /> /// <inheritdoc />
public void QueueExistsWarning(string queueName, IReadOnlyDictionary<string, string> existingArguments, IReadOnlyDictionary<string, string> arguments) public void QueueExistsWarning(string queueName, IRabbitMQArguments existingArguments, IRabbitMQArguments arguments)
{ {
Console.WriteLine($"[Tapeti] Durable queue {queueName} exists with incompatible x-arguments ({GetArgumentsText(existingArguments)} vs. {GetArgumentsText(arguments)}) and will not be redeclared, queue will be consumed as-is"); Console.WriteLine($"[Tapeti] Durable queue {queueName} exists with incompatible x-arguments ({GetArgumentsText(existingArguments)} vs. {GetArgumentsText(arguments)}) and will not be redeclared, queue will be consumed as-is");
} }
private static string GetArgumentsText(IReadOnlyDictionary<string, string> arguments) private static string GetArgumentsText(IRabbitMQArguments arguments)
{ {
var argumentsText = new StringBuilder(); var argumentsText = new StringBuilder();
foreach (var pair in arguments) foreach (var pair in arguments)

View File

@ -99,7 +99,6 @@ namespace Tapeti.Default
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Default implementation for IBindingParameter /// Default implementation for IBindingParameter
/// </summary> /// </summary>
@ -139,7 +138,6 @@ namespace Tapeti.Default
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Default implementation for IBindingResult /// Default implementation for IBindingResult
/// </summary> /// </summary>

View File

@ -4,11 +4,11 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Tapeti.Config; using Tapeti.Config;
using Tapeti.Connection;
using Tapeti.Helpers; using Tapeti.Helpers;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Binding implementation for controller methods. Do not instantiate this class yourself, /// Binding implementation for controller methods. Do not instantiate this class yourself,
/// instead use the ITapetiConfigBuilder RegisterController / RegisterAllControllers extension /// instead use the ITapetiConfigBuilder RegisterController / RegisterAllControllers extension
@ -319,7 +319,7 @@ namespace Tapeti.Default
/// <summary> /// <summary>
/// Optional arguments (x-arguments) passed when declaring the queue. /// Optional arguments (x-arguments) passed when declaring the queue.
/// </summary> /// </summary>
public IReadOnlyDictionary<string, string> QueueArguments { get; set; } public IRabbitMQArguments QueueArguments { get; set; }
/// <summary> /// <summary>
/// Determines if the QueueInfo properties contain a valid combination. /// Determines if the QueueInfo properties contain a valid combination.

View File

@ -4,7 +4,6 @@ using Tapeti.Config;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Attempts to resolve any unhandled parameters to Controller methods using the IoC container. /// Attempts to resolve any unhandled parameters to Controller methods using the IoC container.
/// This middleware is included by default in the standard TapetiConfig. /// This middleware is included by default in the standard TapetiConfig.

View File

@ -3,7 +3,6 @@ using Tapeti.Config;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Default ILogger implementation which does not log anything. /// Default ILogger implementation which does not log anything.
/// </summary> /// </summary>

View File

@ -4,7 +4,6 @@ using Newtonsoft.Json;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Converts an <see cref="T:System.Enum" /> to and from its name string value. If an unknown string value is encountered /// Converts an <see cref="T:System.Enum" /> to and from its name string value. If an unknown string value is encountered
/// it will translate to 0xDEADBEEF (-559038737) so it can be gracefully handled. /// it will translate to 0xDEADBEEF (-559038737) so it can be gracefully handled.

View File

@ -6,7 +6,6 @@ using Tapeti.Config;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// IMessageSerializer implementation for JSON encoding and decoding using Newtonsoft.Json. /// IMessageSerializer implementation for JSON encoding and decoding using Newtonsoft.Json.
/// </summary> /// </summary>
@ -49,7 +48,7 @@ namespace Tapeti.Default
/// <inheritdoc /> /// <inheritdoc />
public object Deserialize(byte[] body, IMessageProperties properties) public object Deserialize(byte[] body, IMessageProperties properties)
{ {
if (!(properties.ContentType is ContentType)) if (properties.ContentType is not ContentType)
throw new ArgumentException($"content_type must be {ContentType}"); throw new ArgumentException($"content_type must be {ContentType}");
var typeName = properties.GetHeader(ClassTypeHeader); var typeName = properties.GetHeader(ClassTypeHeader);

View File

@ -3,7 +3,6 @@ using Tapeti.Config;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Gets the message class from the first parameter of a controller method. /// Gets the message class from the first parameter of a controller method.
/// This middleware is included by default in the standard TapetiConfig. /// This middleware is included by default in the standard TapetiConfig.

View File

@ -4,7 +4,6 @@ using Tapeti.Config;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// IMessagePropertiesReader implementation for providing properties manually /// IMessagePropertiesReader implementation for providing properties manually
/// </summary> /// </summary>

View File

@ -3,7 +3,6 @@ using Tapeti.Config;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Default implementation of an exception strategy which marks the messages as Error. /// Default implementation of an exception strategy which marks the messages as Error.
/// </summary> /// </summary>

View File

@ -3,7 +3,6 @@ using System.Text.RegularExpressions;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// IExchangeStrategy implementation which uses the first identifier in the namespace in lower case, /// IExchangeStrategy implementation which uses the first identifier in the namespace in lower case,
/// skipping the first identifier if it is 'Messaging'. /// skipping the first identifier if it is 'Messaging'.

View File

@ -8,7 +8,6 @@ using Tapeti.Helpers;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Attempts to publish a return value for Controller methods as a response to the incoming message. /// Attempts to publish a return value for Controller methods as a response to the incoming message.
/// </summary> /// </summary>

View File

@ -5,7 +5,6 @@ using Tapeti.Config;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Example exception strategy which requeues all messages that result in an error. /// Example exception strategy which requeues all messages that result in an error.
/// </summary> /// </summary>

View File

@ -7,7 +7,6 @@ using Tapeti.Helpers;
namespace Tapeti.Default namespace Tapeti.Default
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// IRoutingKeyStrategy implementation which transforms the class name into a dot-separated routing key based /// IRoutingKeyStrategy implementation which transforms the class name into a dot-separated routing key based
/// on the casing. Accounts for acronyms. If the class name ends with 'Message' it is not included in the routing key. /// on the casing. Accounts for acronyms. If the class name ends with 'Message' it is not included in the routing key.

View File

@ -2,7 +2,6 @@
namespace Tapeti.Exceptions namespace Tapeti.Exceptions
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Raised when a message is nacked by the message bus. /// Raised when a message is nacked by the message bus.
/// </summary> /// </summary>

View File

@ -2,7 +2,6 @@
namespace Tapeti.Exceptions namespace Tapeti.Exceptions
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Raised when a mandatory message has no route. /// Raised when a mandatory message has no route.
/// </summary> /// </summary>

View File

@ -10,7 +10,7 @@ namespace Tapeti.Helpers
/// <summary> /// <summary>
/// Checks if two dictionaries are considered compatible. If either is null they are considered empty. /// Checks if two dictionaries are considered compatible. If either is null they are considered empty.
/// </summary> /// </summary>
public static bool NullSafeSameValues(this IReadOnlyDictionary<string, string> arguments1, IReadOnlyDictionary<string, string> arguments2) public static bool NullSafeSameValues(this IReadOnlyDictionary<string, object> arguments1, IReadOnlyDictionary<string, object> arguments2)
{ {
if (arguments1 == null || arguments2 == null) if (arguments1 == null || arguments2 == null)
return (arguments1 == null || arguments1.Count == 0) && (arguments2 == null || arguments2.Count == 0); return (arguments1 == null || arguments1.Count == 0) && (arguments2 == null || arguments2.Count == 0);

View File

@ -23,7 +23,6 @@ namespace Tapeti
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Allows registering controller classes into the IoC container. Also registers default implementations, /// Allows registering controller classes into the IoC container. Also registers default implementations,
/// so that the calling application may override these. /// so that the calling application may override these.

View File

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic;
using Tapeti.Config; using Tapeti.Config;
using Tapeti.Connection;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedMemberInSuper.Global // ReSharper disable UnusedMemberInSuper.Global
@ -24,7 +24,6 @@ namespace Tapeti
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Contains information about the failed connection. /// Contains information about the failed connection.
/// </summary> /// </summary>
@ -37,7 +36,6 @@ namespace Tapeti
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Contains information about the established connection. /// Contains information about the established connection.
/// </summary> /// </summary>
@ -140,7 +138,7 @@ namespace Tapeti
/// <param name="queueName">The name of the queue that is declared</param> /// <param name="queueName">The name of the queue that is declared</param>
/// <param name="existingArguments">The x-arguments of the existing queue</param> /// <param name="existingArguments">The x-arguments of the existing queue</param>
/// <param name="arguments">The x-arguments of the queue that would be declared</param> /// <param name="arguments">The x-arguments of the queue that would be declared</param>
void QueueExistsWarning(string queueName, IReadOnlyDictionary<string, string> existingArguments, IReadOnlyDictionary<string, string> arguments); void QueueExistsWarning(string queueName, IRabbitMQArguments existingArguments, IRabbitMQArguments arguments);
/// <summary> /// <summary>
/// Called before a binding is added to a queue. /// Called before a binding is added to a queue.

View File

@ -54,7 +54,6 @@ namespace Tapeti
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Low-level publisher for Tapeti internal use. /// Low-level publisher for Tapeti internal use.
/// </summary> /// </summary>

View File

@ -23,8 +23,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.*" />
<PackageReference Include="RabbitMQ.Client" Version="6.4.0" /> <PackageReference Include="RabbitMQ.Client" Version="6.*" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -1,89 +0,0 @@
using System.Configuration;
using System.Linq;
// ReSharper disable UnusedMember.Global
namespace Tapeti
{
/// <inheritdoc />
/// <summary>
/// Implementation of TapetiConnectionParams which reads the values from the AppSettings.
/// </summary>
/// <list type="table">
/// <listheader>
/// <description>AppSettings keys</description>
/// </listheader>
/// <item><description>rabbitmq:hostname</description></item>
/// <item><description>rabbitmq:port</description></item>
/// <item><description>rabbitmq:virtualhost</description></item>
/// <item><description>rabbitmq:username</description></item>
/// <item><description>rabbitmq:password</description></item>
/// <item><description>rabbitmq:prefetchcount</description></item>
/// <item><description>rabbitmq:managementport</description></item>
/// <item><description>rabbitmq:clientproperty:*</description></item>
/// </list>
public class TapetiAppSettingsConnectionParams : TapetiConnectionParams
{
private const string DefaultPrefix = "rabbitmq:";
// ReSharper disable InconsistentNaming
private const string KeyHostname = "hostname";
private const string KeyPort = "port";
private const string KeyVirtualHost = "virtualhost";
private const string KeyUsername = "username";
private const string KeyPassword = "password";
private const string KeyPrefetchCount = "prefetchcount";
private const string KeyManagementPort = "managementport";
private const string KeyClientProperty = "clientproperty:";
// ReSharper restore InconsistentNaming
private readonly struct AppSettingsKey
{
public readonly string Entry;
public readonly string Parameter;
public AppSettingsKey(string entry, string parameter)
{
Entry = entry;
Parameter = parameter;
}
}
/// <inheritdoc />
/// <summary></summary>
/// <param name="prefix">The prefix to apply to the keys. Defaults to "rabbitmq:"</param>
public TapetiAppSettingsConnectionParams(string prefix = DefaultPrefix)
{
var keys = !string.IsNullOrEmpty(prefix)
? ConfigurationManager.AppSettings.AllKeys.Where(k => k.StartsWith(prefix)).Select(k => new AppSettingsKey(k, k.Substring(prefix.Length)))
: ConfigurationManager.AppSettings.AllKeys.Select(k => new AppSettingsKey(k, k));
foreach (var key in keys)
{
var value = ConfigurationManager.AppSettings[key.Entry];
if (key.Parameter.StartsWith(KeyClientProperty))
{
ClientProperties.Add(key.Parameter.Substring(KeyClientProperty.Length), value);
}
else
{
// ReSharper disable once SwitchStatementMissingSomeCases - don't fail if we encounter an unknown value
switch (key.Parameter)
{
case KeyHostname: HostName = value; break;
case KeyPort: Port = int.Parse(value); break;
case KeyVirtualHost: VirtualHost = value; break;
case KeyUsername: Username = value; break;
case KeyPassword: Password = value; break;
case KeyPrefetchCount: PrefetchCount = ushort.Parse(value); break;
case KeyManagementPort: ManagementPort = int.Parse(value); break;
}
}
}
}
}
}

View File

@ -189,7 +189,7 @@ namespace Tapeti
/// </summary> /// </summary>
protected void RegisterDefaults() protected void RegisterDefaults()
{ {
if (!(DependencyResolver is IDependencyContainer container)) if (DependencyResolver is not IDependencyContainer container)
return; return;
if (ConsoleHelper.IsAvailable()) if (ConsoleHelper.IsAvailable())

View File

@ -1,16 +1,15 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Tapeti.Annotations; using Tapeti.Annotations;
using Tapeti.Config; using Tapeti.Config;
using Tapeti.Connection;
using Tapeti.Default; using Tapeti.Default;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
namespace Tapeti namespace Tapeti
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Thrown when an issue is detected in a controller configuration. /// Thrown when an issue is detected in a controller configuration.
/// </summary> /// </summary>
@ -171,40 +170,34 @@ namespace Tapeti
} }
private static IReadOnlyDictionary<string, string> GetQueueArguments(QueueArgumentsAttribute queueArgumentsAttribute) private static IRabbitMQArguments GetQueueArguments(QueueArgumentsAttribute queueArgumentsAttribute)
{ {
if (queueArgumentsAttribute == null) if (queueArgumentsAttribute == null)
return null; return null;
#if NETSTANDARD2_1_OR_GREATER var arguments = new RabbitMQArguments(queueArgumentsAttribute.CustomArguments);
var arguments = new Dictionary<string, string>(queueArgumentsAttribute.CustomArguments);
#else
var arguments = new Dictionary<string, string>();
foreach (var pair in queueArgumentsAttribute.CustomArguments)
arguments.Add(pair.Key, pair.Value);
#endif
if (queueArgumentsAttribute.MaxLength > 0) if (queueArgumentsAttribute.MaxLength > 0)
arguments.Add(@"x-max-length", queueArgumentsAttribute.MaxLength.ToString()); arguments.Add(@"x-max-length", queueArgumentsAttribute.MaxLength);
if (queueArgumentsAttribute.MaxLengthBytes > 0) if (queueArgumentsAttribute.MaxLengthBytes > 0)
arguments.Add(@"x-max-length-bytes", queueArgumentsAttribute.MaxLengthBytes.ToString()); arguments.Add(@"x-max-length-bytes", queueArgumentsAttribute.MaxLengthBytes);
if (queueArgumentsAttribute.MessageTTL > 0) if (queueArgumentsAttribute.MessageTTL > 0)
arguments.Add(@"x-message-ttl", queueArgumentsAttribute.MessageTTL.ToString()); arguments.Add(@"x-message-ttl", queueArgumentsAttribute.MessageTTL);
switch (queueArgumentsAttribute.Overflow) switch (queueArgumentsAttribute.Overflow)
{ {
case RabbitMQOverflow.NotSpecified: case RabbitMQOverflow.NotSpecified:
break; break;
case RabbitMQOverflow.DropHead: case RabbitMQOverflow.DropHead:
arguments.Add(@"x-overflow", @"drop-head"); arguments.AddUTF8(@"x-overflow", @"drop-head");
break; break;
case RabbitMQOverflow.RejectPublish: case RabbitMQOverflow.RejectPublish:
arguments.Add(@"x-overflow", @"reject-publish"); arguments.AddUTF8(@"x-overflow", @"reject-publish");
break; break;
case RabbitMQOverflow.RejectPublishDeadletter: case RabbitMQOverflow.RejectPublishDeadletter:
arguments.Add(@"x-overflow", @"reject-publish-dlx"); arguments.AddUTF8(@"x-overflow", @"reject-publish-dlx");
break; break;
default: default:
throw new ArgumentOutOfRangeException(nameof(queueArgumentsAttribute.Overflow), queueArgumentsAttribute.Overflow, "Unsupported Overflow value"); throw new ArgumentOutOfRangeException(nameof(queueArgumentsAttribute.Overflow), queueArgumentsAttribute.Overflow, "Unsupported Overflow value");

View File

@ -9,7 +9,6 @@ using Tapeti.Connection;
namespace Tapeti namespace Tapeti
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// Creates a connection to RabbitMQ based on the provided Tapeti config. /// Creates a connection to RabbitMQ based on the provided Tapeti config.
/// </summary> /// </summary>

View File

@ -6,7 +6,6 @@ using System.Threading.Tasks;
namespace Tapeti.Tasks namespace Tapeti.Tasks
{ {
/// <inheritdoc />
/// <summary> /// <summary>
/// An implementation of a queue which runs tasks on a single thread. /// An implementation of a queue which runs tasks on a single thread.
/// </summary> /// </summary>