Skip to content

Commit

Permalink
refactor: prevent exceptions in monads
Browse files Browse the repository at this point in the history
  • Loading branch information
Tr00d committed Nov 21, 2023
1 parent 55a09bc commit c26cdbb
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 25 deletions.
70 changes: 49 additions & 21 deletions Vonage.Common.Test/Monads/ResultTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ namespace Vonage.Common.Test.Monads
{
public class ResultTest
{
[Fact]
public void Bind_ShouldReturnFailure_GivenOperationThrowsException()
{
var expectedException = new Exception("Error");
CreateSuccess(5)
.Bind(value =>
{
throw expectedException;
return Result<int>.FromSuccess(value);
})
.Should()
.BeFailure(SystemFailure.FromException(expectedException));
}

[Fact]
public void Bind_ShouldReturnFailure_GivenValueIsFailure() =>
CreateFailure()
Expand All @@ -21,6 +35,20 @@ public void Bind_ShouldReturnSuccess_GivenValueIsSuccess() =>
.Should()
.BeSuccess(6);

[Fact]
public async Task BindAsync_ShouldReturnFailure_GivenOperationThrowsException()
{
var expectedException = new Exception("Error");
(await CreateSuccess(5)
.BindAsync(value =>
{
throw expectedException;
return Task.FromResult(Result<int>.FromSuccess(value));
}))
.Should()
.BeFailure(SystemFailure.FromException(expectedException));
}

[Fact]
public async Task BindAsync_ShouldReturnFailure_GivenValueBecomesFailure() =>
(await CreateSuccess(5)
Expand Down Expand Up @@ -315,13 +343,6 @@ public void ImplicitOperator_ShouldConvertToSuccess_GivenValueIsSuccess()
result.Should().BeSuccess(value);
}

[Fact]
public void Map_ShouldReturnFailure_GivenValueIsFailure() =>
CreateFailure()
.Map(Increment)
.Should()
.BeFailure(CreateResultFailure());

[Fact]
public void Map_ShouldReturnFailure_GivenOperationThrowsException()
{
Expand All @@ -336,13 +357,34 @@ public void Map_ShouldReturnFailure_GivenOperationThrowsException()
.BeFailure(SystemFailure.FromException(expectedException));
}

[Fact]
public void Map_ShouldReturnFailure_GivenValueIsFailure() =>
CreateFailure()
.Map(Increment)
.Should()
.BeFailure(CreateResultFailure());

[Fact]
public void Map_ShouldReturnSuccess_GivenValueIsSuccess() =>
CreateSuccess(5)
.Map(Increment)
.Should()
.BeSuccess(6);

[Fact]
public async Task MapAsync_ShouldReturnFailure_GivenOperationThrowsException()
{
var expectedException = new Exception("Error");
(await CreateSuccess(5)
.MapAsync(value =>
{
throw expectedException;
return Task.FromResult(value);
}))
.Should()
.BeFailure(SystemFailure.FromException(expectedException));
}

[Fact]
public async Task MapAsync_ShouldReturnFailure_GivenValueIsChainedFailure() =>
(await CreateFailure()
Expand All @@ -360,20 +402,6 @@ public async Task MapAsync_ShouldReturnFailure_GivenValueIsFailure() =>
.MapAsync(IncrementAsync))
.Should()
.BeFailure(CreateResultFailure());

[Fact]
public async Task MapAsync_ShouldReturnFailure_GivenOperationThrowsException()
{
var expectedException = new Exception("Error");
(await CreateSuccess(5)
.MapAsync(value =>
{
throw expectedException;
return Task.FromResult(value);
}))
.Should()
.BeFailure(SystemFailure.FromException(expectedException));
}

[Fact]
public async Task MapAsync_ShouldReturnSuccess_GivenValueIsChainedSuccess() =>
Expand Down
30 changes: 26 additions & 4 deletions Vonage.Common/Monads/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,39 @@ private Result(IResultFailure failure)
/// <param name="bind">Bind operation.</param>
/// <typeparam name="TB">Return type.</typeparam>
/// <returns>Bound functor.</returns>
public Result<TB> Bind<TB>(Func<T, Result<TB>> bind) =>
this.IsFailure ? Result<TB>.FromFailure(this.failure) : bind(this.success);
public Result<TB> Bind<TB>(Func<T, Result<TB>> bind)
{
try
{
return this.IsFailure
? Result<TB>.FromFailure(this.failure)
: bind(this.success);
}
catch (Exception exception)
{
return SystemFailure.FromException(exception).ToResult<TB>();
}
}

/// <summary>
/// Monadic bind operation.
/// </summary>
/// <param name="bind">Asynchronous bind operation.</param>
/// <typeparam name="TB">Return type.</typeparam>
/// <returns>Asynchronous bound functor.</returns>
public async Task<Result<TB>> BindAsync<TB>(Func<T, Task<Result<TB>>> bind) =>
this.IsFailure ? Result<TB>.FromFailure(this.failure) : await bind(this.success);
public async Task<Result<TB>> BindAsync<TB>(Func<T, Task<Result<TB>>> bind)
{
try
{
return this.IsFailure
? Result<TB>.FromFailure(this.failure)
: await bind(this.success);
}
catch (Exception exception)
{
return SystemFailure.FromException(exception).ToResult<TB>();
}
}

/// <inheritdoc />
public override bool Equals(object obj) => obj is Result<T> result && this.Equals(result);
Expand Down

0 comments on commit c26cdbb

Please sign in to comment.