Skip to content

Commit

Permalink
Add Mediatr functional extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
rungwiroon committed Nov 15, 2023
1 parent dfb6cb6 commit 939eede
Show file tree
Hide file tree
Showing 21 changed files with 437 additions and 8 deletions.
14 changes: 14 additions & 0 deletions src/Codehard.Core.sln
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codehard.Functional.Marten"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codehard.Functional.Marten.Tests", "Codehard.Functional\Codehard.Functional.Marten.Tests\Codehard.Functional.Marten.Tests.csproj", "{BCB6D4C3-DD27-43AD-B0E9-6C8DE6821AD5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codehard.Functional.MediatR", "Codehard.Functional\Codehard.Functional.Mediatr\Codehard.Functional.MediatR.csproj", "{8952083F-5A2A-4A37-9039-8588B08CA6EE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codehard.Functional.MediatR.Tests", "Codehard.Functional\Codehard.Functional.Mediatr.Tests\Codehard.Functional.MediatR.Tests.csproj", "{569F15A1-ABDE-4037-825C-90164D9074E2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -143,6 +147,14 @@ Global
{BCB6D4C3-DD27-43AD-B0E9-6C8DE6821AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BCB6D4C3-DD27-43AD-B0E9-6C8DE6821AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCB6D4C3-DD27-43AD-B0E9-6C8DE6821AD5}.Release|Any CPU.Build.0 = Release|Any CPU
{8952083F-5A2A-4A37-9039-8588B08CA6EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8952083F-5A2A-4A37-9039-8588B08CA6EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8952083F-5A2A-4A37-9039-8588B08CA6EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8952083F-5A2A-4A37-9039-8588B08CA6EE}.Release|Any CPU.Build.0 = Release|Any CPU
{569F15A1-ABDE-4037-825C-90164D9074E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{569F15A1-ABDE-4037-825C-90164D9074E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{569F15A1-ABDE-4037-825C-90164D9074E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{569F15A1-ABDE-4037-825C-90164D9074E2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -173,5 +185,7 @@ Global
{6277AC9F-217C-4F89-8589-51FCB4E9BADF} = {2F98EF48-527C-44C5-8FC3-65F25C808AC9}
{8D62918D-C27F-45AF-B26A-437B594C0D5C} = {0C257E94-AD98-4AFB-93B7-B6F64EB7D2BA}
{BCB6D4C3-DD27-43AD-B0E9-6C8DE6821AD5} = {0C257E94-AD98-4AFB-93B7-B6F64EB7D2BA}
{8952083F-5A2A-4A37-9039-8588B08CA6EE} = {0C257E94-AD98-4AFB-93B7-B6F64EB7D2BA}
{569F15A1-ABDE-4037-825C-90164D9074E2} = {0C257E94-AD98-4AFB-93B7-B6F64EB7D2BA}
EndGlobalSection
EndGlobal
3 changes: 2 additions & 1 deletion src/Codehard.Core.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Codehard/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Codehard/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mediat/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="LanguageExt.Core" Version="4.4.3" />
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
<PackageReference Update="FSharp.Core" Version="7.0.300" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LanguageExt.Core" Version="4.4.3" />
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.9" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<ItemGroup>
<PackageReference Include="FSharp.Core" Version="7.0.300" />
<PackageReference Include="LanguageExt.Core" Version="4.4.3" />
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>3.0.0-preview-1</Version>
<Version>3.0.0-preview-2</Version>
<Description>A functional extensions for Marten.</Description>
<PackageProjectUrl>https://github.com/codehardth/Codehard.Functional</PackageProjectUrl>
<RepositoryUrl>https://github.com/codehardth/Codehard.Functional</RepositoryUrl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ public static class DocumentSessionExtensions
/// <summary>
/// Save changes to the database
/// </summary>
public static Aff<Unit> SaveChangesAff(this IDocumentSession documentSession)
public static Aff<Unit> SaveChangesAff(
this IDocumentSession documentSession, CancellationToken cancellationToken = default)
{
return Aff(async () => await documentSession.SaveChangesAsync().ToUnit());
return Aff(
async () =>
await documentSession.SaveChangesAsync(cancellationToken).ToUnit());
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ namespace Marten;

public static class QueryableExtensions
{
/// <summary>
/// Asynchronously converts an IQueryable&lt;T&gt; into a read-only list within an Aff monad.
/// </summary>
/// <typeparam name="T">The type of elements in the IQueryable.</typeparam>
/// <param name="source">The IQueryable to be converted to a read-only list.</param>
/// <param name="ct">A CancellationToken to observe while waiting for the task to complete.</param>
/// <returns>An Aff&lt;IReadOnlyList&lt;T&gt;&gt; representing the asynchronous operation.
/// The Aff monad wraps the result, which is the read-only list of elements.</returns>
public static Aff<IReadOnlyList<T>> ToListAff<T>(this IQueryable<T> source, CancellationToken ct = default)
{
return Aff(async () => await source.ToListAsync(ct));
}

/// <summary>
/// Asynchronously returns the only element of a sequence, or a None value if the sequence is empty;
/// this method returns an Option&lt;TSource&gt;.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>

<AssemblyName>Codehard.Functional.MediatR.Tests</AssemblyName>

<RootNamespace>Codehard.Functional.MediatR.Tests</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Lamar" Version="12.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0"/>
<PackageReference Include="Shouldly" Version="4.2.1" />
<PackageReference Include="xunit" Version="2.4.1"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Codehard.Functional\Codehard.Functional.Mediatr\Codehard.Functional.Mediatr.csproj" />
<ProjectReference Include="..\..\Codehard.Functional\Codehard.Functional\Codehard.Functional.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Text;
using Lamar;
using MediatR;
using Shouldly;

namespace Codehard.Functional.MediatR.Tests;

public class PublishTests
{
public class Ping : INotification
{
public string Message { get; init; }
}

[Fact]
public async Task WhenPublishMessage_ShouldNotifidEachHandlersCorrectly()
{
// Arrange
var builder = new StringBuilder();
var writer = new StringWriter(builder);
var container = BuildMediatr();

// Act
var mediator = container.GetInstance<IMediator>();

await mediator.Publish(new Ping { Message = "Ping" });

// Assert
var result = builder.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);
result.ShouldContain("Ping Pong");
result.ShouldContain("Ping Pung");

Container BuildMediatr()
{
var container = new Container(cfg =>
{
cfg.Scan(scanner =>
{
scanner.AssemblyContainingType(typeof(PublishTests));
scanner.IncludeNamespaceContainingType<Ping>();
scanner.WithDefaultConventions();
scanner.AddAllTypesOf(typeof (INotificationHandler<>));
});
cfg.For<TextWriter>().Use(writer);
cfg.For<IMediator>().Use<Mediator>();
});

return container;
}
}

public class PongHandler : INotificationHandler<Ping>
{
private readonly TextWriter _writer;

public PongHandler(TextWriter writer)
{
_writer = writer;
}

public Task Handle(Ping notification, CancellationToken cancellationToken)
{
return _writer.WriteLineAsync(notification.Message + " Pong");
}
}

public class PungHandler : INotificationHandler<Ping>
{
private readonly TextWriter _writer;

public PungHandler(TextWriter writer)
{
_writer = writer;
}

public Task Handle(Ping notification, CancellationToken cancellationToken)
{
return _writer.WriteLineAsync(notification.Message + " Pung");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System.Text;
using Lamar;
using LanguageExt;
using MediatR;
using Shouldly;

namespace Codehard.Functional.MediatR.Tests;

public class QueryTests
{
public class Ping : IQuery<PongQueryResult>
{
public string? Message { get; set; }
}

public class PingNotFound : IQuery<PongQueryResult>
{
public string? Message { get; set; }
}

public class Pong
{
public string? Message { get; set; }
}

public class PingHandler
: IQueryHandler<Ping, PongQueryResult>,
IQueryHandler<PingNotFound, PongQueryResult>
{
public Task<Fin<PongQueryResult>> Handle(Ping request, CancellationToken cancellationToken)
{
return Task.FromResult(
Fin<PongQueryResult>.Succ(
new PongQueryResult.Success(new Pong { Message = request.Message + " Pong" })));
}

public Task<Fin<PongQueryResult>> Handle(PingNotFound request, CancellationToken cancellationToken)
{
return Task.FromResult(
Fin<PongQueryResult>.Fail(
new ExpectedResultError(new PongQueryResult.NotFound())));
}
}

public abstract record PongQueryResult
{
private PongQueryResult()
{
}

public sealed record Success(Pong Pong) : PongQueryResult;

public sealed record NotFound : PongQueryResult;
}

[Fact]
public async Task WhenSendQuery_ShouldResponseCorrectly()
{
// Arrange
var builder = new StringBuilder();
var writer = new StringWriter(builder);
var container = BuildMediatr();

// Act
var mediator = container.GetInstance<IMediator>();

var response =
await mediator
.SendQueryAff<Ping, PongQueryResult>(new Ping { Message = "Ping" })
.MapExpectedResultError()
.Run();

// Assert
Assert.True(response.IsSucc);

var result = response.ThrowIfFail();

var successValue = result.ShouldBeOfType<PongQueryResult.Success>();

successValue.Pong.Message.ShouldBe("Ping Pong");

return;

Container BuildMediatr()
{
var container = new Container(cfg =>
{
cfg.Scan(scanner =>
{
scanner.AssemblyContainingType(typeof(QueryTests));
scanner.IncludeNamespaceContainingType<Ping>();
scanner.WithDefaultConventions();
scanner.AddAllTypesOf(typeof(IRequestHandler<,>));
});
cfg.For<IMediator>().Use<Mediator>();
});

return container;
}
}

[Fact]
public async Task WhenSendNotFoundQuery_ShouldResponseNotFoundCorrectly()
{
// Arrange
var builder = new StringBuilder();
var writer = new StringWriter(builder);
var container = BuildMediatr();

// Act
var mediator = container.GetInstance<IMediator>();

var response =
await mediator
.SendQueryAff<PingNotFound, PongQueryResult>(new PingNotFound { Message = "Ping" })
.MapExpectedResultError()
.Run();

// Assert
Assert.True(response.IsSucc);

var result = response.ThrowIfFail();

result.ShouldBeOfType<PongQueryResult.NotFound>();

return;

Container BuildMediatr()
{
var container = new Container(cfg =>
{
cfg.Scan(scanner =>
{
scanner.AssemblyContainingType(typeof(QueryTests));
scanner.IncludeNamespaceContainingType<Ping>();
scanner.WithDefaultConventions();
scanner.AddAllTypesOf(typeof(IRequestHandler<,>));
});
cfg.For<IMediator>().Use<Mediator>();
});

return container;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Codehard.Functional.MediatR</AssemblyName>
<RootNamespace>Codehard.Functional.MediatR</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
<PackageReference Include="MediatR" Version="12.1.1" />
</ItemGroup>

</Project>
Loading

0 comments on commit 939eede

Please sign in to comment.