diff --git a/HotwiredBooks.sln.DotSettings.user b/HotwiredBooks.sln.DotSettings.user
index 44bb357..0a6041a 100644
--- a/HotwiredBooks.sln.DotSettings.user
+++ b/HotwiredBooks.sln.DotSettings.user
@@ -1,8 +1,11 @@
+ <AssemblyExplorer>
+ <Assembly Path="/home/andreas/.nuget/packages/erroror/1.3.0/lib/net6.0/ErrorOr.dll" />
+</AssemblyExplorer>
- <SessionState ContinuousTestingMode="0" Name="Create_inserts_value_into_repository" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <SessionState ContinuousTestingMode="0" IsActive="True" Name="Create_inserts_value_into_repository" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
<Project Location="/home/andreas/code/HotwiredBooks/HotwiredBooksTests" Presentation="<HotwiredBooksTests>" />
</SessionState>
- <SessionState ContinuousTestingMode="0" IsActive="True" Name="Create_inserts_value_into_repository #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <SessionState ContinuousTestingMode="0" Name="Create_inserts_value_into_repository #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
<Project Location="/home/andreas/code/HotwiredBooks/HotwiredBooksTests" Presentation="<HotwiredBooksTests>" />
</SessionState>
\ No newline at end of file
diff --git a/HotwiredBooks/Components/IBooksRepository.cs b/HotwiredBooks/Components/IBooksRepository.cs
index 8ea9555..21248a1 100644
--- a/HotwiredBooks/Components/IBooksRepository.cs
+++ b/HotwiredBooks/Components/IBooksRepository.cs
@@ -1,13 +1,13 @@
using HotwiredBooks.Models;
-using MonadicBits;
+using ErrorOr;
namespace HotwiredBooks.Components;
public interface IBooksRepository
{
- Task> Lookup(Guid id);
+ Task> Lookup(Guid id);
Task> All();
- Task> Create(string title, string author);
- Task> Update(Book book);
- Task> Delete(Book book);
+ Task> Create(string title, string author);
+ Task> Update(Book book);
+ Task> Delete(Book book);
}
diff --git a/HotwiredBooks/Components/MemoryBasedBooksRepository.cs b/HotwiredBooks/Components/MemoryBasedBooksRepository.cs
index 6487a86..2176cec 100644
--- a/HotwiredBooks/Components/MemoryBasedBooksRepository.cs
+++ b/HotwiredBooks/Components/MemoryBasedBooksRepository.cs
@@ -1,12 +1,12 @@
using System.Collections.Concurrent;
using bridgefield.FoundationalBits;
using HotwiredBooks.Models;
-using MonadicBits;
+using ErrorOr;
+using ErrorOr.Extensions;
+using HotwiredBooks.Extensions;
namespace HotwiredBooks.Components;
-using static Functional;
-
internal interface IBooksCommand;
internal sealed record Create(string Title, string Author, DateTime CreatedAt) : IBooksCommand;
@@ -17,44 +17,45 @@ internal sealed record Delete(Book Book) : IBooksCommand;
public sealed class MemoryBasedBooksRepository : IBooksRepository
{
- private readonly IAgent> agent;
+ private readonly IAgent> agent;
private readonly ConcurrentDictionary books = new(InitialBooks());
public MemoryBasedBooksRepository() =>
- agent = Agent.Start, IBooksCommand, Maybe>(
+ agent = Agent.Start, IBooksCommand, ErrorOr>(
books,
(current, command) => command switch
{
- Create create => Task.FromResult((current,
- from book in new Book(Guid.NewGuid(), create.Title, create.Author, create.CreatedAt).Just()
- from createdBook in current.TryAdd(book.Id, book) ? book.Just() : Nothing
- select createdBook)),
- Delete delete => Task.FromResult((current,
- current.TryRemove(delete.Book.Id, out var deletedBook) ? deletedBook.Just() : Nothing)),
+ Create create => (current,
+ from book in new Book(Guid.NewGuid(), create.Title, create.Author, create.CreatedAt).Success()
+ from createdBook in current.TryAdd(book.Id, book) ? book.Success() : ErrorOr.From([Error.Failure()])
+ select createdBook).AsTask(),
+ Delete delete => (current,
+ current.TryRemove(delete.Book.Id, out var deletedBook) ? deletedBook.Success() : ErrorOr.From(
+ [Error.Failure()])).AsTask(),
Update update => Task.FromResult((current,
from currentBook in current.TryGetValue(update.Book.Id, out var currentValue)
- ? currentValue.Just()
- : Nothing
+ ? currentValue.Success()
+ : ErrorOr.From([Error.Failure()])
from updatedBook in current.TryUpdate(update.Book.Id, update.Book, currentBook)
- ? update.Book.Just()
- : Nothing
+ ? update.Book.Success()
+ : ErrorOr.From([Error.Failure()])
select updatedBook)),
_ => throw new ArgumentOutOfRangeException(nameof(command))
});
- public Task> Lookup(Guid id) =>
- Task.FromResult(books.TryGetValue(id, out var book) ? book.Just() : Nothing);
+ public Task> Lookup(Guid id) =>
+ Task.FromResult(books.TryGetValue(id, out var book) ? ErrorOrFactory.From(book) : Error.NotFound());
public Task> All() =>
Task.FromResult>(books.Values);
- public Task> Create(string title, string author) =>
+ public Task> Create(string title, string author) =>
agent.Tell(new Create(title, author, DateTime.Now));
- public Task> Update(Book book) =>
+ public Task> Update(Book book) =>
agent.Tell(new Update(book));
- public Task> Delete(Book book) =>
+ public Task> Delete(Book book) =>
agent.Tell(new Delete(book));
private static IEnumerable> InitialBooks()
diff --git a/HotwiredBooks/Controllers/BooksController.cs b/HotwiredBooks/Controllers/BooksController.cs
index 9291af5..205226c 100644
--- a/HotwiredBooks/Controllers/BooksController.cs
+++ b/HotwiredBooks/Controllers/BooksController.cs
@@ -1,3 +1,5 @@
+using ErrorOr;
+using ErrorOr.Extensions;
using HotwiredBooks.Attributes;
using HotwiredBooks.Components;
using HotwiredBooks.Extensions;
@@ -5,7 +7,6 @@
using HotwiredBooks.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
-using MonadicBits;
namespace HotwiredBooks.Controllers;
@@ -32,7 +33,7 @@ from formData in ParseFormData(collection)
from book in booksRepository.Create(formData.Title, formData.Author)
select book
)
- .MapAsync(async book =>
+ .Select(async book =>
View(new BooksCreateViewModel(book, (await booksRepository.All()).Count())))
.OrElse(StatusCode(500, "An unexpected error occurred on the server."));
@@ -40,7 +41,7 @@ select book
public Task Edit(Guid id) =>
booksRepository
.Lookup(id)
- .MapAsync(book => View(new BooksEditViewModel(book)))
+ .Select(book => View(new BooksEditViewModel(book)))
.OrElse(StatusCode(500, "An unexpected error occurred on the server."));
[HttpPatch, HttpPut]
@@ -58,7 +59,7 @@ book with
)
select updatedBook
)
- .MapAsync(book => ViewComponentRenderer.RenderAsync("Book", new BooksEditViewModel(book)))
+ .Select(book => ViewComponentRenderer.RenderAsync("Book", new BooksEditViewModel(book)))
.OrElse(StatusCode(500, "An unexpected error occurred on the server."));
[HttpPost]
@@ -67,15 +68,15 @@ select updatedBook
public Task Delete(Guid id) =>
booksRepository
.Lookup(id)
- .BindAsync(booksRepository.Delete)
- .MapAsync(async book =>
+ .SelectMany(booksRepository.Delete)
+ .Select(async book =>
View(new BooksDeleteViewModel(book, (await booksRepository.All()).Count())))
.OrElse(StatusCode(500, "An unexpected error occurred on the server."));
- private static Task> ParseFormData(IFormCollection collection) =>
- Task.FromResult(
- from title in collection.JustGetValue("title")
- from author in collection.JustGetValue("author")
- select new FormData(title, author)
- );
+ private static Task> ParseFormData(IFormCollection collection) =>
+ (
+ from title in collection.JustGetValue("title")
+ from author in collection.JustGetValue("author")
+ select new FormData(title, author)
+ ).AsTask();
}
diff --git a/HotwiredBooks/Extensions/AsyncExtensions.cs b/HotwiredBooks/Extensions/AsyncExtensions.cs
new file mode 100644
index 0000000..9f6ea81
--- /dev/null
+++ b/HotwiredBooks/Extensions/AsyncExtensions.cs
@@ -0,0 +1,7 @@
+namespace HotwiredBooks.Extensions;
+
+public static class AsyncExtensions
+{
+ public static Task AsTask(this T value) =>
+ Task.FromResult(value);
+}
diff --git a/HotwiredBooks/Extensions/ErrorOrExtensions.cs b/HotwiredBooks/Extensions/ErrorOrExtensions.cs
new file mode 100644
index 0000000..2c910b0
--- /dev/null
+++ b/HotwiredBooks/Extensions/ErrorOrExtensions.cs
@@ -0,0 +1,9 @@
+using ErrorOr;
+
+namespace HotwiredBooks.Extensions;
+
+public static class ErrorOrExtensions
+{
+ public static ErrorOr Success(this T value) =>
+ ErrorOrFactory.From(value);
+}
diff --git a/HotwiredBooks/Extensions/FunctionalExtensions.cs b/HotwiredBooks/Extensions/FunctionalExtensions.cs
index f202ff4..300b68c 100644
--- a/HotwiredBooks/Extensions/FunctionalExtensions.cs
+++ b/HotwiredBooks/Extensions/FunctionalExtensions.cs
@@ -1,26 +1,27 @@
using Microsoft.Extensions.Primitives;
-using MonadicBits;
+using ErrorOr;
namespace HotwiredBooks.Extensions;
-using static Functional;
-
public static class FunctionalExtensions
{
- public static Maybe ToMaybe(this T value) => value?.Just() ?? Nothing;
+ public static ErrorOr ToErrorOr(this T value) =>
+ value is not null ? ErrorOrFactory.From(value) : ErrorOr.From([Error.Failure()]);
- public static Maybe JustGetValue(this IFormCollection collection, string key) =>
- collection.TryGetValue(key, out var value) ? value.Just() : Nothing;
+ public static ErrorOr JustGetValue(this IFormCollection collection, string key) =>
+ collection.TryGetValue(key, out var value)
+ ? ErrorOrFactory.From(value)
+ : ErrorOr.From([Error.NotFound()]);
- public static T OrElse(this Maybe maybe, T orElse) =>
- maybe.Match(value => value, () => orElse);
+ public static T OrElse(this ErrorOr maybe, T orElse) =>
+ maybe.Match(value => value, _ => orElse);
- public static T OrElse(this Maybe maybe, Func orElse) =>
- maybe.Match(value => value, orElse);
+ public static T OrElse(this ErrorOr maybe, Func orElse) =>
+ maybe.Match(value => value, _ => orElse());
- public static async Task OrElse(this Task> maybe, Func orElse) =>
- (await maybe).Match(value => value, orElse);
+ public static async Task OrElse(this Task> maybe, Func orElse) =>
+ (await maybe).Match(value => value, _ => orElse());
- public static async Task OrElse(this Task> maybe, T orElse) =>
- (await maybe).Match(value => value, () => orElse);
+ public static async Task OrElse(this Task> maybe, T orElse) =>
+ (await maybe).Match(value => value, _ => orElse);
}
diff --git a/HotwiredBooks/Extensions/HtmlHelperExtensions.cs b/HotwiredBooks/Extensions/HtmlHelperExtensions.cs
index 395d1cc..0d94575 100644
--- a/HotwiredBooks/Extensions/HtmlHelperExtensions.cs
+++ b/HotwiredBooks/Extensions/HtmlHelperExtensions.cs
@@ -1,7 +1,7 @@
using System.Reflection;
+using ErrorOr.Extensions;
using Humanizer;
using Microsoft.AspNetCore.Mvc.Rendering;
-using MonadicBits;
namespace HotwiredBooks.Extensions;
@@ -12,12 +12,12 @@ public static class HtmlHelperExtensions
public static string DomId(this IHtmlHelper htmlHelper, object @object, string prefix = null) =>
(
- from property in @object.GetType().GetProperty("Id").ToMaybe()
- from value in property.GetValue(@object).ToMaybe()
+ from property in @object.GetType().GetProperty("Id").ToErrorOr()
+ from value in property.GetValue(@object).ToErrorOr()
select value.ToString()
).Match(
id => $"{DomClass(htmlHelper, @object, prefix)}{Join}{id}",
- () => DomClass(htmlHelper, @object, prefix ?? New)
+ _ => DomClass(htmlHelper, @object, prefix ?? New)
);
public static string DomClass(this IHtmlHelper _, object objectOrType, string prefix = null)
diff --git a/HotwiredBooks/HotwiredBooks.csproj b/HotwiredBooks/HotwiredBooks.csproj
index d9f8ba2..d768915 100644
--- a/HotwiredBooks/HotwiredBooks.csproj
+++ b/HotwiredBooks/HotwiredBooks.csproj
@@ -8,7 +8,8 @@
-
+
+
diff --git a/HotwiredBooks/ViewComponents/BookFormViewComponent.cs b/HotwiredBooks/ViewComponents/BookFormViewComponent.cs
index 7f2f8ea..c76510f 100644
--- a/HotwiredBooks/ViewComponents/BookFormViewComponent.cs
+++ b/HotwiredBooks/ViewComponents/BookFormViewComponent.cs
@@ -1,11 +1,11 @@
using HotwiredBooks.Models;
using HotwiredBooks.ViewModels;
using Microsoft.AspNetCore.Mvc;
-using MonadicBits;
+using ErrorOr;
namespace HotwiredBooks.ViewComponents;
public sealed class BookFormViewComponent : ViewComponent
{
- public IViewComponentResult Invoke(Maybe book) => View(new BookFormViewModel(book));
+ public IViewComponentResult Invoke(ErrorOr book) => View(new BookFormViewModel(book));
}
diff --git a/HotwiredBooks/ViewModels/BookFormViewModel.cs b/HotwiredBooks/ViewModels/BookFormViewModel.cs
index 7343897..8fb3628 100644
--- a/HotwiredBooks/ViewModels/BookFormViewModel.cs
+++ b/HotwiredBooks/ViewModels/BookFormViewModel.cs
@@ -1,6 +1,6 @@
using HotwiredBooks.Models;
-using MonadicBits;
+using ErrorOr;
namespace HotwiredBooks.ViewModels;
-public sealed record BookFormViewModel(Maybe Book);
+public sealed record BookFormViewModel(ErrorOr Book);
diff --git a/HotwiredBooks/Views/Books/Edit.cshtml b/HotwiredBooks/Views/Books/Edit.cshtml
index 98dc2f2..c501454 100644
--- a/HotwiredBooks/Views/Books/Edit.cshtml
+++ b/HotwiredBooks/Views/Books/Edit.cshtml
@@ -1,10 +1,11 @@
@using HotwiredBooks.Extensions
-@using MonadicBits
+@using HotwiredBooks.TagHelpers
+@using HotwiredBooks.ViewComponents
@model HotwiredBooks.ViewModels.BooksEditViewModel
@{
Layout = null;
}
-
+
diff --git a/HotwiredBooks/Views/Shared/Components/BookForm/Default.cshtml b/HotwiredBooks/Views/Shared/Components/BookForm/Default.cshtml
index da2b05b..8f15fac 100644
--- a/HotwiredBooks/Views/Shared/Components/BookForm/Default.cshtml
+++ b/HotwiredBooks/Views/Shared/Components/BookForm/Default.cshtml
@@ -1,11 +1,12 @@
+@using ErrorOr.Extensions
@model HotwiredBooks.ViewModels.BookFormViewModel
@{
- var id = Model.Book.Map(book => book.Id).Match(id => id.ToString(), () => string.Empty);
- var action = Model.Book.Match(_ => "Update", () => "Create");
- var method = Model.Book.Match(_ => "put", () => "post");
- var title = Model.Book.Map(book => book.Title).Match(title => title, () => string.Empty);
- var author = Model.Book.Map(book => book.Author).Match(author => author, () => string.Empty);
+ var id = Model.Book.Select(book => book.Id).Match(id => id.ToString(), _ => string.Empty);
+ var action = Model.Book.Match(_ => "Update", _ => "Create");
+ var method = Model.Book.Match(_ => "put", _ => "post");
+ var title = Model.Book.Select(book => book.Title).Match(title => title, _ => string.Empty);
+ var author = Model.Book.Select(book => book.Author).Match(author => author, _ => string.Empty);
}