chore: initial commit

This commit is contained in:
Stevan Freeborn
2026-03-30 12:51:19 -05:00
commit 1012decef0
26 changed files with 2619 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
[*.cs]
dotnet_diagnostic.CA1515.severity = none
dotnet_diagnostic.CA2007.severity = none
dotnet_diagnostic.CA2201.severity = none
dotnet_diagnostic.CA1707.severity = none
+8
View File
@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="TUnit" Version="0.73.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,57 @@
namespace StevanFreeborn.Results.Tests;
public class ErrorTests
{
[Test]
public async Task Constructor_WhenCalled_ItShouldSetCode()
{
var error = new Error("code", "message");
await Assert.That(error.Code).IsEqualTo("code");
}
[Test]
public async Task Constructor_WhenCalled_ItShouldSetMessage()
{
var error = new Error("code", "message");
await Assert.That(error.Message).IsEqualTo("message");
}
[Test]
public async Task Constructor_WhenCalledWithMetadata_ItShouldSetMetadata()
{
var metadata = new Dictionary<string, object> { { "key", "value" } };
var error = new Error("code", "message", metadata);
await Assert.That(error.Metadata!.Count).IsEqualTo(1);
await Assert.That(error.Metadata["key"]).IsEqualTo("value");
}
[Test]
public async Task Constructor_WhenCalledWithNullMetadata_ItShouldSetMetadataToNull()
{
var error = new Error("code", "message", null);
await Assert.That(error.Metadata!).IsNull();
}
[Test]
public async Task Equals_WhenErrorsHaveSameCodeAndMessage_ItShouldReturnTrue()
{
var error1 = new Error("code", "message");
var error2 = new Error("code", "message");
await Assert.That(error1).IsEqualTo(error2);
}
[Test]
public async Task Equals_WhenErrorsHaveDifferentCode_ItShouldReturnFalse()
{
var error1 = new Error("code1", "message");
var error2 = new Error("code2", "message");
await Assert.That(error1 == error2).IsFalse();
}
[Test]
public async Task Error_WhenCreated_ItShouldImplementIError()
{
var error = new Error("code", "message");
await Assert.That(error).IsAssignableTo<IError>();
}
}
@@ -0,0 +1,174 @@
using System.Globalization;
namespace StevanFreeborn.Results.Tests;
public class ResultAsyncExtensionsTests
{
[Test]
public async Task MapAsync_WhenResultIsSuccess_ItShouldInvokeAsyncAction()
{
var result = Result<Unit, Error>.Ok(default);
var invoked = false;
await result.MapAsync(_ => { invoked = true; return Task.CompletedTask; });
await Assert.That(invoked).IsTrue();
}
[Test]
public async Task MapAsync_WhenResultIsFailure_ItShouldSkipAsyncAction()
{
var result = Result<Unit, Error>.Fail(new Error("code", "message"));
var invoked = false;
await result.MapAsync(_ => { invoked = true; return Task.CompletedTask; });
await Assert.That(invoked).IsFalse();
}
[Test]
public async Task MapAsync_WhenCalled_ItShouldReturnOriginalResult()
{
var result = Result<Unit, Error>.Ok(default);
var mapped = await result.MapAsync(_ => Task.CompletedTask);
await Assert.That(mapped.IsSuccess).IsTrue();
}
[Test]
public async Task BindAsync_WhenResultIsSuccess_ItShouldInvokeBinderAndReturnResult()
{
var result = Result<Unit, Error>.Ok(default);
var bound = await result.BindAsync(_ => Task.FromResult(Result<Unit, Error>.Fail(new Error("new_error", "new message"))));
await Assert.That(bound.IsFailure).IsTrue();
}
[Test]
public async Task BindAsync_WhenResultIsFailure_ItShouldReturnOriginalResult()
{
var result = Result<Unit, Error>.Fail(new Error("code", "message"));
var bound = await result.BindAsync(_ => Task.FromResult(Result<Unit, Error>.Ok(default)));
await Assert.That(bound.IsFailure).IsTrue();
await Assert.That(bound.Error.Code).IsEqualTo("code");
}
[Test]
public async Task MatchAsync_WhenResultIsSuccess_ItShouldReturnOnSuccessValue()
{
var result = Result<Unit, Error>.Ok(default);
var matched = await result.MatchAsync(_ => Task.FromResult("success"), _ => Task.FromResult("failure"));
await Assert.That(matched).IsEqualTo("success");
}
[Test]
public async Task MatchAsync_WhenResultIsFailure_ItShouldReturnOnFailureValue()
{
var result = Result<Unit, Error>.Fail(new Error("code", "message"));
var matched = await result.MatchAsync(_ => Task.FromResult("success"), e => Task.FromResult(e.Code));
await Assert.That(matched).IsEqualTo("code");
}
[Test]
public async Task TryAsync_WhenActionSucceeds_ItShouldReturnOk()
{
var result = await ResultAsyncExtensions.TryAsync(() => Task.CompletedTask);
await Assert.That(result.IsSuccess).IsTrue();
}
[Test]
public async Task TryAsync_WhenActionThrowsException_ItShouldReturnFailWithUnexpectedError()
{
var result = await ResultAsyncExtensions.TryAsync(() => throw new InvalidOperationException("test error"));
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error.Code).IsEqualTo("UnexpectedError");
}
[Test]
public async Task TryAsync_WhenActionThrowsException_ItShouldUseCustomError()
{
var result = await ResultAsyncExtensions.TryAsync(
() => throw new InvalidOperationException("test"),
ex => new Error("custom", ex.Message));
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error.Code).IsEqualTo("custom");
}
[Test]
public async Task MapAsync_WhenResultIsSuccess_ItShouldTransformValue()
{
var result = Result<int, Error>.Ok(5);
var mapped = await result.MapAsync(async n => (await Task.FromResult(n)).ToString(CultureInfo.InvariantCulture));
await Assert.That(mapped.IsSuccess).IsTrue();
await Assert.That(mapped.Value).IsEqualTo("5");
}
[Test]
public async Task MapAsync_WhenResultIsFailure_ItShouldPropagateError()
{
var error = new Error("code", "message");
var result = Result<int, Error>.Fail(error);
var mapped = await result.MapAsync(async n => (await Task.FromResult(n)).ToString(CultureInfo.InvariantCulture));
await Assert.That(mapped.IsFailure).IsTrue();
await Assert.That(mapped.Error).IsEqualTo(error);
}
[Test]
public async Task BindAsync_WhenResultIsSuccess_ItShouldChainToBinderResult()
{
var result = Result<int, Error>.Ok(5);
var bound = await result.BindAsync(async n => await Task.FromResult(Result<string, Error>.Ok(n.ToString(CultureInfo.InvariantCulture))));
await Assert.That(bound.IsSuccess).IsTrue();
await Assert.That(bound.Value).IsEqualTo("5");
}
[Test]
public async Task BindAsync_WhenResultIsFailure_ItShouldPropagateError()
{
var error = new Error("code", "message");
var result = Result<int, Error>.Fail(error);
var bound = await result.BindAsync(async n => await Task.FromResult(Result<string, Error>.Ok(n.ToString(CultureInfo.InvariantCulture))));
await Assert.That(bound.IsFailure).IsTrue();
await Assert.That(bound.Error).IsEqualTo(error);
}
[Test]
public async Task MatchAsyncGeneric_WhenResultIsSuccess_ItShouldReturnOnSuccessValue()
{
var result = Result<string, Error>.Ok("test");
var matched = await result.MatchAsync(
v => Task.FromResult(v.Length),
_ => Task.FromResult(0));
await Assert.That(matched).IsEqualTo(4);
}
[Test]
public async Task MatchAsyncGeneric_WhenResultIsFailure_ItShouldReturnOnFailureValue()
{
var result = Result<string, Error>.Fail(new Error("code", "message"));
var matched = await result.MatchAsync(
v => Task.FromResult(v.Length),
_ => Task.FromResult(-1));
await Assert.That(matched).IsEqualTo(-1);
}
[Test]
public async Task TryAsync_WhenFuncSucceeds_ItShouldReturnOkWithValue()
{
var result = await ResultAsyncExtensions.TryAsync(async () => await Task.FromResult(42));
await Assert.That(result.IsSuccess).IsTrue();
await Assert.That(result.Value).IsEqualTo(42);
}
[Test]
public async Task TryAsync_WhenFuncThrowsException_ItShouldReturnFailWithUnexpectedError()
{
var result = await ResultAsyncExtensions.TryAsync<int>(() => throw new InvalidOperationException("test error"));
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error.Code).IsEqualTo("UnexpectedError");
}
[Test]
public async Task TryAsync_WhenFuncThrowsException_ItShouldUseCustomError()
{
var result = await ResultAsyncExtensions.TryAsync<int>(
() => throw new InvalidOperationException("test"),
ex => new Error("custom", ex.Message));
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error.Code).IsEqualTo("custom");
}
}
@@ -0,0 +1,183 @@
using System.Globalization;
namespace StevanFreeborn.Results.Tests;
public class ResultTTests
{
[Test]
public async Task Ok_WhenCalledWithValue_ItShouldReturnSuccessResult()
{
var result = Result<string, Error>.Ok("test");
await Assert.That(result.IsSuccess).IsTrue();
await Assert.That(result.IsFailure).IsFalse();
await Assert.That(result.Value).IsEqualTo("test");
}
[Test]
public async Task Fail_WhenCalled_ItShouldReturnFailureResult()
{
var error = new Error("code", "message");
var result = Result<string, Error>.Fail(error);
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.IsSuccess).IsFalse();
await Assert.That(result.Error).IsEqualTo(error);
}
[Test]
public async Task Value_WhenResultIsFailure_ItShouldThrowInvalidOperationException()
{
var result = Result<string, Error>.Fail(new Error("code", "message"));
Assert.Throws<InvalidOperationException>(() => _ = result.Value);
}
[Test]
public async Task Error_WhenResultIsSuccess_ItShouldThrowInvalidOperationException()
{
var result = Result<string, Error>.Ok("test");
Assert.Throws<InvalidOperationException>(() => _ = result.Error);
}
[Test]
public async Task ImplicitConversion_FromValue_ItShouldReturnSuccessResult()
{
Result<string, Error> result = "test";
await Assert.That(result.IsSuccess).IsTrue();
await Assert.That(result.Value).IsEqualTo("test");
}
[Test]
public async Task ImplicitConversion_FromError_ItShouldReturnFailureResult()
{
var error = new Error("code", "message");
Result<string, Error> result = error;
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error).IsEqualTo(error);
}
[Test]
public async Task Map_WhenResultIsSuccess_ItShouldTransformValue()
{
var result = Result<int, Error>.Ok(5);
var mapped = result.Map(n => n.ToString(CultureInfo.InvariantCulture));
await Assert.That(mapped.IsSuccess).IsTrue();
await Assert.That(mapped.Value).IsEqualTo("5");
}
[Test]
public async Task Map_WhenResultIsFailure_ItShouldPropagateError()
{
var error = new Error("code", "message");
var result = Result<int, Error>.Fail(error);
var mapped = result.Map(n => n.ToString(CultureInfo.InvariantCulture));
await Assert.That(mapped.IsFailure).IsTrue();
await Assert.That(mapped.Error).IsEqualTo(error);
}
[Test]
public async Task MapError_WhenResultIsFailure_ItShouldTransformError()
{
var originalError = new Error("original", "original message");
var result = Result<int, Error>.Fail(originalError);
var transformed = result.MapError(e => new Error("transformed", e.Message));
await Assert.That(transformed.IsFailure).IsTrue();
await Assert.That(transformed.Error.Code).IsEqualTo("transformed");
}
[Test]
public async Task MapError_WhenResultIsSuccess_ItShouldReturnUnchanged()
{
var result = Result<int, Error>.Ok(5);
var mapped = result.MapError(e => new Error("transformed", e.Message));
await Assert.That(mapped.IsSuccess).IsTrue();
await Assert.That(mapped.Value).IsEqualTo(5);
}
[Test]
public async Task Bind_WhenResultIsSuccess_ItShouldChainToBinderResult()
{
var result = Result<int, Error>.Ok(5);
var bound = result.Bind(n => Result<string, Error>.Ok(n.ToString(CultureInfo.InvariantCulture)));
await Assert.That(bound.IsSuccess).IsTrue();
await Assert.That(bound.Value).IsEqualTo("5");
}
[Test]
public async Task Bind_WhenResultIsFailure_ItShouldPropagateError()
{
var error = new Error("code", "message");
var result = Result<int, Error>.Fail(error);
var bound = result.Bind(n => Result<string, Error>.Ok(n.ToString(CultureInfo.InvariantCulture)));
await Assert.That(bound.IsFailure).IsTrue();
await Assert.That(bound.Error).IsEqualTo(error);
}
[Test]
public async Task Bind_WhenResultIsSuccess_ItShouldReturnBinderFailureResult()
{
var result = Result<int, Error>.Ok(5);
var bound = result.Bind(_ => Result<string, Error>.Fail(new Error("bind_error", "bound failed")));
await Assert.That(bound.IsFailure).IsTrue();
await Assert.That(bound.Error.Code).IsEqualTo("bind_error");
}
[Test]
public async Task Match_WhenResultIsSuccess_ItShouldReturnOnSuccessValue()
{
var result = Result<string, Error>.Ok("test");
var matched = result.Match(v => v.Length, _ => 0);
await Assert.That(matched).IsEqualTo(4);
}
[Test]
public async Task Match_WhenResultIsFailure_ItShouldReturnOnFailureValue()
{
var result = Result<string, Error>.Fail(new Error("code", "message"));
var matched = result.Match(v => v.Length, e => -1);
await Assert.That(matched).IsEqualTo(-1);
}
[Test]
public async Task Match_WhenResultIsSuccess_ItShouldInvokeOnSuccess()
{
var result = Result<string, Error>.Ok("test");
var invoked = false;
result.Match(v => { invoked = true; }, _ => { });
await Assert.That(invoked).IsTrue();
}
[Test]
public async Task Match_WhenResultIsFailure_ItShouldInvokeOnFailure()
{
var error = new Error("code", "message");
var result = Result<string, Error>.Fail(error);
var invoked = false;
result.Match(_ => { }, e => { invoked = true; });
await Assert.That(invoked).IsTrue();
}
[Test]
public async Task Try_WhenFuncSucceeds_ItShouldReturnOkWithValue()
{
var result = Result<int, Error>.Try(() => 42);
await Assert.That(result.IsSuccess).IsTrue();
await Assert.That(result.Value).IsEqualTo(42);
}
[Test]
public async Task Try_WhenFuncThrowsException_ItShouldReturnFailWithUnexpectedError()
{
var result = Result<int, Error>.Try(() => throw new InvalidOperationException("test error"));
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error.Code).IsEqualTo("UnexpectedError");
}
[Test]
public async Task Try_WhenFuncThrowsException_ItShouldUseCustomError()
{
var result = Result<int, Error>.Try(() => throw new InvalidOperationException("test"), ex => new Error("custom", ex.Message));
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error.Code).IsEqualTo("custom");
}
}
@@ -0,0 +1,146 @@
namespace StevanFreeborn.Results.Tests;
public class ResultTests
{
[Test]
public async Task Ok_WhenCalled_ItShouldReturnSuccessResult()
{
var result = Result<Unit, Error>.Ok(default);
await Assert.That(result.IsSuccess).IsTrue();
await Assert.That(result.IsFailure).IsFalse();
}
[Test]
public async Task Fail_WhenCalled_ItShouldReturnFailureResult()
{
var error = new Error("code", "message");
var result = Result<Unit, Error>.Fail(error);
await Assert.That(result.IsSuccess).IsFalse();
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error).IsEqualTo(error);
}
[Test]
public async Task Error_WhenResultIsSuccess_ItShouldThrowInvalidOperationException()
{
Assert.Throws<InvalidOperationException>(() => _ = Result<Unit, Error>.Ok(default).Error);
}
[Test]
public async Task ImplicitConversion_FromError_ItShouldReturnFailureResult()
{
var error = new Error("code", "message");
Result<Unit, Error> result = error;
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error).IsEqualTo(error);
}
[Test]
public async Task Map_WhenResultIsSuccess_ItShouldInvokeAction()
{
var result = Result<Unit, Error>.Ok(default);
var invoked = false;
result.Map(_ => invoked = true);
await Assert.That(invoked).IsTrue();
}
[Test]
public async Task Map_WhenResultIsFailure_ItShouldSkipAction()
{
var result = Result<Unit, Error>.Fail(new Error("code", "message"));
var invoked = false;
result.Map(_ => invoked = true);
await Assert.That(invoked).IsFalse();
}
[Test]
public async Task Map_WhenCalled_ItShouldReturnOriginalResult()
{
var result = Result<Unit, Error>.Ok(default);
var mapped = result.Map(_ => { });
await Assert.That(mapped.IsSuccess).IsTrue();
}
[Test]
public async Task Bind_WhenResultIsSuccess_ItShouldInvokeBinder()
{
var result = Result<Unit, Error>.Ok(default);
var bound = result.Bind(_ => Result<Unit, Error>.Ok(default));
await Assert.That(bound.IsSuccess).IsTrue();
}
[Test]
public async Task Bind_WhenResultIsFailure_ItShouldReturnOriginalResult()
{
var result = Result<Unit, Error>.Fail(new Error("code", "message"));
var bound = result.Bind(_ => Result<Unit, Error>.Ok(default));
await Assert.That(bound.IsFailure).IsTrue();
await Assert.That(bound.Error).IsEqualTo(result.Error);
}
[Test]
public async Task Bind_WhenResultIsSuccess_ItShouldReturnBinderResult()
{
var result = Result<Unit, Error>.Ok(default);
var bound = result.Bind(_ => Result<Unit, Error>.Fail(new Error("new_error", "new message")));
await Assert.That(bound.IsFailure).IsTrue();
}
[Test]
public async Task Match_WhenResultIsSuccess_ItShouldReturnOnSuccessValue()
{
var result = Result<Unit, Error>.Ok(default);
var matched = result.Match(_ => "success", _ => "failure");
await Assert.That(matched).IsEqualTo("success");
}
[Test]
public async Task Match_WhenResultIsFailure_ItShouldReturnOnFailureValue()
{
var result = Result<Unit, Error>.Fail(new Error("code", "message"));
var matched = result.Match(_ => "success", e => e.Code);
await Assert.That(matched).IsEqualTo("code");
}
[Test]
public async Task Match_WhenResultIsSuccess_ItShouldInvokeOnSuccess()
{
var result = Result<Unit, Error>.Ok(default);
var invoked = false;
result.Match(_ => invoked = true, _ => { });
await Assert.That(invoked).IsTrue();
}
[Test]
public async Task Match_WhenResultIsFailure_ItShouldInvokeOnFailure()
{
var error = new Error("code", "message");
var result = Result<Unit, Error>.Fail(error);
var invoked = false;
result.Match(_ => { }, e => invoked = true);
await Assert.That(invoked).IsTrue();
}
[Test]
public async Task Try_WhenActionSucceeds_ItShouldReturnOk()
{
var result = Result<Unit, Error>.Try(() => default(Unit));
await Assert.That(result.IsSuccess).IsTrue();
}
[Test]
public async Task Try_WhenActionThrowsException_ItShouldReturnFailWithUnexpectedError()
{
var result = Result<Unit, Error>.Try(() => throw new Exception("test error"));
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error.Code).IsEqualTo("UnexpectedError");
}
[Test]
public async Task Try_WhenActionThrowsException_ItShouldUseCustomError()
{
var result = Result<Unit, Error>.Try(() => throw new Exception("test"), ex => new Error("custom", ex.Message));
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(result.Error.Code).IsEqualTo("custom");
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>StevanFreeborn.Results.Tests</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TUnit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\StevanFreeborn.Results\StevanFreeborn.Results.csproj" />
</ItemGroup>
</Project>