diff --git a/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/SearchWorkflow.cs b/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/SearchWorkflow.cs index 0e989385..c816c6d9 100644 --- a/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/SearchWorkflow.cs +++ b/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/SearchWorkflow.cs @@ -22,17 +22,16 @@ public Task> RunAsync(CancellationToken cancellationTo private async Task> ProcessSearchCategoriesAsync(IReadOnlyList searchCategories, CancellationToken cancellationToken) { + Result outcome = Unit.Value; + foreach (var searchCategory in searchCategories) { cancellationToken.ThrowIfCancellationRequested(); - var categoryResult = await ProcessSearchCategoryAsync(searchCategory, cancellationToken).ConfigureAwait(false); - bool categoryFailed = categoryResult.Match(_ => false, _ => true); - - if (categoryFailed) return categoryResult; + outcome = await outcome.BindAsync(_ => ProcessSearchCategoryAsync(searchCategory, cancellationToken)).ConfigureAwait(false); } - return Unit.Value; + return outcome; } private Task> ProcessSearchCategoryAsync(Category searchCategory, CancellationToken cancellationToken) diff --git a/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/SubscriptionsWorkflow.cs b/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/SubscriptionsWorkflow.cs index 219555cc..e84556ac 100644 --- a/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/SubscriptionsWorkflow.cs +++ b/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/SubscriptionsWorkflow.cs @@ -29,12 +29,9 @@ private async Task> RunSubscriptionsAsync(Cancellation } private async Task LoadStartingPageAsync() - { - var loadResult = await subscriptionsImagesListPage.LoadSubscriptionResultsPageAsync(FirstPageNumber).ConfigureAwait(false); - bool loadedSuccessfully = loadResult.Match(_ => true, _ => false); - - if (!loadedSuccessfully) _ = await subscriptionsImagesListPage.LoadSubscriptionResultsPageAsync(FirstPageNumber).ConfigureAwait(false); - } + => _ = await subscriptionsImagesListPage.LoadSubscriptionResultsPageAsync(FirstPageNumber) + .OrElseAsync(_ => subscriptionsImagesListPage.LoadSubscriptionResultsPageAsync(FirstPageNumber)) + .ConfigureAwait(false); private async Task> ProcessSubscriptionsAsync(PageInfo pageInfo, CancellationToken cancellationToken) { diff --git a/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/TopWallpapersWorkflow.cs b/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/TopWallpapersWorkflow.cs index 3359e1e4..307d8d51 100644 --- a/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/TopWallpapersWorkflow.cs +++ b/apps/desktop/Scraper/AStar.Dev.Wallpaper.Scraper/Workflows/TopWallpapersWorkflow.cs @@ -33,12 +33,9 @@ private async Task> RunTopWallpapersAsync(Cancellation } private async Task LoadStartingPageAsync(int startingPageNumber) - { - var loadResult = await topWallpapersPage.LoadTopWallpapersPageAsync(startingPageNumber).ConfigureAwait(false); - bool loadedSuccessfully = loadResult.Match(_ => true, _ => false); - - if (!loadedSuccessfully) _ = await topWallpapersPage.LoadTopWallpapersPageAsync(FirstPageNumber).ConfigureAwait(false); - } + => _ = await topWallpapersPage.LoadTopWallpapersPageAsync(startingPageNumber) + .OrElseAsync(_ => topWallpapersPage.LoadTopWallpapersPageAsync(FirstPageNumber)) + .ConfigureAwait(false); private async Task> ProcessTopWallpapersAsync(SearchConfiguration searchConfiguration, CancellationToken cancellationToken) { diff --git a/packages/core/functional/AStar.Dev.FunctionalParadigm.Tests.Unit/GivenOrElseAsync.cs b/packages/core/functional/AStar.Dev.FunctionalParadigm.Tests.Unit/GivenOrElseAsync.cs new file mode 100644 index 00000000..cbe9f250 --- /dev/null +++ b/packages/core/functional/AStar.Dev.FunctionalParadigm.Tests.Unit/GivenOrElseAsync.cs @@ -0,0 +1,60 @@ +using Shouldly; +using Xunit; + +namespace AStar.Dev.FunctionalParadigm.Tests.Unit; + +public class GivenOrElseAsync +{ + [Fact] + public async Task when_result_is_success_then_returns_original_without_invoking_fallback() + { + var resultTask = Task.FromResult(Result.Success(42)); + bool invoked = false; + + var actual = await resultTask.OrElseAsync(_ => + { + invoked = true; + + return Task.FromResult(Result.Success(0)); + }); + + actual.ShouldBe(new Ok(42)); + invoked.ShouldBeFalse(); + } + + [Fact] + public async Task when_result_is_failure_then_returns_fallback_result() + { + var resultTask = Task.FromResult(Result.Failure("first failed")); + + var actual = await resultTask.OrElseAsync(_ => Task.FromResult(Result.Success(7))); + + actual.ShouldBe(new Ok(7)); + } + + [Fact] + public async Task when_result_is_failure_then_fallback_receives_original_error() + { + var resultTask = Task.FromResult(Result.Failure("original error")); + string? receivedError = null; + + _ = await resultTask.OrElseAsync(error => + { + receivedError = error; + + return Task.FromResult(Result.Success(1)); + }); + + receivedError.ShouldBe("original error"); + } + + [Fact] + public async Task when_result_and_fallback_both_fail_then_returns_fallback_failure() + { + var resultTask = Task.FromResult(Result.Failure("first failed")); + + var actual = await resultTask.OrElseAsync(_ => Task.FromResult(Result.Failure("fallback failed"))); + + actual.ShouldBe(new Fail("fallback failed")); + } +} diff --git a/packages/core/functional/AStar.Dev.FunctionalParadigm/ResultExtensions.cs b/packages/core/functional/AStar.Dev.FunctionalParadigm/ResultExtensions.cs index 78f6c0c1..24252cec 100644 --- a/packages/core/functional/AStar.Dev.FunctionalParadigm/ResultExtensions.cs +++ b/packages/core/functional/AStar.Dev.FunctionalParadigm/ResultExtensions.cs @@ -107,6 +107,14 @@ public static Result Bind(this Result _ => throw new InvalidOperationException("Unexpected result type.") }; + public static async Task> OrElseAsync(this Task> resultTask, Func>> fallback) + => await resultTask.ConfigureAwait(false) switch + { + Ok ok => ok, + Fail fail => await fallback(fail.Error).ConfigureAwait(false), + _ => throw new InvalidOperationException("Unexpected result type.") + }; + public static Result Ensure(this Result result, Action finallyAction) { finallyAction();