diff --git a/src/Codehard.Core.sln b/src/Codehard.Core.sln
index 1d58b2a..74b53d9 100644
--- a/src/Codehard.Core.sln
+++ b/src/Codehard.Core.sln
@@ -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
@@ -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
@@ -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
diff --git a/src/Codehard.Core.sln.DotSettings b/src/Codehard.Core.sln.DotSettings
index f78115f..649abbd 100644
--- a/src/Codehard.Core.sln.DotSettings
+++ b/src/Codehard.Core.sln.DotSettings
@@ -1,2 +1,3 @@
- True
\ No newline at end of file
+ True
+ True
\ No newline at end of file
diff --git a/src/Codehard.Functional/CodeHard.Function.FSharp.Tests.Types/CodeHard.Functional.FSharp.Tests.Types.fsproj b/src/Codehard.Functional/CodeHard.Function.FSharp.Tests.Types/CodeHard.Functional.FSharp.Tests.Types.fsproj
index 2c92068..78d3020 100644
--- a/src/Codehard.Functional/CodeHard.Function.FSharp.Tests.Types/CodeHard.Functional.FSharp.Tests.Types.fsproj
+++ b/src/Codehard.Functional/CodeHard.Function.FSharp.Tests.Types/CodeHard.Functional.FSharp.Tests.Types.fsproj
@@ -10,7 +10,7 @@
-
+
diff --git a/src/Codehard.Functional/Codehard.Functional.EntityFramework/Codehard.Functional.EntityFramework.csproj b/src/Codehard.Functional/Codehard.Functional.EntityFramework/Codehard.Functional.EntityFramework.csproj
index b448570..72f7cf1 100644
--- a/src/Codehard.Functional/Codehard.Functional.EntityFramework/Codehard.Functional.EntityFramework.csproj
+++ b/src/Codehard.Functional/Codehard.Functional.EntityFramework/Codehard.Functional.EntityFramework.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/Codehard.Functional/Codehard.Functional.FSharp/Codehard.Functional.FSharp.csproj b/src/Codehard.Functional/Codehard.Functional.FSharp/Codehard.Functional.FSharp.csproj
index 0e5f807..ea7c1d5 100644
--- a/src/Codehard.Functional/Codehard.Functional.FSharp/Codehard.Functional.FSharp.csproj
+++ b/src/Codehard.Functional/Codehard.Functional.FSharp/Codehard.Functional.FSharp.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/Codehard.Functional/Codehard.Functional.Marten/Codehard.Functional.Marten.csproj b/src/Codehard.Functional/Codehard.Functional.Marten/Codehard.Functional.Marten.csproj
index 01eea4e..1a77991 100644
--- a/src/Codehard.Functional/Codehard.Functional.Marten/Codehard.Functional.Marten.csproj
+++ b/src/Codehard.Functional/Codehard.Functional.Marten/Codehard.Functional.Marten.csproj
@@ -4,7 +4,7 @@
net6.0
enable
enable
- 3.0.0-preview-1
+ 3.0.0-preview-2
A functional extensions for Marten.
https://github.com/codehardth/Codehard.Functional
https://github.com/codehardth/Codehard.Functional
diff --git a/src/Codehard.Functional/Codehard.Functional.Marten/DocumentSessionExtensions.cs b/src/Codehard.Functional/Codehard.Functional.Marten/DocumentSessionExtensions.cs
index be1b04b..7df6e31 100644
--- a/src/Codehard.Functional/Codehard.Functional.Marten/DocumentSessionExtensions.cs
+++ b/src/Codehard.Functional/Codehard.Functional.Marten/DocumentSessionExtensions.cs
@@ -13,9 +13,12 @@ public static class DocumentSessionExtensions
///
/// Save changes to the database
///
- public static Aff SaveChangesAff(this IDocumentSession documentSession)
+ public static Aff SaveChangesAff(
+ this IDocumentSession documentSession, CancellationToken cancellationToken = default)
{
- return Aff(async () => await documentSession.SaveChangesAsync().ToUnit());
+ return Aff(
+ async () =>
+ await documentSession.SaveChangesAsync(cancellationToken).ToUnit());
}
///
diff --git a/src/Codehard.Functional/Codehard.Functional.Marten/QueryableExtensions.cs b/src/Codehard.Functional/Codehard.Functional.Marten/QueryableExtensions.cs
index 546ebeb..ea0184c 100644
--- a/src/Codehard.Functional/Codehard.Functional.Marten/QueryableExtensions.cs
+++ b/src/Codehard.Functional/Codehard.Functional.Marten/QueryableExtensions.cs
@@ -8,6 +8,19 @@ namespace Marten;
public static class QueryableExtensions
{
+ ///
+ /// Asynchronously converts an IQueryable<T> into a read-only list within an Aff monad.
+ ///
+ /// The type of elements in the IQueryable.
+ /// The IQueryable to be converted to a read-only list.
+ /// A CancellationToken to observe while waiting for the task to complete.
+ /// An Aff<IReadOnlyList<T>> representing the asynchronous operation.
+ /// The Aff monad wraps the result, which is the read-only list of elements.
+ public static Aff> ToListAff(this IQueryable source, CancellationToken ct = default)
+ {
+ return Aff(async () => await source.ToListAsync(ct));
+ }
+
///
/// Asynchronously returns the only element of a sequence, or a None value if the sequence is empty;
/// this method returns an Option<TSource>.
diff --git a/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/Codehard.Functional.MediatR.Tests.csproj b/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/Codehard.Functional.MediatR.Tests.csproj
new file mode 100644
index 0000000..a0a0193
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/Codehard.Functional.MediatR.Tests.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+
+ Codehard.Functional.MediatR.Tests
+
+ Codehard.Functional.MediatR.Tests
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/PublishTests.cs b/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/PublishTests.cs
new file mode 100644
index 0000000..000f9ab
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/PublishTests.cs
@@ -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();
+
+ 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();
+ scanner.WithDefaultConventions();
+ scanner.AddAllTypesOf(typeof (INotificationHandler<>));
+ });
+ cfg.For().Use(writer);
+ cfg.For().Use();
+ });
+
+ return container;
+ }
+ }
+
+ public class PongHandler : INotificationHandler
+ {
+ 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
+ {
+ private readonly TextWriter _writer;
+
+ public PungHandler(TextWriter writer)
+ {
+ _writer = writer;
+ }
+
+ public Task Handle(Ping notification, CancellationToken cancellationToken)
+ {
+ return _writer.WriteLineAsync(notification.Message + " Pung");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/QueryTests.cs b/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/QueryTests.cs
new file mode 100644
index 0000000..081abe1
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/QueryTests.cs
@@ -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
+ {
+ public string? Message { get; set; }
+ }
+
+ public class PingNotFound : IQuery
+ {
+ public string? Message { get; set; }
+ }
+
+ public class Pong
+ {
+ public string? Message { get; set; }
+ }
+
+ public class PingHandler
+ : IQueryHandler,
+ IQueryHandler
+ {
+ public Task> Handle(Ping request, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(
+ Fin.Succ(
+ new PongQueryResult.Success(new Pong { Message = request.Message + " Pong" })));
+ }
+
+ public Task> Handle(PingNotFound request, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(
+ Fin.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();
+
+ var response =
+ await mediator
+ .SendQueryAff(new Ping { Message = "Ping" })
+ .MapExpectedResultError()
+ .Run();
+
+ // Assert
+ Assert.True(response.IsSucc);
+
+ var result = response.ThrowIfFail();
+
+ var successValue = result.ShouldBeOfType();
+
+ successValue.Pong.Message.ShouldBe("Ping Pong");
+
+ return;
+
+ Container BuildMediatr()
+ {
+ var container = new Container(cfg =>
+ {
+ cfg.Scan(scanner =>
+ {
+ scanner.AssemblyContainingType(typeof(QueryTests));
+ scanner.IncludeNamespaceContainingType();
+ scanner.WithDefaultConventions();
+ scanner.AddAllTypesOf(typeof(IRequestHandler<,>));
+ });
+ cfg.For().Use();
+ });
+
+ 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();
+
+ var response =
+ await mediator
+ .SendQueryAff(new PingNotFound { Message = "Ping" })
+ .MapExpectedResultError()
+ .Run();
+
+ // Assert
+ Assert.True(response.IsSucc);
+
+ var result = response.ThrowIfFail();
+
+ result.ShouldBeOfType();
+
+ return;
+
+ Container BuildMediatr()
+ {
+ var container = new Container(cfg =>
+ {
+ cfg.Scan(scanner =>
+ {
+ scanner.AssemblyContainingType(typeof(QueryTests));
+ scanner.IncludeNamespaceContainingType();
+ scanner.WithDefaultConventions();
+ scanner.AddAllTypesOf(typeof(IRequestHandler<,>));
+ });
+ cfg.For().Use();
+ });
+
+ return container;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/Usings.cs b/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/Usings.cs
new file mode 100644
index 0000000..8c927eb
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.MediatR.Tests/Usings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional.Mediatr/Codehard.Functional.Mediatr.csproj b/src/Codehard.Functional/Codehard.Functional.Mediatr/Codehard.Functional.Mediatr.csproj
new file mode 100644
index 0000000..bcd2170
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.Mediatr/Codehard.Functional.Mediatr.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net6.0
+ enable
+ enable
+ Codehard.Functional.MediatR
+ Codehard.Functional.MediatR
+
+
+
+
+
+
+
+
diff --git a/src/Codehard.Functional/Codehard.Functional.Mediatr/ICommand.cs b/src/Codehard.Functional/Codehard.Functional.Mediatr/ICommand.cs
new file mode 100644
index 0000000..eb89cdc
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.Mediatr/ICommand.cs
@@ -0,0 +1,8 @@
+using LanguageExt;
+
+// ReSharper disable once CheckNamespace
+namespace MediatR;
+
+public interface ICommand : IRequest>
+{
+}
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional.Mediatr/ICommandHandler.cs b/src/Codehard.Functional/Codehard.Functional.Mediatr/ICommandHandler.cs
new file mode 100644
index 0000000..166a122
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.Mediatr/ICommandHandler.cs
@@ -0,0 +1,9 @@
+using LanguageExt;
+
+// ReSharper disable once CheckNamespace
+namespace MediatR;
+
+public interface ICommandHandler : IRequestHandler>
+ where TCommand : ICommand
+{
+}
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional.Mediatr/IMediatorExtensions.cs b/src/Codehard.Functional/Codehard.Functional.Mediatr/IMediatorExtensions.cs
new file mode 100644
index 0000000..7b350b4
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.Mediatr/IMediatorExtensions.cs
@@ -0,0 +1,33 @@
+using LanguageExt;
+
+using static LanguageExt.Prelude;
+
+// ReSharper disable once CheckNamespace
+namespace MediatR;
+
+public static class IMediatorExtensions
+{
+ public static Aff SendCommandAff(
+ this IMediator mediator,
+ TCommand command)
+ where TCommand : ICommand
+ {
+ return
+ Aff(async () =>
+ await mediator.Send(command)
+ .Map(x => x.ToAff()))
+ .Flatten();
+ }
+
+ public static Aff SendQueryAff(
+ this IMediator mediator,
+ TQuery query)
+ where TQuery : IQuery
+ {
+ return
+ Aff(async () =>
+ await mediator.Send(query)
+ .Map(x => x.ToAff()))
+ .Flatten();
+ }
+}
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional.Mediatr/IQuery.cs b/src/Codehard.Functional/Codehard.Functional.Mediatr/IQuery.cs
new file mode 100644
index 0000000..90030c0
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.Mediatr/IQuery.cs
@@ -0,0 +1,8 @@
+using LanguageExt;
+
+// ReSharper disable once CheckNamespace
+namespace MediatR;
+
+public interface IQuery : IRequest>
+{
+}
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional.Mediatr/IQueryHandler.cs b/src/Codehard.Functional/Codehard.Functional.Mediatr/IQueryHandler.cs
new file mode 100644
index 0000000..e05dce1
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.Mediatr/IQueryHandler.cs
@@ -0,0 +1,9 @@
+using LanguageExt;
+
+// ReSharper disable once CheckNamespace
+namespace MediatR;
+
+public interface IQueryHandler : IRequestHandler>
+ where TQuery : IQuery
+{
+}
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional/Codehard.Functional.csproj b/src/Codehard.Functional/Codehard.Functional/Codehard.Functional.csproj
index 1102e51..10b53fd 100644
--- a/src/Codehard.Functional/Codehard.Functional/Codehard.Functional.csproj
+++ b/src/Codehard.Functional/Codehard.Functional/Codehard.Functional.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/Codehard.Functional/Codehard.Functional/ExpectedResultError.cs b/src/Codehard.Functional/Codehard.Functional/ExpectedResultError.cs
new file mode 100644
index 0000000..02dcb7b
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional/ExpectedResultError.cs
@@ -0,0 +1,29 @@
+using LanguageExt.Common;
+
+namespace Codehard.Functional;
+
+public record ExpectedResultError : Expected
+{
+ public ExpectedResultError(object errorObject, Error? inner)
+ : base(string.Empty, 0, Optional(inner))
+ {
+ this.ErrorObject = errorObject;
+ }
+
+ public ExpectedResultError(object errorObject, Exception? exception = null)
+ : base(string.Empty, 0, exception == null ? Option.None : Option.Some(ErrorException.New(exception)))
+ {
+ this.ErrorObject = errorObject;
+ }
+
+ public override ErrorException ToErrorException() => ErrorException.New(this.ErrorObject.ToString() ?? string.Empty);
+
+ public override string ToString() => this.ErrorObject.ToString() ?? string.Empty;
+
+ public object ErrorObject { get; init; }
+
+ public void Deconstruct(out object errorObject)
+ {
+ errorObject = this.ErrorObject;
+ }
+}
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional/ExpectedResultErrorExtensions.cs b/src/Codehard.Functional/Codehard.Functional/ExpectedResultErrorExtensions.cs
new file mode 100644
index 0000000..4d4198a
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional/ExpectedResultErrorExtensions.cs
@@ -0,0 +1,24 @@
+using LanguageExt.Common;
+
+namespace Codehard.Functional;
+
+public static class ExpectedResultErrorExtensions
+{
+ public static Aff MapExpectedResultError(
+ this Error error)
+ {
+ return
+ error is ExpectedResultError { ErrorObject: TResult } expected
+ ? SuccessAff((TResult)expected.ErrorObject)
+ : FailAff(error);
+ }
+
+ public static Aff MapExpectedResultError(
+ this Aff aff)
+ {
+ return
+ aff.MatchAff(
+ Succ: SuccessAff,
+ Fail: err => err.MapExpectedResultError());
+ }
+}
\ No newline at end of file