Skip to content

Commit

Permalink
Merge pull request #101 from doomchild/bugfix/issue-100
Browse files Browse the repository at this point in the history
Issue-100: Fix async Filters double-wrapping exceptions
  • Loading branch information
doomchild authored Nov 25, 2024
2 parents 169481a + 59f920d commit d0d6957
Show file tree
Hide file tree
Showing 8 changed files with 522 additions and 453 deletions.
88 changes: 23 additions & 65 deletions src/TaskExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public static Task<T> Filter<T>(this Task<T> task, Predicate<T> predicate, Excep
/// <paramref name="predicate"/> returns <code>false</code>.</param>
/// <returns>The transformed task.</returns>
public static Task<T> Filter<T>(this Task<T> task, Predicate<T> predicate, Func<Exception> supplier)
=> task.Filter(predicate, _ => PotentiallyUnwindException(supplier()));
=> task.Filter(predicate, _ => supplier());

/// <summary>
/// Allows a fulfilled <see cref="Task{T}"/> to be transitioned to a faulted one if the <paramref name="predicate"/>
Expand All @@ -192,7 +192,7 @@ public static Task<T> Filter<T>(this Task<T> task, Predicate<T> predicate, Func<
/// returns <code>false</code>.</param>
/// <returns>The transformed task.</returns>
public static Task<T> Filter<T>(this Task<T> task, Predicate<T> predicate, Func<T, Exception> morphism)
=> task.Bind(value => predicate(value) ? Task.FromResult(value) : Task.FromException<T>(PotentiallyUnwindException(morphism(value))));
=> task.Bind(value => predicate(value) ? Task.FromResult(value) : Task.FromException<T>(morphism(value)));

/// <summary>
/// Allows a fulfilled <see cref="Task{T}"/> to be transitioned to a faulted one if the <paramref name="predicate"/>
Expand All @@ -209,7 +209,7 @@ public static Task<T> Filter<T, E>(
this Task<T> task,
Predicate<T> predicate,
Func<Task<E>> morphism
) where E : Exception => task.Filter(predicate, _ => morphism().Then(PotentiallyUnwindException));
) where E : Exception => task.Filter(predicate, _ => morphism());

/// <summary>
/// Allows a fulfilled <see cref="Task{T}"/> to be transitioned to a faulted one if the <paramref name="predicate"/>
Expand All @@ -228,19 +228,10 @@ public static Task<T> Filter<T, E>(
Func<T, Task<E>> morphism
) where E: Exception
{
return task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCompleted)
{
T result = continuationTask.Result;

return predicate(result) == true
? Task.FromResult(result)
: morphism(result).Then(failureTask => Task.FromException<T>(PotentiallyUnwindException(failureTask)));
}

return continuationTask;
}).Unwrap();
return task.Then(value => predicate(value)
? Task.FromResult(value)
: morphism(value).Then(Task.FromException<T>)
);
}

/// <summary>
Expand Down Expand Up @@ -271,7 +262,7 @@ public static Task<T> Filter<T>(this Task<T> task, Func<T, Task<bool>> predicate
/// <paramref name="predicate"/> returns <code>false</code>.</param>
/// <returns>The transformed task.</returns>
public static Task<T> Filter<T>(this Task<T> task, Func<T, Task<bool>> predicate, Func<Exception> supplier)
=> task.Filter(predicate, _ => PotentiallyUnwindException(supplier()));
=> task.Filter(predicate, _ => supplier());

/// <summary>
/// Allows a fulfilled <see cref="Task{T}"/> to be transitioned to a faulted one if the <paramref name="predicate"/>
Expand All @@ -291,21 +282,11 @@ public static Task<T> Filter<T>(
Func<T, Exception> morphism
)
{
return task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCompleted)
{
T predicateValue = continuationTask.Result;

return predicate(predicateValue)
.Then(predicateResult => predicateResult
? Task.FromResult(predicateValue)
: Task.FromException<T>(PotentiallyUnwindException(morphism(predicateValue)))
);
}

return continuationTask;
}).Unwrap();
return task.Then(value => predicate(value)
.Then(result => result
? Task.FromResult(value)
: Task.FromException<T>(morphism(value))
));
}

/// <summary>
Expand All @@ -325,22 +306,11 @@ public static Task<T> Filter<T, E>(
Func<Task<E>> morphism
) where E : Exception
{
return task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCompleted)
{
T continuationValue = continuationTask.Result;

return predicate(continuationValue).ContinueWith(predicateTask =>
{
return predicateTask.Result
? Task.FromResult(continuationValue)
: morphism().Then(exception => Task.FromException<T>(PotentiallyUnwindException(exception)));
}).Unwrap();
}

return continuationTask;
}).Unwrap();
return task.Then(value => predicate(value)
.Then(result => result
? Task.FromResult(value)
: morphism().Then(Task.FromException<T>))
);
}

/// <summary>
Expand All @@ -360,23 +330,11 @@ public static Task<T> Filter<T, E>(
Func<T, Task<E>> morphism
) where E : Exception
{
return task.ContinueWith(continuationTask =>
{
if (continuationTask.IsCompleted)
{
T continuationValue = continuationTask.Result;

return predicate(continuationValue).ContinueWith(predicateTask =>
{
return predicateTask.Result
? Task.FromResult(continuationValue)
: morphism(continuationValue)
.Then(exception => Task.FromException<T>(PotentiallyUnwindException(exception)));
}).Unwrap();
}

return continuationTask;
}).Unwrap();
return task.Then(value => predicate(value)
.Then(result => result
? Task.FromResult(value)
: morphism(value).Then(Task.FromException<T>)
));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Threading.Tasks;
using RLC.TaskChaining;
using Xunit;

namespace RLC.TaskChainingTests.Filter.WithAsyncPredicate.WithMorphism;

public class WithRawMorphism
{
[Fact]
public async Task ItShouldNotFaultForASuccessfulPredicate()
{
Task<int> testTask = Task.FromResult(2)
.Filter(AsyncPredicate, value => new Exception("not even"));

await Task.Delay(10);

Assert.True(testTask.IsCompletedSuccessfully);
}

[Fact]
public async Task ItShouldNotChangeTheValueForASuccessfulPredicate()
{
int expectedValue = 2;
int actualValue = await Task.FromResult(2)
.Filter(AsyncPredicate, value => new Exception("not even"));

Assert.Equal(expectedValue, actualValue);
}

[Fact]
public async Task ItShouldTransitionForAFailedPredicate()
{
string expectedMessage = Guid.NewGuid().ToString();
Task<int> testTask = Task.FromResult(1)
.Filter(AsyncPredicate, value => new ArgumentException(expectedMessage));
Exception thrownException = new();

try
{
await testTask;
}
catch (ArgumentException exception)
{
thrownException = exception;
}

Assert.Equal(expectedMessage, thrownException.Message);
}

[Fact]
public async Task ItShouldNotDoubleWrapAnExistingException()
{
Task<int> testTask = Task.FromException<int>(new ArithmeticException())
.Filter(
AsyncPredicate,
_ => new ArgumentException()
);

await Assert.ThrowsAsync<ArithmeticException>(() => testTask);
}

private async Task<bool> AsyncPredicate(int value)
{
await Task.Delay(1);

return value % 2 == 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Threading.Tasks;
using RLC.TaskChaining;
using Xunit;

namespace RLC.TaskChainingTests.Filter.WithAsyncPredicate.WithMorphism;

public class WithTaskMorphism
{
[Fact]
public async Task ItShouldNotFaultForASuccessfulPredicate()
{
Task<int> testTask = Task.FromResult(2)
.Filter(
AsyncPredicate,
_ => Task.FromResult(new Exception("not even"))
);

await Task.Delay(10);

Assert.True(testTask.IsCompletedSuccessfully);
}

[Fact]
public async Task ItShouldNotChangeTheValueForASuccessfulPredicate()
{
int expectedValue = 2;
int actualValue = await Task.FromResult(2)
.Filter(
AsyncPredicate,
_ => Task.FromResult(new Exception("not even"))
);

Assert.Equal(expectedValue, actualValue);
}

[Fact]
public async Task ItShouldTransitionForAFailedPredicate()
{
string expectedMessage = Guid.NewGuid().ToString();
Task<int> testTask = Task.FromResult(1)
.Filter(
AsyncPredicate,
_ => Task.FromResult(new ArgumentException(expectedMessage))
);
Exception thrownException = new();

try
{
await testTask;
}
catch (ArgumentException exception)
{
thrownException = exception;
}

Assert.Equal(expectedMessage, thrownException.Message);
}

[Fact]
public async Task ItShouldNotDoubleWrapAnExistingException()
{
Task<int> testTask = Task.FromException<int>(new ArithmeticException())
.Filter(
AsyncPredicate,
_ => Task.FromResult(new ArgumentException())
);

await Assert.ThrowsAsync<ArithmeticException>(() => testTask);
}

private async Task<bool> AsyncPredicate(int value)
{
await Task.Delay(1);

return value % 2 == 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Threading.Tasks;
using RLC.TaskChaining;
using Xunit;

namespace RLC.TaskChainingTests.Filter.WithAsyncPredicate.WithMorphism;

public class WithTaskSupplier
{
[Fact]
public async Task ItShouldNotFaultForASuccessfulPredicate()
{
Task<int> testTask = Task.FromResult(2).Filter(
AsyncPredicate,
() => Task.FromResult(new Exception("not even"))
);

await Task.Delay(10);

Assert.True(testTask.IsCompletedSuccessfully);
}

[Fact]
public async Task ItShouldNotChangeTheValueForASuccessfulPredicate()
{
int expectedValue = 2;
int actualValue = await Task.FromResult(2).Filter(
AsyncPredicate,
() => Task.FromResult(new Exception("not even"))
);

Assert.Equal(expectedValue, actualValue);
}

[Fact]
public async Task ItShouldTransitionForAFailedPredicate()
{
string expectedMessage = Guid.NewGuid().ToString();
Task<int> testTask = Task.FromResult(1)
.Filter(
AsyncPredicate,
() => Task.FromResult(new ArgumentException(expectedMessage))
);
Exception thrownException = new();

try
{
await testTask;
}
catch (ArgumentException exception)
{
thrownException = exception;
}

Assert.Equal(expectedMessage, thrownException.Message);
}

[Fact]
public async Task ItShouldNotDoubleWrapAnExistingException()
{
Task<int> testTask = Task.FromException<int>(new ArithmeticException())
.Filter(
AsyncPredicate,
() => Task.FromResult(new ArgumentException())
);

await Assert.ThrowsAsync<ArithmeticException>(() => testTask);
}

private async Task<bool> AsyncPredicate(int value)
{
await Task.Delay(1);

return value % 2 == 0;
}
}
Loading

0 comments on commit d0d6957

Please sign in to comment.