Merge pull request 'Added translators feature' (#8) from Translators into main

Reviewed-on: #8
This commit is contained in:
Alexander Kozachenko 2023-11-24 06:45:46 +00:00
commit 7bfdd64d25
10 changed files with 122 additions and 7 deletions

View File

@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProSol.Messaging", "src\fixMessaging.csproj", "{89203112-653F-4A81-A3B9-9B711E5F6A6B}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProSol.Messaging", "src\Messaging.csproj", "{89203112-653F-4A81-A3B9-9B711E5F6A6B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Messaging.Tests", "tests\Messaging.Tests.csproj", "{9D9B8827-380D-4AE5-8BA5-F4056935C13A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -18,5 +20,9 @@ Global
{89203112-653F-4A81-A3B9-9B711E5F6A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89203112-653F-4A81-A3B9-9B711E5F6A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89203112-653F-4A81-A3B9-9B711E5F6A6B}.Release|Any CPU.Build.0 = Release|Any CPU
{9D9B8827-380D-4AE5-8BA5-F4056935C13A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D9B8827-380D-4AE5-8BA5-F4056935C13A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D9B8827-380D-4AE5-8BA5-F4056935C13A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D9B8827-380D-4AE5-8BA5-F4056935C13A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -10,5 +10,5 @@ public interface IPublisher<TMessage>
/// </summary>
/// <param name="subscriber"> Target. </param>
/// <returns> Custom IDisposable implementation. </returns>
Unsubscriber<TMessage> Subscribe(ISubscriber<TMessage> subscriber);
}
IDisposable Subscribe(ISubscriber<TMessage> subscriber);
}

View File

@ -7,4 +7,9 @@
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>

View File

@ -1,11 +1,15 @@
namespace ProSol.Messaging;
using ProSol.Messaging.Translating;
namespace ProSol.Messaging;
/// <summary>
/// Collects a subscribers and publishes a messages to them.
/// Provides a pipeline mechanics, so a subscriber is able to stop message processing.
/// </summary>
/// <typeparam name="TMessage"> Type of message. </typeparam>
public class PipelinePublisher<TMessage> : IPublisher<TMessage>
public class PipelineMessagePublisher<TMessage> :
IPublisher<TMessage>,
ITranslatorPublisher<TMessage>
{
private readonly HashSet<ISubscriber<TMessage>> subscribers = [];
@ -14,12 +18,20 @@ public class PipelinePublisher<TMessage> : IPublisher<TMessage>
/// </summary>
/// <param name="subscriber"> An implementation of <see cref="ISubscriber"/> or <see cref="SubscriptionBase"/> interfaces. </param>
/// <returns> Unsubscriber. </returns>
public Unsubscriber<TMessage> Subscribe(ISubscriber<TMessage> subscriber)
public IDisposable Subscribe(ISubscriber<TMessage> subscriber)
{
subscribers.Add(subscriber);
return new Unsubscriber<TMessage>(subscribers, subscriber);
}
/// <summary>
/// Subscribes a subscriber in different message format.
/// </summary>
public IDisposable Subscribe<TDest>(TranslatorBase<TMessage, TDest> translator)
{
return Subscribe(translator);
}
/// <summary>
/// Pushes the message to subscribers.
/// </summary>
@ -49,7 +61,7 @@ public class PipelinePublisher<TMessage> : IPublisher<TMessage>
return;
}
NextDelegate next = () => PublishInternal(subscribers.Skip(1), message);
void next() => PublishInternal(subscribers.Skip(1), message);
subscribers.First().OnNext(message, next);
}
}

View File

@ -0,0 +1,6 @@
namespace ProSol.Messaging.Translating;
public interface ITranslatorPublisher<TMessage>
{
IDisposable Subscribe<TDest>(TranslatorBase<TMessage, TDest> translatorBase);
}

View File

@ -0,0 +1,8 @@
namespace ProSol.Messaging.Translating;
internal sealed class SimpleTranslator<TSource, TDest>(ISubscriber<TDest> destSubscriber, Func<TSource, TDest> converter)
: TranslatorBase<TSource, TDest>(destSubscriber)
{
protected override TDest ConvertMessage(TSource message)
=> converter(message);
}

View File

@ -0,0 +1,17 @@
namespace ProSol.Messaging.Translating;
public abstract class TranslatorBase<TSource, TDest>(ISubscriber<TDest> destSubscriber) : ISubscriber<TSource>
{
private readonly ISubscriber<TDest> destSubscriber = destSubscriber;
/// <inheritdoc/>
public void OnCompleted() => destSubscriber.OnCompleted();
/// <inheritdoc/>
public void OnNext(TSource message, NextDelegate next) => destSubscriber.OnNext(ConvertMessage(message), next);
protected abstract TDest ConvertMessage(TSource message);
public static TranslatorBase<TSource, TDest> Create(ISubscriber<TDest> destSubscriber, Func<TSource, TDest> converter)
=> new SimpleTranslator<TSource, TDest>(destSubscriber, converter);
}

1
tests/GlobalUsings.cs Normal file
View File

@ -0,0 +1 @@
global using NUnit.Framework;

View File

@ -0,0 +1,35 @@
using FakeItEasy;
using ProSol.Messaging;
using ProSol.Messaging.Translating;
namespace Messaging.Tests;
public class MessageTranslatorTests
{
[Test]
public void SimpleCase_OneToOne_ShouldWork()
{
// Arrange configs.
string[] input = ["Hello", "World!"];
int[] expected = [5, 6];
static int converter(string message) => message.Length;
List<int> messages = [];
// Arrange operations.
var fakeListener = A.Fake<ISubscriber<int>>();
A.CallTo(() => fakeListener.OnNext(A<int>._, A<NextDelegate>._))
.Invokes((int i, NextDelegate next) => messages.Add(i));
var publisher = new PipelineMessagePublisher<string>();
publisher.Subscribe(TranslatorBase<string, int>.Create(fakeListener, converter));
// Act.
foreach(var item in input)
{
publisher.Publish(item);
}
// Assert.
Assert.That(messages, Is.EquivalentTo(expected));
}
}

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\Messaging.csproj" />
</ItemGroup>
</Project>