Skip to content

Commit

Permalink
Merge pull request #60 from doomchild/dev
Browse files Browse the repository at this point in the history
2.16.0
  • Loading branch information
doomchild authored Dec 18, 2023
2 parents 88e00cd + af0eb79 commit 0e3a741
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 1 deletion.
2 changes: 1 addition & 1 deletion project-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "task-chaining",
"description": "Extension methods to System.Threading.Task to allow Promise-like chaining",
"title": "TaskChaining",
"version": "2.15.0",
"version": "2.16.0",
"ciEnvironment": {
"variables": [
{
Expand Down
81 changes: 81 additions & 0 deletions src/TaskExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,87 @@ Func<T, Task<E>> morphism
: Task.FromException<T>(await morphism(value))
).Unwrap();

/// <summary>
/// Allows a fulfilled <see name="Task{T}"/> to be transitioned to a faulted one if the <paramref name="predicate"/>
/// returns <code>false</code>.
/// </summary>
/// <remarks>This method provides another way to perform validation on the value contained within the
/// <see name="Task{T}"/>.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="predicate">The predicate to run on the <see name="Task{T}"/>'s value.</param>
/// <param name="exception">The exception to fault with if the <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, Exception exception)
=> task.Filter(predicate, Constant(exception));

/// <summary>
/// Allows a fulfilled <see name="Task{T}"/> to be transitioned to a faulted one if the <paramref name="predicate"/>
/// returns <code>false</code>.
/// </summary>
/// <remarks>This method provides another way to perform validation on the value contained within the
/// <see name="Task{T}"/>.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="predicate">The predicate to run on the <see name="Task{T}"/>'s value.</param>
/// <param name="supplier">A supplier function that produces the exception to fault with if the
/// <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, _ => supplier());

/// <summary>
/// Allows a fulfilled <see cref="Task{T}"/> to be transitioned to a faulted one if the <paramref name="predicate"/>
/// returns <code>false</code>.
/// </summary>
/// <remarks>This method provides another way to perform validation on the value contained within the
/// <see name="Task{T}"/>.</remarks>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <param name="predicate">The predicate to run on the <see cref="Task{T}"/>'s value.</param>
/// <param name="morphism">A function that produces the exception to fault with if the <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<T, Exception> morphism)
=> task.Bind(async value => await predicate(value) ? Task.FromResult(value) : Task.FromException<T>(morphism(value)))
.Unwrap();

/// <summary>
/// Allows a fulfilled <see cref="Task{T}"/> to be transitioned to a faulted one if the <paramref name="predicate"/>
/// return <code>false</code>.
/// </summary>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <typeparam name="E">The type of <see cref="Exception"/> that <paramref name="morphism"/> returns.</typeparam>
/// <param name="task">The task.</param>
/// <param name="predicate">The predicate to run on the <see cref="Task{T}"/>'s value.</param>
/// <param name="morphism">A function that produces a <see cref="Task{E}"/> to fault with if the
/// <paramref name="predicate"/>returns <code>false</code>.</param>
/// <returns>The transformed task.</returns>
public static Task<T> Filter<T, E>(
this Task<T> task,
Func<T, Task<bool>> predicate,
Func<Task<E>> morphism
) 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"/>
/// return <code>false</code>.
/// </summary>
/// <typeparam name="T">The task's underlying type.</typeparam>
/// <typeparam name="E">The type of <see cref="Exception"/> that <paramref name="morphism"/> returns.</typeparam>
/// <param name="task">The task.</param>
/// <param name="predicate">The predicate to run on the <see cref="Task{T}"/>'s value.</param>
/// <param name="morphism">A function that produces a <see cref="Task{E}"/> to fault with if the
/// <paramref name="predicate"/>returns <code>false</code>.</param>
/// <returns>The transformed task.</returns>
public static Task<T> Filter<T, E>(
this Task<T> task,
Func<T, Task<bool>> predicate,
Func<T, Task<E>> morphism
) where E : Exception => task.Bind(
async value => (await predicate(value))
? Task.FromResult(value)
: Task.FromException<T>(await morphism(value))
).Unwrap();

/// <summary>
/// Disjunctive 'rightMap'. Can be thought of as 'fmap' for <see name="Task{T}"/>s.
/// </summary>
Expand Down
200 changes: 200 additions & 0 deletions tests/unit/TaskChainingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,206 @@ public async Task ItShouldTransitionForAFailedPredicate()
}
}

public class FilterWithAsyncPredicate
{
public class WithRawValue
{
[Fact]
public async Task ItShouldNotFaultForASuccessfulPredicate()
{
Task<int> testTask = Task.FromResult(2)
.Filter(
value => Task.FromResult(value % 2 == 0),
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(
value => Task.FromResult(value % 2 == 0),
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(
value => Task.FromResult(value % 2 == 0),
new ArgumentException(expectedMessage)
);
Exception thrownException = new();

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

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

public class WithMorphism
{
public class WithRawMorphism
{
[Fact]
public async Task ItShouldNotFaultForASuccessfulPredicate()
{
Task<int> testTask = Task.FromResult(2).Filter(value => Task.FromResult(value % 2 == 0), 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(value => Task.FromResult(value % 2 == 0), 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(value => Task.FromResult(value % 2 == 0), value => new ArgumentException(expectedMessage));
Exception thrownException = new();

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

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

public class WithTaskSupplier
{
[Fact]
public async Task ItShouldNotFaultForASuccessfulPredicate()
{
Task<int> testTask = Task.FromResult(2).Filter(
value => Task.FromResult(value % 2 == 0),
() => 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(
value => Task.FromResult(value % 2 == 0),
() => 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(
value => Task.FromResult(value % 2 == 0),
() => Task.FromResult(new ArgumentException(expectedMessage))
);
Exception thrownException = new();

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

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

public class WithTaskMorphism
{
[Fact]
public async Task ItShouldNotFaultForASuccessfulPredicate()
{
Task<int> testTask = Task.FromResult(2).Filter(
value => Task.FromResult(value % 2 == 0),
_ => 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(
value => Task.FromResult(value % 2 == 0),
_ => 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(
value => Task.FromResult(value % 2 == 0),
_ => Task.FromResult(new ArgumentException(expectedMessage))
);
Exception thrownException = new();

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

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

public class Then
{
public class ForOnlyFulfilled
Expand Down

0 comments on commit 0e3a741

Please sign in to comment.