Skip to content

Commit

Permalink
Replaced ErrorOr with AwesomeResult
Browse files Browse the repository at this point in the history
  • Loading branch information
apfohl committed Jan 25, 2024
1 parent 6152d0e commit c46ef69
Show file tree
Hide file tree
Showing 14 changed files with 67 additions and 61 deletions.
10 changes: 5 additions & 5 deletions HotwiredBooks/Components/IBooksRepository.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using AwesomeResult;
using HotwiredBooks.Models;
using ErrorOr;

namespace HotwiredBooks.Components;

public interface IBooksRepository
{
Task<ErrorOr<Book>> Lookup(Guid id);
Task<Result<Book>> Lookup(Guid id);
Task<IEnumerable<Book>> All();
Task<ErrorOr<Book>> Create(string title, string author);
Task<ErrorOr<Book>> Update(Book book);
Task<ErrorOr<Book>> Delete(Book book);
Task<Result<Book>> Create(string title, string author);
Task<Result<Book>> Update(Book book);
Task<Result<Book>> Delete(Book book);
}
34 changes: 20 additions & 14 deletions HotwiredBooks/Components/MemoryBasedBooksRepository.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using System.Collections.Concurrent;
using AwesomeResult;
using bridgefield.FoundationalBits;
using HotwiredBooks.Models;
using ErrorOr;
using ErrorOr.Extensions;
using HotwiredBooks.Extensions;

namespace HotwiredBooks.Components;
Expand All @@ -15,47 +14,54 @@ internal sealed record Update(Book Book) : IBooksCommand;

internal sealed record Delete(Book Book) : IBooksCommand;

internal sealed record BooksReadError : IError;

internal sealed record BooksWriteError : IError;

public sealed class MemoryBasedBooksRepository : IBooksRepository
{
private readonly IAgent<IBooksCommand, ErrorOr<Book>> agent;
private readonly IAgent<IBooksCommand, Result<Book>> agent;
private readonly ConcurrentDictionary<Guid, Book> books = new(InitialBooks());

public MemoryBasedBooksRepository() =>
agent = Agent.Start<ConcurrentDictionary<Guid, Book>, IBooksCommand, ErrorOr<Book>>(
agent = Agent.Start<ConcurrentDictionary<Guid, Book>, IBooksCommand, Result<Book>>(
books,
(current, command) => command switch
{
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<Book>.From([Error.Failure()])
from createdBook in current.TryAdd(book.Id, book)
? book.Success()
: new BooksWriteError().Fail<Book>()
select createdBook).AsTask(),
Delete delete => (current,
current.TryRemove(delete.Book.Id, out var deletedBook) ? deletedBook.Success() : ErrorOr<Book>.From(
[Error.Failure()])).AsTask(),
current.TryRemove(delete.Book.Id, out var deletedBook)
? deletedBook.Success()
: new BooksWriteError().Fail<Book>()).AsTask(),
Update update => Task.FromResult((current,
from currentBook in current.TryGetValue(update.Book.Id, out var currentValue)
? currentValue.Success()
: ErrorOr<Book>.From([Error.Failure()])
: new BooksWriteError().Fail<Book>()
from updatedBook in current.TryUpdate(update.Book.Id, update.Book, currentBook)
? update.Book.Success()
: ErrorOr<Book>.From([Error.Failure()])
: new BooksWriteError().Fail<Book>()
select updatedBook)),
_ => throw new ArgumentOutOfRangeException(nameof(command))
});

public Task<ErrorOr<Book>> Lookup(Guid id) =>
Task.FromResult(books.TryGetValue(id, out var book) ? ErrorOrFactory.From(book) : Error.NotFound());
public Task<Result<Book>> Lookup(Guid id) =>
Task.FromResult(books.TryGetValue(id, out var book) ? book : new BooksReadError().Fail<Book>());

public Task<IEnumerable<Book>> All() =>
Task.FromResult<IEnumerable<Book>>(books.Values);

public Task<ErrorOr<Book>> Create(string title, string author) =>
public Task<Result<Book>> Create(string title, string author) =>
agent.Tell(new Create(title, author, DateTime.Now));

public Task<ErrorOr<Book>> Update(Book book) =>
public Task<Result<Book>> Update(Book book) =>
agent.Tell(new Update(book));

public Task<ErrorOr<Book>> Delete(Book book) =>
public Task<Result<Book>> Delete(Book book) =>
agent.Tell(new Delete(book));

private static IEnumerable<KeyValuePair<Guid, Book>> InitialBooks()
Expand Down
24 changes: 11 additions & 13 deletions HotwiredBooks/Controllers/BooksController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using ErrorOr;
using ErrorOr.Extensions;
using AwesomeResult;
using HotwiredBooks.Attributes;
using HotwiredBooks.Components;
using HotwiredBooks.Extensions;
using HotwiredBooks.Models;
using HotwiredBooks.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
Expand Down Expand Up @@ -33,16 +31,16 @@ from formData in ParseFormData(collection)
from book in booksRepository.Create(formData.Title, formData.Author)
select book
)
.ThenAsync(async book =>
.Map(async book =>
View(new BooksCreateViewModel(book, (await booksRepository.All()).Count())) as IActionResult)
.Else(StatusCode(500, "An unexpected error occurred on the server."));
.OrElse(StatusCode(500, "An unexpected error occurred on the server."));

[HttpGet]
public Task<IActionResult> Edit(Guid id) =>
booksRepository
.Lookup(id)
.Then(book => View(new BooksEditViewModel(book)) as IActionResult)
.Else(StatusCode(500, "An unexpected error occurred on the server."));
.Map(book => View(new BooksEditViewModel(book)) as IActionResult)
.OrElse(StatusCode(500, "An unexpected error occurred on the server."));

[HttpPatch, HttpPut]
[ValidateAntiForgeryToken]
Expand All @@ -59,21 +57,21 @@ book with
)
select updatedBook
)
.ThenAsync(book => ViewComponentRenderer.RenderAsync("Book", new BooksEditViewModel(book)))
.Else(StatusCode(500, "An unexpected error occurred on the server."));
.Map(book => ViewComponentRenderer.RenderAsync("Book", new BooksEditViewModel(book)))
.OrElse(StatusCode(500, "An unexpected error occurred on the server."));

[HttpPost]
[ValidateAntiForgeryToken]
[TurboStreamResponse]
public Task<IActionResult> Delete(Guid id) =>
booksRepository
.Lookup(id)
.ThenAsync(booksRepository.Delete)
.ThenAsync(async book =>
.Bind(booksRepository.Delete)
.Map(async book =>
View(new BooksDeleteViewModel(book, (await booksRepository.All()).Count())) as IActionResult)
.Else(StatusCode(500, "An unexpected error occurred on the server."));
.OrElse(StatusCode(500, "An unexpected error occurred on the server."));

private static Task<ErrorOr<FormData>> ParseFormData(IFormCollection collection) =>
private static Task<Result<FormData>> ParseFormData(IFormCollection collection) =>
(
from title in collection.JustGetValue("title")
from author in collection.JustGetValue("author")
Expand Down
5 changes: 5 additions & 0 deletions HotwiredBooks/Errors/Error.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using AwesomeResult;

namespace HotwiredBooks.Errors;

public sealed record Error : IError;
5 changes: 5 additions & 0 deletions HotwiredBooks/Errors/NotFoundError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using AwesomeResult;

namespace HotwiredBooks.Errors;

public sealed record NotFoundError : IError;
9 changes: 0 additions & 9 deletions HotwiredBooks/Extensions/ErrorOrExtensions.cs

This file was deleted.

13 changes: 6 additions & 7 deletions HotwiredBooks/Extensions/FunctionalExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
using AwesomeResult;
using HotwiredBooks.Errors;
using Microsoft.Extensions.Primitives;
using ErrorOr;

namespace HotwiredBooks.Extensions;

public static class FunctionalExtensions
{
public static ErrorOr<T> ToErrorOr<T>(this T value) =>
value is not null ? ErrorOrFactory.From(value) : ErrorOr<T>.From([Error.Failure()]);
public static Result<T> ResultNotNull<T>(this T value) =>
value is not null ? value : new Error().Fail<T>();

public static ErrorOr<StringValues> JustGetValue(this IFormCollection collection, string key) =>
collection.TryGetValue(key, out var value)
? ErrorOrFactory.From(value)
: ErrorOr<StringValues>.From([Error.NotFound()]);
public static Result<StringValues> JustGetValue(this IFormCollection collection, string key) =>
collection.TryGetValue(key, out var value) ? value : new NotFoundError().Fail<StringValues>();
}
6 changes: 3 additions & 3 deletions HotwiredBooks/Extensions/HtmlHelperExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Reflection;
using ErrorOr.Extensions;
using Humanizer;
using Microsoft.AspNetCore.Mvc.Rendering;
using AwesomeResult;

namespace HotwiredBooks.Extensions;

Expand All @@ -12,8 +12,8 @@ public static class HtmlHelperExtensions

public static string DomId(this IHtmlHelper htmlHelper, object @object, string prefix = null) =>
(
from property in @object.GetType().GetProperty("Id").ToErrorOr()
from value in property.GetValue(@object).ToErrorOr()
from property in @object.GetType().GetProperty("Id").ResultNotNull()
from value in property.GetValue(@object).ResultNotNull()
select value.ToString()
).Match(
id => $"{DomClass(htmlHelper, @object, prefix)}{Join}{id}",
Expand Down
3 changes: 1 addition & 2 deletions HotwiredBooks/HotwiredBooks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AwesomeResult" Version="0.5.0" />
<PackageReference Include="bridgefield.FoundationalBits" Version="0.1.2" />
<PackageReference Include="ErrorOr" Version="1.8.0" />
<PackageReference Include="ErrorOr.Extensions" Version="1.2.0" />
<PackageReference Include="Humanizer" Version="2.14.1" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions HotwiredBooks/ViewComponents/BookFormViewComponent.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using AwesomeResult;
using HotwiredBooks.Models;
using HotwiredBooks.ViewModels;
using Microsoft.AspNetCore.Mvc;
using ErrorOr;

namespace HotwiredBooks.ViewComponents;

public sealed class BookFormViewComponent : ViewComponent
{
public IViewComponentResult Invoke(ErrorOr<Book> book) => View(new BookFormViewModel(book));
public IViewComponentResult Invoke(Result<Book> book) => View(new BookFormViewModel(book));
}
4 changes: 2 additions & 2 deletions HotwiredBooks/ViewModels/BookFormViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using AwesomeResult;
using HotwiredBooks.Models;
using ErrorOr;

namespace HotwiredBooks.ViewModels;

public sealed record BookFormViewModel(ErrorOr<Book> Book);
public sealed record BookFormViewModel(Result<Book> Book);
1 change: 1 addition & 0 deletions HotwiredBooks/Views/Books/Edit.cshtml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@using AwesomeResult
@using HotwiredBooks.Extensions
@using HotwiredBooks.TagHelpers
@using HotwiredBooks.ViewComponents
Expand Down
8 changes: 5 additions & 3 deletions HotwiredBooks/Views/Shared/Components/BookForm/Default.cshtml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
@using AwesomeResult
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model HotwiredBooks.ViewModels.BookFormViewModel

@{
var id = Model.Book.Then(book => book.Id.ToString()).Else(string.Empty);
var id = Model.Book.Map(book => book.Id.ToString()).OrElse(string.Empty);
var action = Model.Book.Match(_ => "Update", _ => "Create");
var method = Model.Book.Match(_ => "put", _ => "post");
var title = Model.Book.Then(book => book.Title).Else(string.Empty);
var author = Model.Book.Then(book => book.Author).Else(string.Empty);
var title = Model.Book.Map(book => book.Title).OrElse(string.Empty);
var author = Model.Book.Map(book => book.Author).OrElse(string.Empty);
}

<form asp-controller="Books"
Expand Down
2 changes: 1 addition & 1 deletion HotwiredBooksTests/MemoryBasedBookRepositoryTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using ErrorOr.Extensions;
using HotwiredBooks.Components;
using AwesomeResult;

namespace HotwiredBooksTests;

Expand Down

0 comments on commit c46ef69

Please sign in to comment.