Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tap + TapAsync Refactoring #12

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 56 additions & 23 deletions src/RailwayResult.FunctionalExtensions/ResultExtensions.Tap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,105 @@

public static partial class ResultExtensions
{
public static Result<TValue> Tap<TValue>(this Result<TValue> result, Action<TValue> func)
public static Result Tap(this Result result, Action func)
{
if (result.IsFailure)
return result;
if (result.IsSuccess)
func();

func(result.Value);
return result;
}

public static async Task<Result<TValue>> Tap<TValue>(this Task<Result<TValue>> resultTask, Action<TValue> func)
public static async Task<Result> Tap(this Task<Result> resultTask, Action func)
{
var result = await resultTask;
return result.Tap(func);
}

public static async Task<Result<TValue>> Tap<TValue>(this Task<Result<Result<TValue>>> resultTask, Action<TValue> func)
public static Result Tap(this Result result, Func<Result> func)
{
if (result.IsSuccess)
return func();

return result;
}

public static async Task<Result> Tap(this Task<Result> resultTask, Func<Result> func)
{
var result = await resultTask;
if (result.IsFailure)
return result.Error;
return result.Tap(func);
}

return result.Value.Tap(func);
public static Result<TValue> Tap<TValue>(this Result<TValue> result, Action<TValue> func)
{
if (result.IsSuccess)
func(result.Value);

return result;
}

public static Result<(TFirst, TSecond)> Tap<TFirst, TSecond>(this Result<(TFirst, TSecond)> result, Action<TFirst, TSecond> func)
public static async Task<Result<TValue>> Tap<TValue>(this Task<Result<TValue>> resultTask, Action<TValue> func)
{
var result = await resultTask;
return result.Tap(func);
}

public static Result<TValue> Tap<TValue>(this Result<TValue> result, Func<TValue, Result> func)
{
if (result.IsFailure)
return result;

func(result.Value.Item1, result.Value.Item2);
var nestedResult = func(result.Value);
if (nestedResult.IsFailure)
return nestedResult.Error;

return result;
}

public static async Task<Result<(TFirst, TSecond)>> Tap<TFirst, TSecond>(this Task<Result<(TFirst, TSecond)>> resultTask, Action<TFirst, TSecond> func)
public static async Task<Result<TValue>> Tap<TValue>(this Task<Result<TValue>> resultTask, Func<TValue, Result> func)
{
var result = await resultTask;
return result.Tap(func);
}

public static async Task<Result> TapAsync(this Result result, Func<Task> asyncFunc)
public static Result<(TFirst, TSecond)> Tap<TFirst, TSecond>(this Result<(TFirst, TSecond)> result, Action<TFirst, TSecond> func)
{
if (result.IsFailure)
return result.Error;
if (result.IsSuccess)
func(result.Value.Item1, result.Value.Item2);

await asyncFunc();
return Result.Success;
return result;
}

public static async Task<Result> TapAsync(this Task<Result> resultTask, Func<Task> asyncFunc)
public static async Task<Result<(TFirst, TSecond)>> Tap<TFirst, TSecond>(this Task<Result<(TFirst, TSecond)>> resultTask, Action<TFirst, TSecond> func)
{
var result = await resultTask;
return await result.TapAsync(asyncFunc);
return result.Tap(func);
}

public static async Task<Result<TValue>> TapAsync<TValue>(this Result<TValue> result, Func<TValue, Task> asyncFunc)
public static Result<(TFirst, TSecond)> Tap<TFirst, TSecond>(this Result<(TFirst, TSecond)> result, Func<TFirst, TSecond, Result> func)
{
if (result.IsFailure)
return result;

await asyncFunc(result.Value);
var nestedResult = func(result.Value.Item1, result.Value.Item2);
if (nestedResult.IsFailure)
return nestedResult.Error;

return result;
}

public static async Task<Result<TValue>> TapAsync<TValue>(this Task<Result<TValue>> resultTask, Func<TValue, Task> asyncFunc)
public static async Task<Result<(TFirst, TSecond)>> Tap<TFirst, TSecond>(this Task<Result<(TFirst, TSecond)>> resultTask, Func<TFirst, TSecond, Result> func)
{
var result = await resultTask;
return await result.TapAsync(asyncFunc);
return result.Tap(func);
}

[Obsolete("This methods seems useless, as Then methods should always unwrap returning result object.")]
public static async Task<Result<TValue>> Tap<TValue>(this Task<Result<Result<TValue>>> resultTask, Action<TValue> func)
{
var result = await resultTask;
if (result.IsFailure)
return result.Error;

return result.Value.Tap(func);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
namespace RailwayResult;

public static partial class ResultExtensions
{
public static async Task<Result> TapAsync(this Result result, Func<Task> asyncFunc)
{
if (result.IsSuccess)
await asyncFunc();

return result;
}

public static async Task<Result> TapAsync(this Task<Result> resultTask, Func<Task> asyncFunc)
{
var result = await resultTask;
return await result.TapAsync(asyncFunc);
}

public static async Task<Result> TapAsync(this Result result, Func<Task<Result>> asyncFunc)
{
if (result.IsSuccess)
return await asyncFunc();

return result;
}

public static async Task<Result> TapAsync(this Task<Result> resultTask, Func<Task<Result>> asyncFunc)
{
var result = await resultTask;
return await result.TapAsync(asyncFunc);
}

public static async Task<Result<TValue>> TapAsync<TValue>(this Result<TValue> result, Func<TValue, Task> asyncFunc)
{
if (result.IsSuccess)
await asyncFunc(result.Value);

return result;
}

public static async Task<Result<TValue>> TapAsync<TValue>(this Task<Result<TValue>> resultTask, Func<TValue, Task> asyncFunc)
{
var result = await resultTask;
return await result.TapAsync(asyncFunc);
}

public static async Task<Result<TValue>> TapAsync<TValue>(this Result<TValue> result, Func<TValue, Task<Result>> asyncFunc)
{
if (result.IsFailure)
return result;

var nestedResult = await asyncFunc(result.Value);
if (nestedResult.IsFailure)
return nestedResult.Error;

return result;
}

public static async Task<Result<TValue>> TapAsync<TValue>(this Task<Result<TValue>> resultTask, Func<TValue, Task<Result>> asyncFunc)
{
var result = await resultTask;
return await result.TapAsync(asyncFunc);
}

public static async Task<Result<(TFirst, TSecond)>> TapAsync<TFirst, TSecond>(this Result<(TFirst, TSecond)> result, Func<TFirst, TSecond, Task> asyncFunc)
{
if (result.IsSuccess)
await asyncFunc(result.Value.Item1, result.Value.Item2);

return result;
}

public static async Task<Result<(TFirst, TSecond)>> TapAsync<TFirst, TSecond>(this Task<Result<(TFirst, TSecond)>> resultTask, Func<TFirst, TSecond, Task> asyncFunc)
{
var result = await resultTask;
return await result.TapAsync(asyncFunc);
}

public static async Task<Result<(TFirst, TSecond)>> TapAsync<TFirst, TSecond>(this Result<(TFirst, TSecond)> result, Func<TFirst, TSecond, Task<Result>> asyncFunc)
{
if (result.IsFailure)
return result;

var nestedResult = await asyncFunc(result.Value.Item1, result.Value.Item2);
if (nestedResult.IsFailure)
return nestedResult.Error;

return result;
}

public static async Task<Result<(TFirst, TSecond)>> TapAsync<TFirst, TSecond>(this Task<Result<(TFirst, TSecond)>> resultTask, Func<TFirst, TSecond, Task<Result>> asyncFunc)
{
var result = await resultTask;
return await result.TapAsync(asyncFunc);
}
}
27 changes: 27 additions & 0 deletions tests/RailwayResult.FunctionalExtensions.Tests/Callback.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace RailwayResult.FunctionalExtensions.Tests;

public sealed class Callback
{
public bool WasCalled { get; private set; } = false;

public void Invoke() => WasCalled = true;

public Result ResultInvoke(BasicError? error = null)
{
WasCalled = true;
return error is null ? Result.Success : error;
}

public Task InvokeAsync()
{
WasCalled = true;
return Task.CompletedTask;
}

public Task<Result> ResultInvokeAsync(BasicError? error = null)
{
WasCalled = true;
return Task.FromResult(error is null ? Result.Success : error);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using RailwayResult.FunctionalExtensions.Tests.TapTests;

namespace RailwayResult.FunctionalExtensions.Tests.TapAsyncTests;

public sealed class TapAsyncTests
{
private Callback Callback { get; } = new();

[Theory]
[ClassData(typeof(TheoryData_R_TapAsync))]
public async Task R_TapAsync(Func<Result, Callback, Task<Result>> tapAsync, Result input, Result expectedOutput, bool wasCallbackInvoked)
{
var result = await tapAsync.Invoke(input, Callback);
result.ShouldBe(expectedOutput);
Callback.WasCalled.Should().Be(wasCallbackInvoked);
}

[Theory]
[ClassData(typeof(TheoryData_R1_TapAsync))]
public async Task R1_TapAsync(Func<R1, Callback, Task<R1>> tapAsync, R1 input, R1 expectedOutput, bool wasCallbackInvoked)
{
var result = await tapAsync.Invoke(input, Callback);
result.ShouldBe(expectedOutput);
Callback.WasCalled.Should().Be(wasCallbackInvoked);
}

[Theory]
[ClassData(typeof(TheoryData_R2_TapAsync))]
public async Task R2_TapAsync(Func<R2, Callback, Task<R2>> tapAsync, R2 input, R2 expectedOutput, bool wasCallbackInvoked)
{
var result = await tapAsync.Invoke(input, Callback);
result.ShouldBe(expectedOutput);
Callback.WasCalled.Should().Be(wasCallbackInvoked);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
namespace RailwayResult.FunctionalExtensions.Tests.TapAsyncTests;

public sealed class TheoryData_R1_TapAsync : TheoryData<Func<R1, Callback, Task<R1>>, R1, R1, bool>
{
public TheoryData_R1_TapAsync()
{
// --- R1 ThenAsync ---

//callback should be invoked and result stays on success railway
Add(
(result, callback) => result.TapAsync(_ => callback.InvokeAsync()),
O.A,
O.A,
true
);

//callback should not be invoked and result stays on failure railway
Add(
(result, callback) => result.TapAsync(_ => callback.InvokeAsync()),
Errors.ErrorA,
Errors.ErrorA,
false
);

//callback should be invoked and result stays on success railway
Add(
(result, callback) => result.TapAsync(_ => callback.ResultInvokeAsync()),
O.A,
O.A,
true
);

//callback should not be invoked and result stays on failure railway
Add(
(result, callback) => result.TapAsync(_ => callback.ResultInvokeAsync()),
Errors.ErrorA,
Errors.ErrorA,
false
);

//callback should be invoked and result should be on failure railway
Add(
(result, callback) => result.TapAsync(_ => callback.ResultInvokeAsync(Errors.ErrorD)),
O.A,
Errors.ErrorD,
true
);

//callback should not be invoked and result stays on failure railway
Add(
(result, callback) => result.TapAsync(_ => callback.ResultInvokeAsync(Errors.ErrorD)),
Errors.ErrorA,
Errors.ErrorA,
false
);

// --- TaskOfR1 ThenAsync ---

//callback should be invoked and result stays on success railway
Add(
(result, callback) => result.ToResultTask().TapAsync(_ => callback.InvokeAsync()),
O.A,
O.A,
true
);

//callback should not be invoked and result stays on failure railway
Add(
(result, callback) => result.ToResultTask().TapAsync(_ => callback.InvokeAsync()),
Errors.ErrorA,
Errors.ErrorA,
false
);

//callback should be invoked and result stays on success railway
Add(
(result, callback) => result.ToResultTask().TapAsync(_ => callback.ResultInvokeAsync()),
O.A,
O.A,
true
);

//callback should not be invoked and result stays on failure railway
Add(
(result, callback) => result.ToResultTask().TapAsync(_ => callback.ResultInvokeAsync()),
Errors.ErrorA,
Errors.ErrorA,
false
);

//callback should be invoked and result should be on failure railway
Add(
(result, callback) => result.ToResultTask().TapAsync(_ => callback.ResultInvokeAsync(Errors.ErrorD)),
O.A,
Errors.ErrorD,
true
);

//callback should not be invoked and result stays on failure railway
Add(
(result, callback) => result.ToResultTask().TapAsync(_ => callback.ResultInvokeAsync(Errors.ErrorD)),
Errors.ErrorA,
Errors.ErrorA,
false
);
}
}
Loading
Loading