diff --git a/Directory.Build.props b/Directory.Build.props
index 7996aa7..cf00c22 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -34,4 +34,4 @@
moderate
all
-
\ No newline at end of file
+
diff --git a/request.slnx b/request.slnx
index af7d750..9ec504a 100644
--- a/request.slnx
+++ b/request.slnx
@@ -1,4 +1,6 @@
+
+
diff --git a/src/Request.Result.Tests/.editorconfig b/src/Request.Result.Tests/.editorconfig
new file mode 100644
index 0000000..78f1f31
--- /dev/null
+++ b/src/Request.Result.Tests/.editorconfig
@@ -0,0 +1,9 @@
+
+[*.{cs,vb}]
+# disable CA1822: Mark members as static
+# -> TUnit requiring instance methods for test cases
+dotnet_diagnostic.CA1822.severity = none
+# disable CA1707: Identifiers should not contain underscores
+dotnet_diagnostic.CA1707.severity = none
+# disable IDE0060: Remove unused parameter
+dotnet_diagnostic.IDE0060.severity = none
diff --git a/src/Request.Result.Tests/ErrorTests.cs b/src/Request.Result.Tests/ErrorTests.cs
new file mode 100644
index 0000000..01bbeed
--- /dev/null
+++ b/src/Request.Result.Tests/ErrorTests.cs
@@ -0,0 +1,29 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class ErrorTests
+{
+ [Test]
+ public async Task I_can_implicitly_convert_from_string_and_get_string_error()
+ {
+ Error error = "error";
+
+ using var scope = Assert.Multiple();
+ await Assert.That(error).IsTypeOf();
+ await Assert.That(error.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_implicitly_convert_from_exception_and_get_exception_error()
+ {
+ Error error = new CustomTestException();
+
+ using var scope = Assert.Multiple();
+ var instance = await Assert.That(error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/ExtensionsEnumerableTests.cs b/src/Request.Result.Tests/ExtensionsEnumerableTests.cs
new file mode 100644
index 0000000..64ac5ff
--- /dev/null
+++ b/src/Request.Result.Tests/ExtensionsEnumerableTests.cs
@@ -0,0 +1,52 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class ExtensionsEnumerableTests
+{
+ [Test]
+ public async Task I_can_join_sequence_and_get_all_success_when_all_elements_are_success()
+ {
+ IEnumerable> xs = [1, 2, 3, 4, 5];
+
+ var result = xs.Join();
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEquivalentTo([1, 2, 3, 4, 5]);
+ }
+
+ [Test]
+ public async Task I_can_join_sequence_and_get_first_failure_when_sequence_contains_failure()
+ {
+ IEnumerable> xs =
+ [
+ Prelude.Success(1),
+ Prelude.Success(2),
+ Prelude.Failure("error 1"),
+ Prelude.Success(4),
+ Prelude.Failure("error 2")
+ ];
+
+ var result = xs.Join();
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error 1");
+ }
+
+ [Test]
+ public async Task I_can_join_empty_sequence_and_get_success()
+ {
+ IEnumerable> xs = [];
+
+ var result = xs.Join();
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEmpty();
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/Geekeey.Request.Result.Tests.csproj b/src/Request.Result.Tests/Geekeey.Request.Result.Tests.csproj
new file mode 100644
index 0000000..6781d57
--- /dev/null
+++ b/src/Request.Result.Tests/Geekeey.Request.Result.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net10.0
+ false
+
+
+
+ Geekeey.Request.Result
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Request.Result.Tests/PreludeTests.cs b/src/Request.Result.Tests/PreludeTests.cs
new file mode 100644
index 0000000..5065395
--- /dev/null
+++ b/src/Request.Result.Tests/PreludeTests.cs
@@ -0,0 +1,102 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class PreludeTests
+{
+ [Test]
+ public async Task I_can_try_with_success_value_and_get_a_success_result()
+ {
+ var result = Prelude.Try(() => 2);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_try_with_throwing_exception_and_get_a_failure_result()
+ {
+ var result = Prelude.Try(() => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_with_async_success_value_and_get_a_success_result()
+ {
+ var result = await Prelude.TryAsync(() => Task.FromResult(2));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_try_with_async_throwing_exception_and_get_a_failure_result()
+ {
+ var result = await Prelude.TryAsync(Task () => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_with_async_await_throwing_exception_and_get_a_failure_result()
+ {
+ var result = await Prelude.TryAsync(async Task () =>
+ {
+ await Task.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_with_async_success_value_and_get_a_success_result_of_type_ValueTask()
+ {
+ var result = await Prelude.TryAsync(() => ValueTask.FromResult(2));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_try_with_async_throwing_exception_and_get_a_failure_result_of_type_ValueTask()
+ {
+ var result = await Prelude.TryAsync(ValueTask () => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_with_async_await_throwing_exception_and_get_a_failure_result_of_type_ValueTask()
+ {
+ var result = await Prelude.TryAsync(async ValueTask () =>
+ {
+ await Task.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/ResultConversionTests.cs b/src/Request.Result.Tests/ResultConversionTests.cs
new file mode 100644
index 0000000..2fea25f
--- /dev/null
+++ b/src/Request.Result.Tests/ResultConversionTests.cs
@@ -0,0 +1,66 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class ResultConversionTests
+{
+ [Test]
+ public async Task I_can_implicitly_convert_from_value_and_get_success()
+ {
+ var result = Prelude.Success(2);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.IsFailure).IsFalse();
+ await Assert.That(result.Value).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_implicitly_convert_from_error_and_get_failure()
+ {
+ var error = new CustomTestError();
+ var result = Prelude.Failure(error);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.IsFailure).IsTrue();
+ await Assert.That(result.Error).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_unwrap_and_get_value_for_success()
+ {
+ var result = Prelude.Success(2);
+ var value = result.Unwrap();
+
+ await Assert.That(value).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_unwrap_and_get_exception_for_failure()
+ {
+ var result = Prelude.Failure("error");
+
+ await Assert.That(result.Unwrap).Throws();
+ }
+
+ [Test]
+ public async Task I_can_explicitly_convert_and_get_value_for_success()
+ {
+ var result = Prelude.Success(2);
+ var value = (int)result;
+
+ await Assert.That(value).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_explicitly_convert_and_get_exception_for_failure()
+ {
+ var result = Prelude.Failure("error");
+
+ await Assert.That(() => (int)result).Throws();
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/ResultEqualityTests.cs b/src/Request.Result.Tests/ResultEqualityTests.cs
new file mode 100644
index 0000000..ca136be
--- /dev/null
+++ b/src/Request.Result.Tests/ResultEqualityTests.cs
@@ -0,0 +1,177 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class ResultEqualityTests
+{
+ [Test]
+ public async Task I_can_equal_t_and_get_true_for_success_with_equal_value()
+ {
+ var a = Prelude.Success(2);
+ var b = 2;
+
+ await Assert.That(a.Equals(b)).IsTrue();
+ }
+
+ [Test]
+ public async Task I_can_equal_t_and_get_false_for_success_with_unequal_value()
+ {
+ var a = Prelude.Success(2);
+ var b = 3;
+
+ await Assert.That(a.Equals(b)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equal_t_and_get_false_for_failure()
+ {
+ var a = Prelude.Failure("error");
+ var b = 2;
+
+ await Assert.That(a.Equals(b)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equal_result_and_get_true_for_success_and_success_with_equal_value()
+ {
+ var a = Prelude.Success(2);
+ var b = Prelude.Success(2);
+
+ await Assert.That(a.Equals(b)).IsTrue();
+ }
+
+ [Test]
+ public async Task I_can_equal_result_and_get_false_for_success_and_success_with_unequal_value()
+ {
+ var a = Prelude.Success(2);
+ var b = Prelude.Success(3);
+
+ await Assert.That(a.Equals(b)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equals_result_and_get_false_for_success_and_failure()
+ {
+ var a = Prelude.Success(2);
+ var b = Prelude.Failure("error 1");
+
+ await Assert.That(a.Equals(b)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equals_result_and_get_false_for_failure_and_success()
+ {
+ var a = Prelude.Failure("error");
+ var b = Prelude.Success(2);
+
+ await Assert.That(a.Equals(b)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equals_result_and_get_true_for_failure_and_failure()
+ {
+ var a = Prelude.Failure("error 1");
+ var b = Prelude.Failure("error 2");
+
+ await Assert.That(a.Equals(b)).IsTrue();
+ }
+
+ [Test]
+ public async Task I_can_equal_t_and_get_true_for_success_with_equal_value_using_comparer()
+ {
+ var a = Prelude.Success(2);
+ var b = 2;
+
+ await Assert.That(a.Equals(b, EqualityComparer.Default)).IsTrue();
+ }
+
+ [Test]
+ public async Task I_can_equal_t_and_get_false_for_success_with_unequal_value_using_comparer()
+ {
+ var a = Prelude.Success(2);
+ var b = 3;
+
+ await Assert.That(a.Equals(b, EqualityComparer.Default)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equal_t_and_get_false_for_failure_using_comparer()
+ {
+ var a = Prelude.Failure("error");
+ var b = 2;
+
+ await Assert.That(a.Equals(b, EqualityComparer.Default)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equal_result_and_get_true_for_success_and_success_with_equal_value_using_comparer()
+ {
+ var a = Prelude.Success(2);
+ var b = Prelude.Success(2);
+
+ await Assert.That(a.Equals(b, EqualityComparer.Default)).IsTrue();
+ }
+
+ [Test]
+ public async Task I_can_equal_result_and_get_false_for_success_and_success_with_unequal_value_using_comparer()
+ {
+ var a = Prelude.Success(2);
+ var b = Prelude.Success(3);
+
+ await Assert.That(a.Equals(b, EqualityComparer.Default)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equals_result_and_get_false_for_success_and_failure_using_comparer()
+ {
+ var a = Prelude.Success(2);
+ var b = Prelude.Failure("error 1");
+
+ await Assert.That(a.Equals(b, EqualityComparer.Default)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equals_result_and_get_false_for_failure_and_success_using_comparer()
+ {
+ var a = Prelude.Failure("error");
+ var b = Prelude.Success(2);
+
+ await Assert.That(a.Equals(b, EqualityComparer.Default)).IsFalse();
+ }
+
+ [Test]
+ public async Task I_can_equals_result_and_get_true_for_failure_and_failure_using_comparer()
+ {
+ var a = Prelude.Failure("error 1");
+ var b = Prelude.Failure("error 2");
+
+ await Assert.That(a.Equals(b, EqualityComparer.Default)).IsTrue();
+ }
+
+ [Test]
+ public async Task I_can_get_hashcode_and_get_hashcode_for_success()
+ {
+ var result = Prelude.Success(2);
+
+ await Assert.That(result.GetHashCode()).IsEqualTo(2.GetHashCode());
+ }
+
+ [Test]
+ public async Task I_can_get_hashcode_and_get_zero_for_null()
+ {
+ var result = Prelude.Success(null);
+
+ await Assert.That(result.GetHashCode()).IsZero();
+ }
+
+ [Test]
+ public async Task I_can_get_hashcode_and_get_zero_for_failure()
+ {
+ var result = Prelude.Failure("error");
+
+ await Assert.That(result.GetHashCode()).IsZero();
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/ResultMatchingTests.cs b/src/Request.Result.Tests/ResultMatchingTests.cs
new file mode 100644
index 0000000..662c3dc
--- /dev/null
+++ b/src/Request.Result.Tests/ResultMatchingTests.cs
@@ -0,0 +1,234 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class ResultMatchingTests
+{
+ [Test]
+ public async Task I_can_match_and_it_calls_success_func_for_success()
+ {
+ var result = Prelude.Success(2);
+ var match = result.Match(
+ v => v,
+ _ => throw new InvalidOperationException());
+
+ await Assert.That(match).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_match_and_it_calls_failure_func_for_failure()
+ {
+ var result = Prelude.Failure("error");
+ var match = result.Match(
+ _ => throw new InvalidOperationException(),
+ e => e);
+
+ await Assert.That(match.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_switch_and_it_calls_success_action_for_success()
+ {
+ var called = false;
+ var value = default(int);
+
+ var result = Prelude.Success(2);
+ result.Switch(OnSuccess, OnFailure);
+
+ await Assert.That(called).IsTrue();
+ await Assert.That(value).IsEqualTo(2);
+
+ return;
+
+ void OnSuccess(int i)
+ {
+ value = i;
+ called = true;
+ }
+
+ void OnFailure(Error e)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ [Test]
+ public async Task I_can_switch_and_it_calls_failure_action_for_failure()
+ {
+ var called = false;
+ var value = default(Error);
+
+ var result = Prelude.Failure("error");
+ result.Switch(OnSuccess, OnFailure);
+
+ await Assert.That(called).IsTrue();
+ await Assert.That(value?.Message).IsEqualTo("error");
+
+ return;
+
+ void OnSuccess(int i)
+ {
+ throw new InvalidOperationException();
+ }
+
+ void OnFailure(Error e)
+ {
+ value = e;
+ called = true;
+ }
+ }
+
+ [Test]
+ public async Task I_can_match_async_and_it_calls_success_func_for_success()
+ {
+ var result = Prelude.Success(2);
+ var match = await result.MatchAsync(
+ Task.FromResult,
+ _ => throw new InvalidOperationException());
+
+ await Assert.That(match).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_match_async_and_it_calls_failure_func_for_failure()
+ {
+ var result = Prelude.Failure("error");
+ var match = await result.MatchAsync(
+ _ => throw new InvalidOperationException(),
+ Task.FromResult);
+
+ await Assert.That(match.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_switch_async_and_it_calls_success_action_for_success()
+ {
+ var called = false;
+ var value = default(int);
+
+ var result = Prelude.Success(2);
+ await result.SwitchAsync(OnSuccess, OnFailure);
+
+ await Assert.That(called).IsTrue();
+ await Assert.That(value).IsEqualTo(2);
+ return;
+
+ Task OnSuccess(int i)
+ {
+ value = i;
+ called = true;
+ return Task.CompletedTask;
+ }
+
+ Task OnFailure(Error e)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ [Test]
+ public async Task I_can_switch_async_and_it_calls_failure_action_for_failure()
+ {
+ var called = false;
+ var value = default(Error);
+
+ var result = Prelude.Failure("error");
+ await result.SwitchAsync(OnSuccess, OnFailure);
+
+ await Assert.That(called).IsTrue();
+ await Assert.That(value?.Message).IsEqualTo("error");
+
+ return;
+
+ Task OnSuccess(int i)
+ {
+ throw new InvalidOperationException();
+ }
+
+ Task OnFailure(Error e)
+ {
+ value = e;
+ called = true;
+ return Task.CompletedTask;
+ }
+ }
+
+ [Test]
+ public async Task I_can_match_and_it_calls_success_func_for_success_ValueTask()
+ {
+ var result = Prelude.Success(2);
+ var match = await result.MatchAsync(
+ ValueTask.FromResult,
+ _ => throw new InvalidOperationException());
+
+ await Assert.That(match).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_match_async_and_it_calls_failure_func_for_failure_ValueTask()
+ {
+ var result = Prelude.Failure("error");
+ var match = await result.MatchAsync(
+ _ => throw new InvalidOperationException(),
+ ValueTask.FromResult);
+
+ await Assert.That(match.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_switch_async_and_it_calls_success_action_for_success_ValueTask()
+ {
+ var called = false;
+ var value = default(int);
+
+ var result = Prelude.Success(2);
+ await result.SwitchAsync(OnSuccess, OnFailure);
+
+ await Assert.That(called).IsTrue();
+ await Assert.That(value).IsEqualTo(2);
+
+ return;
+
+ ValueTask OnSuccess(int i)
+ {
+ value = i;
+ called = true;
+ return ValueTask.CompletedTask;
+ }
+
+ ValueTask OnFailure(Error e)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ [Test]
+ public async Task I_can_switch_async_and_it_calls_failure_action_for_failure_ValueTask()
+ {
+ var called = false;
+ var value = default(Error);
+
+ var result = Prelude.Failure("error");
+ await result.SwitchAsync(OnSuccess, OnFailure);
+
+ await Assert.That(called).IsTrue();
+ await Assert.That(value?.Message).IsEqualTo("error");
+
+ return;
+
+ ValueTask OnSuccess(int i)
+ {
+ throw new InvalidOperationException();
+ }
+
+ ValueTask OnFailure(Error e)
+ {
+ value = e;
+ called = true;
+ return ValueTask.CompletedTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/ResultTests.cs b/src/Request.Result.Tests/ResultTests.cs
new file mode 100644
index 0000000..5bf2a6f
--- /dev/null
+++ b/src/Request.Result.Tests/ResultTests.cs
@@ -0,0 +1,61 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class ResultTests
+{
+ [Test]
+ public async Task I_can_create_new_success_result_from_t()
+ {
+ var result = new Result(1);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.IsFailure).IsFalse();
+ await Assert.That(result.Value).IsNotEqualTo(default);
+ await Assert.That(result.Error).IsNull();
+ }
+
+ [Test]
+ public async Task I_can_create_new_failure_result_from_error()
+ {
+ var result = new Result(new CustomTestError());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.IsFailure).IsTrue();
+ await Assert.That(result.Value).IsEqualTo(default(int));
+ await Assert.That(result.Error).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_distinguish_default_result_from_created()
+ {
+ var result = default(Result);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.IsFailure).IsTrue();
+ await Assert.That(result.Value).IsEqualTo(default(int));
+ await Assert.That(result.Error).IsEqualTo(Error.DefaultValueError);
+ }
+
+ [Test]
+ public async Task I_can_to_string_success_result_value()
+ {
+ Result result = 2;
+
+ await Assert.That(result.ToString()).IsEqualTo("Success { 2 }");
+ }
+
+ [Test]
+ public async Task I_can_to_string_failure_result_value()
+ {
+ Result result = new StringError("error");
+
+ await Assert.That(result.ToString()).IsEqualTo("Failure { error }");
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/ResultTransformTests.cs b/src/Request.Result.Tests/ResultTransformTests.cs
new file mode 100644
index 0000000..ca62f85
--- /dev/null
+++ b/src/Request.Result.Tests/ResultTransformTests.cs
@@ -0,0 +1,644 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class ResultTransformTests
+{
+ [Test]
+ public async Task I_can_map_and_it_returns_success_for_success()
+ {
+ var start = Prelude.Success(2);
+ var result = start.Map(value => value.ToString());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_map_and_it_returns_failure_for_failure()
+ {
+ var start = Prelude.Failure("error");
+ var result = start.Map(value => value.ToString());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_with_then_and_it_returns_success_for_success_and_mapping_returning_success()
+ {
+ var start = Prelude.Success(2);
+ var result = start.Then(value => Prelude.Success(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_with_then_and_it_returns_failure_for_success_and_mapping_returning_failure()
+ {
+ var start = Prelude.Success(2);
+ var result = start.Then(_ => Prelude.Failure("error"));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_with_then_and_it_returns_failure_for_failure_and_mapping_returning_success()
+ {
+ var start = Prelude.Failure("error");
+ var result = start.Then(value => Prelude.Success(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_with_then_and_it_returns_failure_for_failure_and_mapping_returning_failure()
+ {
+ var start = Prelude.Failure("error");
+ var result = start.Then(_ => Prelude.Failure("error 2"));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_map_and_it_returns_success_for_success_without_throwing()
+ {
+ var start = Prelude.Success(2);
+ var result = start.TryMap(value => value.ToString());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_try_map_and_it_returns_failure_for_failure_without_throwing()
+ {
+ var start = Prelude.Failure("error");
+ var result = start.TryMap(value => value.ToString());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_map_and_it_returns_failure_for_success_with_throwing()
+ {
+ var start = Prelude.Success(2);
+ var result = start.TryMap(_ => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_map_and_it_returns_failure_for_failure_with_throwing()
+ {
+ var start = Prelude.Failure("error");
+ var result = start.TryMap(_ => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_with_then_and_it_returns_success_for_success_and_mapping_returning_success()
+ {
+ var start = Prelude.Success(2);
+ var result = start.ThenTry(value => Prelude.Success(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_with_then_and_it_returns_failure_for_success_and_mapping_returning_failure()
+ {
+ var start = Prelude.Success(2);
+ var result = start.ThenTry(_ => Prelude.Failure("error"));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_with_then_and_it_returns_failure_for_failure_and_mapping_returning_failure()
+ {
+ var start = Prelude.Failure("error");
+ var result = start.ThenTry(x => Prelude.Success(x.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_with_then_and_it_returns_failure_for_success_and_mapping_throwing()
+ {
+ var start = Prelude.Success(2);
+ var result = start.ThenTry(_ => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_with_then_and_it_returns_failure_for_failure_and_mapping_throwing()
+ {
+ var start = Prelude.Failure("error");
+ var result = start.ThenTry(_ => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_map_async_and_it_returns_success_for_success()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.MapAsync(value => Task.FromResult(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_map_async_and_it_returns_failure_for_failure()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.MapAsync(value => Task.FromResult(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_async_with_then_and_it_returns_success_for_success_and_mapping_returning_success()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenAsync(value => Task.FromResult(Prelude.Success(value.ToString())));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_async_with_then_and_it_returns_failure_for_success_and_mapping_returning_failure()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenAsync(_ => Task.FromResult(Prelude.Failure("error")));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_returning_success()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenAsync(value => Task.FromResult(Prelude.Success(value.ToString())));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_returning_failure()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenAsync(_ => Task.FromResult(Prelude.Failure("error 2")));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_success_for_success_without_throwing()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.TryMapAsync(value => Task.FromResult(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_failure_without_throwing()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.TryMapAsync(value => Task.FromResult(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_success_with_throwing()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.TryMapAsync(Task (_) => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_success_with_await_throwing()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.TryMapAsync(async Task (_) =>
+ {
+ await Task.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_failure_with_throwing()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.TryMapAsync(Task (_) => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_failure_with_await_throwing()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.TryMapAsync(async Task (_) =>
+ {
+ await Task.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_success_for_success_and_mapping_returning_success()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenTryAsync(value => Task.FromResult(Prelude.Success(value.ToString())));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_success_and_mapping_returning_failure()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenTryAsync(_ => Task.FromResult(Prelude.Failure("error")));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_returning_failure()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenTryAsync(x => Task.FromResult(Prelude.Success(x.ToString())));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_success_and_mapping_throwing()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenTryAsync(Task> (_) => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_success_and_mapping_await_throwing()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenTryAsync(async Task> (_) =>
+ {
+ await Task.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_throwing()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenTryAsync(Task> (_) => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_await_throwing()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenTryAsync(async Task> (_) =>
+ {
+ await Task.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_map_async_and_it_returns_success_for_success_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.MapAsync(value => ValueTask.FromResult(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_map_async_and_it_returns_failure_for_failure_ValueTask()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.MapAsync(value => ValueTask.FromResult(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_async_with_then_and_it_returns_success_for_success_and_mapping_returning_success_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenAsync(value => ValueTask.FromResult(Prelude.Success(value.ToString())));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_async_with_then_and_it_returns_failure_for_success_and_mapping_returning_failure_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenAsync(_ => ValueTask.FromResult(Prelude.Failure("error")));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_returning_success_ValueTask()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenAsync(value => ValueTask.FromResult(Prelude.Success(value.ToString())));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_returning_failure_ValueTask()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenAsync(_ => ValueTask.FromResult(Prelude.Failure("error 2")));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_success_for_success_without_throwing_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.TryMapAsync(value => ValueTask.FromResult(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_failure_without_throwing_ValueTask()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.TryMapAsync(value => ValueTask.FromResult(value.ToString()));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_success_with_throwing_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.TryMapAsync(ValueTask (_) => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_success_with_await_throwing_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.TryMapAsync(async ValueTask (_) =>
+ {
+ await ValueTask.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_failure_with_throwing_ValueTask()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.TryMapAsync(ValueTask (_) => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_map_async_and_it_returns_failure_for_failure_with_await_throwing_ValueTask()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.TryMapAsync(async ValueTask (_) =>
+ {
+ await ValueTask.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_success_for_success_and_mapping_returning_success_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenTryAsync(value => ValueTask.FromResult(Prelude.Success(value.ToString())));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsTrue();
+ await Assert.That(result.Value).IsEqualTo("2");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_success_and_mapping_returning_failure_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenTryAsync(_ => ValueTask.FromResult(Prelude.Failure("error")));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_returning_failure_ValueTask()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenTryAsync(x => ValueTask.FromResult(Prelude.Success(x.ToString())));
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_success_and_mapping_throwing_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenTryAsync(ValueTask> (_) => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_success_and_mapping_await_throwing_ValueTask()
+ {
+ var start = Prelude.Success(2);
+ var result = await start.ThenTryAsync(async ValueTask> (_) =>
+ {
+ await ValueTask.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ var instance = await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(instance?.Exception).IsTypeOf();
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_throwing_ValueTask()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenTryAsync(ValueTask> (_) => throw new CustomTestException());
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_transform_result_async_with_then_and_it_returns_failure_for_failure_and_mapping_await_throwing_ValueTask()
+ {
+ var start = Prelude.Failure("error");
+ var result = await start.ThenTryAsync(async ValueTask> (_) =>
+ {
+ await ValueTask.CompletedTask;
+ throw new CustomTestException();
+ });
+
+ using var scope = Assert.Multiple();
+ await Assert.That(result.IsSuccess).IsFalse();
+ await Assert.That(result.Error).IsTypeOf();
+ await Assert.That(result.Error?.Message).IsEqualTo("error");
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/ResultUnboxTests.cs b/src/Request.Result.Tests/ResultUnboxTests.cs
new file mode 100644
index 0000000..6684041
--- /dev/null
+++ b/src/Request.Result.Tests/ResultUnboxTests.cs
@@ -0,0 +1,101 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class ResultUnboxTests
+{
+ [Test]
+ public async Task I_can_try_get_value_and_it_returns_true_and_sets_value_for_success_with_1_param()
+ {
+ var result = Prelude.Success(2);
+ var ok = result.TryGetValue(out int value);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(ok).IsTrue();
+ await Assert.That(value).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task I_can_try_get_value_and_it_returns_false_for_failure_with_1_param()
+ {
+ var result = Prelude.Failure("error");
+ var ok = result.TryGetValue(out int value);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(ok).IsFalse();
+ await Assert.That(value).IsEqualTo(default(int));
+ }
+
+ [Test]
+ public async Task I_can_try_get_value_and_it_returns_true_and_sets_value_for_success_with_2_param()
+ {
+ var result = Prelude.Success(2);
+ var ok = result.TryGetValue(out int value, out var error);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(ok).IsTrue();
+ await Assert.That(value).IsEqualTo(2);
+ await Assert.That(error).IsEqualTo(default(Error));
+ }
+
+ [Test]
+ public async Task I_can_try_get_value_and_it_returns_false_and_sets_error_for_failure_with_2_param()
+ {
+ var result = Prelude.Failure("error");
+ var ok = result.TryGetValue(out int value, out var error);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(ok).IsFalse();
+ await Assert.That(value).IsEqualTo(default(int));
+ await Assert.That(error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_get_error_and_it_returns_true_and_sets_error_for_failure_with_1_param()
+ {
+ var result = Prelude.Failure("error");
+ var ok = result.TryGetValue(out Error? error);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(ok).IsTrue();
+ await Assert.That(error?.Message).IsEqualTo("error");
+ }
+
+ [Test]
+ public async Task I_can_try_get_error_and_it_returns_false_for_success_with_1_param()
+ {
+ var result = Prelude.Success(2);
+ var ok = result.TryGetValue(out Error? error);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(ok).IsFalse();
+ await Assert.That(error).IsEqualTo(default(Error));
+ }
+
+ [Test]
+ public async Task I_can_try_get_error_and_it_returns_true_and_sets_error_for_failure_with_2_param()
+ {
+ var result = Prelude.Failure("error");
+ var ok = result.TryGetValue(out Error? error, out var value);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(ok).IsTrue();
+ await Assert.That(error?.Message).IsEqualTo("error");
+ await Assert.That(value).IsEqualTo(default(int));
+ }
+
+ [Test]
+ public async Task I_can_try_get_error_and_it_returns_false_and_sets_value_for_success_with_2_param()
+ {
+ var result = Prelude.Success(2);
+ var ok = result.TryGetValue(out Error? error, out var value);
+
+ using var scope = Assert.Multiple();
+ await Assert.That(ok).IsFalse();
+ await Assert.That(error).IsEqualTo(default(Error));
+ await Assert.That(value).IsEqualTo(2);
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/_fixtures/CustomTestError.cs b/src/Request.Result.Tests/_fixtures/CustomTestError.cs
new file mode 100644
index 0000000..d9ce0dc
--- /dev/null
+++ b/src/Request.Result.Tests/_fixtures/CustomTestError.cs
@@ -0,0 +1,13 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using Geekeey.Request.Result;
+
+namespace Request.Result.Tests;
+
+internal sealed class CustomTestError : Error
+{
+ internal const string DefaultMessage = "This is a custom error for test";
+
+ public override string Message => DefaultMessage;
+}
\ No newline at end of file
diff --git a/src/Request.Result.Tests/_fixtures/CustomTestException.cs b/src/Request.Result.Tests/_fixtures/CustomTestException.cs
new file mode 100644
index 0000000..4274981
--- /dev/null
+++ b/src/Request.Result.Tests/_fixtures/CustomTestException.cs
@@ -0,0 +1,8 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+namespace Request.Result.Tests;
+
+internal sealed class CustomTestException : Exception
+{
+}
\ No newline at end of file
diff --git a/src/Request.Result/Geekeey.Request.Result.csproj b/src/Request.Result/Geekeey.Request.Result.csproj
new file mode 100644
index 0000000..b39f727
--- /dev/null
+++ b/src/Request.Result/Geekeey.Request.Result.csproj
@@ -0,0 +1,30 @@
+
+
+
+ Library
+ net10.0
+ true
+
+
+
+ true
+
+
+
+
+
+
+
+ package-readme.md
+ package-icon.png
+ https://code.geekeey.de/geekeey/request/src/branch/main/src/request.result
+ EUPL-1.2
+
+
+
+
+
+
+
+
+
diff --git a/src/Request.Result/Prelude.cs b/src/Request.Result/Prelude.cs
new file mode 100644
index 0000000..cd23136
--- /dev/null
+++ b/src/Request.Result/Prelude.cs
@@ -0,0 +1,101 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using System.Diagnostics.Contracts;
+
+namespace Geekeey.Request.Result;
+
+///
+/// A class containing various utility methods, a 'prelude' to the rest of the library.
+///
+///
+/// This class is meant to be imported statically, e.g. using static Geekeey.Extensions.Result.Prelude;.
+/// Recommended to be imported globally via a global using statement.
+///
+public static class Prelude
+{
+ ///
+ /// Creates a result containing a success value.
+ ///
+ /// The type of the success value.
+ /// The success value to create the result from.
+ [Pure]
+ public static Result Success(T value)
+ {
+ return new Result(value);
+ }
+
+ ///
+ /// Creates a result containing a failure value.
+ ///
+ /// The type of success value in the result.
+ /// The failure value to create the result from.
+ [Pure]
+ public static Result Failure(Error error)
+ {
+ return new Result(error);
+ }
+
+ ///
+ /// Tries to execute a function and return the result. If the function throws an exception, the exception will be
+ /// returned wrapped in an .
+ ///
+ /// The type the function returns.
+ /// The function to try to execute.
+ /// A result containing the return value of the function or an containing the
+ /// exception thrown by the function.
+ [Pure]
+ public static Result Try(Func function)
+ {
+ try
+ {
+ return new Result(function());
+ }
+ catch (Exception exception)
+ {
+ return new Result(new ExceptionError(exception));
+ }
+ }
+
+ ///
+ /// Tries to execute an asynchronous function and return the result. If the function throws an exception, the
+ /// exception will be returned wrapped in an .
+ ///
+ /// The type the function returns.
+ /// The function to try to execute.
+ /// A result containing the return value of the function or an containing the
+ /// exception thrown by the function.
+ [Pure]
+ public static async ValueTask> TryAsync(Func> function)
+ {
+ try
+ {
+ return new Result(await function());
+ }
+ catch (Exception exception)
+ {
+ return new Result(new ExceptionError(exception));
+ }
+ }
+
+ ///
+ /// Tries to execute an asynchronous function and return the result. If the function throws an exception, the
+ /// exception will be returned wrapped in an .
+ ///
+ /// The type the function returns.
+ /// The function to try to execute.
+ /// A result containing the return value of the function or an containing the
+ /// exception thrown by the function.
+ [Pure]
+ public static async Task> TryAsync(Func> function)
+ {
+ try
+ {
+ return new Result(await function());
+ }
+ catch (Exception exception)
+ {
+ return new Result(new ExceptionError(exception));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result/Result.Conversion.cs b/src/Request.Result/Result.Conversion.cs
new file mode 100644
index 0000000..6c87e3d
--- /dev/null
+++ b/src/Request.Result/Result.Conversion.cs
@@ -0,0 +1,51 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using System.Diagnostics.Contracts;
+
+namespace Geekeey.Request.Result;
+
+public readonly partial struct Result
+{
+ ///
+ /// Implicitly constructs a result from a success value.
+ ///
+ /// The value to construct the result from.
+ [Pure]
+ public static implicit operator Result(T value)
+ {
+ return new Result(value);
+ }
+
+ ///
+ /// Implicitly constructs a result from a failure value.
+ ///
+ /// The error to construct the result from.
+ [Pure]
+ public static implicit operator Result(Error error)
+ {
+ return new Result(error);
+ }
+
+ ///
+ /// Unwraps the success value of the result. Throws an if the result is a failure.
+ ///
+ ///
+ /// This call is unsafe in the sense that it might intentionally throw an exception. Please only use this
+ /// call if the caller knows that this operation is safe, or that an exception is acceptable to be thrown.
+ ///
+ /// The success value of the result.
+ /// The result is not a success.
+ [Pure]
+ public T Unwrap()
+ {
+ return IsSuccess ? Value : throw new UnwrapException();
+ }
+
+ ///
+ [Pure]
+ public static explicit operator T(Result result)
+ {
+ return result.Unwrap();
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result/Result.Equality.cs b/src/Request.Result/Result.Equality.cs
new file mode 100644
index 0000000..babaf81
--- /dev/null
+++ b/src/Request.Result/Result.Equality.cs
@@ -0,0 +1,163 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Numerics;
+
+namespace Geekeey.Request.Result;
+
+public readonly partial struct Result : IEquatable>, IEquatable
+{
+ ///
+ /// Checks whether the result is equal to another result. Results are equal if both results are success values and
+ /// the success values are equal, or if both results are failures.
+ ///
+ /// The result to check for equality with the current result.
+ [Pure]
+ public bool Equals(Result other)
+ {
+ return Equals(this, other, EqualityComparer.Default);
+ }
+
+ ///
+ /// Checks whether the result is equal to another result. Results are equal if both results are success values and
+ /// the success values are equal, or if both results are failures.
+ ///
+ /// The result to check for equality with the current result.
+ /// The equality comparer to use for comparing values.
+ [Pure]
+ public bool Equals(Result other, IEqualityComparer comparer)
+ {
+ return Equals(this, other, comparer);
+ }
+
+ ///
+ /// Checks whether the result is a success value and the success value is equal to another value.
+ ///
+ /// The value to check for equality with the success value of the result.
+ [Pure]
+ public bool Equals(T? other)
+ {
+ return Equals(this, other, EqualityComparer.Default);
+ }
+
+ ///
+ /// Checks whether the result is a success value and the success value is equal to another value using a specified
+ /// equality comparer.
+ ///
+ /// The value to check for equality with the success value of the result.
+ /// The equality comparer to use for comparing values.
+ [Pure]
+ public bool Equals(T? other, IEqualityComparer comparer)
+ {
+ return Equals(this, other, comparer);
+ }
+
+ ///
+ [Pure]
+ public override bool Equals(object? obj)
+ {
+ return (obj is T x && Equals(x)) || (obj is Result r && Equals(r));
+ }
+
+ ///
+ [Pure]
+ public override int GetHashCode()
+ {
+ return GetHashCode(this, EqualityComparer.Default);
+ }
+
+ internal static bool Equals(Result a, Result b, IEqualityComparer comparer)
+ {
+ if (!a.IsSuccess || !b.IsSuccess)
+ {
+ return !a.IsSuccess && !b.IsSuccess;
+ }
+
+ if (a.Value is null || b.Value is null)
+ {
+ return a.Value is null && b.Value is null;
+ }
+
+ return comparer.Equals(a.Value, b.Value);
+ }
+
+ internal static bool Equals(Result a, T? b, IEqualityComparer comparer)
+ {
+ if (!a.IsSuccess)
+ {
+ return false;
+ }
+
+ if (a.Value is null || b is null)
+ {
+ return a.Value is null && b is null;
+ }
+
+ return comparer.Equals(a.Value, b);
+ }
+
+ internal static int GetHashCode(Result result, IEqualityComparer comparer)
+ {
+ if (result is { IsSuccess: true, Value: not null })
+ {
+ return comparer.GetHashCode(result.Value);
+ }
+
+ return 0;
+ }
+}
+
+public readonly partial struct Result : IEqualityOperators, Result, bool>, IEqualityOperators, T, bool>
+{
+ ///
+ /// Checks whether two results are equal. Results are equal if both results are success values and the success
+ /// values are equal, or if both results are failures.
+ ///
+ /// The first result to compare.
+ /// The second result to compare.
+ [Pure]
+ [ExcludeFromCodeCoverage]
+ public static bool operator ==(Result a, Result b)
+ {
+ return Equals(a, b, EqualityComparer.Default);
+ }
+
+ ///
+ /// Checks whether two results are not equal. Results are equal if both results are success values and the success
+ /// values are equal, or if both results are failures.
+ ///
+ /// The first result to compare.
+ /// The second result to compare.
+ [Pure]
+ [ExcludeFromCodeCoverage]
+ public static bool operator !=(Result a, Result b)
+ {
+ return !Equals(a, b, EqualityComparer.Default);
+ }
+
+ ///
+ /// Checks whether a result is a success value and the success value is equal to another value.
+ ///
+ /// The result to compare.
+ /// The value to check for equality with the success value in the result.
+ [Pure]
+ [ExcludeFromCodeCoverage]
+ public static bool operator ==(Result a, T? b)
+ {
+ return Equals(a, b, EqualityComparer.Default);
+ }
+
+ ///
+ /// Checks whether a result either does not have a value, or the value is not equal to another value.
+ ///
+ /// The result to compare.
+ /// The value to check for inequality with the success value in the result.
+ [Pure]
+ [ExcludeFromCodeCoverage]
+ public static bool operator !=(Result a, T? b)
+ {
+ return !Equals(a, b, EqualityComparer.Default);
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result/Result.Matching.cs b/src/Request.Result/Result.Matching.cs
new file mode 100644
index 0000000..9520111
--- /dev/null
+++ b/src/Request.Result/Result.Matching.cs
@@ -0,0 +1,104 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using System.Diagnostics.Contracts;
+
+namespace Geekeey.Request.Result;
+
+public readonly partial struct Result
+{
+ ///
+ /// Matches over the success value or failure value of the result and returns another value. Can be conceptualized
+ /// as an exhaustive switch expression matching all possible values of the type.
+ ///
+ /// The type to return from the match.
+ /// The function to apply to the success value of the result if the result is a success.
+ /// The function to apply to the failure value of the result if the result is a failure.
+ /// The result of applying either or on the success
+ /// value or failure value of the result.
+ [Pure]
+ public TResult Match(Func success, Func failure)
+ {
+ return IsSuccess ? success(Value!) : failure(Error!);
+ }
+
+ ///
+ /// Matches over the success value or failure value of the result and invokes an effectful action onto the success
+ /// value or failure value. Can be conceptualized as an exhaustive switch statement matching all possible
+ /// values of the type.
+ ///
+ /// The function to call with the success value of the result if the result is a success.
+ /// The function to call with the failure value of the result if the result is a failure.
+ public void Switch(Action success, Action failure)
+ {
+ if (IsSuccess)
+ {
+ success(Value!);
+ }
+ else
+ {
+ failure(Error!);
+ }
+ }
+}
+
+public readonly partial struct Result
+{
+ ///
+ /// Asynchronously matches over the success value or failure value of the result and returns another value. Can be
+ /// conceptualized as an exhaustive switch expression matching all possible values of the type.
+ ///
+ /// The type to return from the match.
+ /// The function to apply to the success value of the result if the result is a success.
+ /// The function to apply to the failure value of the result if the result is a failure.
+ /// A task completing with the result of applying either or
+ /// on the success value or failure value of the result.
+ [Pure]
+ public async Task MatchAsync(Func> success, Func> failure)
+ {
+ return IsSuccess ? await success(Value!) : await failure(Error!);
+ }
+
+ ///
+ /// Asynchronously matches over the success value or failure value of the result and invokes an effectful action
+ /// onto the success value or failure value. Can be conceptualized as an exhaustive switch statement matching
+ /// all possible values of
+ /// the type.
+ ///
+ /// The function to call with the success value of the result if the result is a success.
+ /// The function to call with the failure value of the result if the result is a failure.
+ public Task SwitchAsync(Func success, Func failure)
+ {
+ return IsSuccess ? success(Value!) : failure(Error!);
+ }
+}
+
+public readonly partial struct Result
+{
+ ///
+ /// Asynchronously matches over the success value or failure value of the result and returns another value. Can be
+ /// conceptualized as an exhaustive switch expression matching all possible values of the type.
+ ///
+ /// The type to return from the match.
+ /// The function to apply to the success value of the result if the result is a success.
+ /// The function to apply to the failure value of the result if the result is a failure.
+ /// A task completing with the result of applying either or
+ /// on the success value or failure value of the result.
+ [Pure]
+ public ValueTask MatchAsync(Func> success, Func> failure)
+ {
+ return IsSuccess ? success(Value!) : failure(Error!);
+ }
+
+ ///
+ /// Asynchronously matches over the success value or failure value of the result and invokes an effectful action
+ /// onto the success value or the failure value. Can be conceptualized as an exhaustive switch statement
+ /// matching all possible values of the type.
+ ///
+ /// The function to call with the success value of the result if the result is a success.
+ /// The function to call with the failure value of the result if the result is a failure.
+ public ValueTask SwitchAsync(Func success, Func failure)
+ {
+ return IsSuccess ? success(Value!) : failure(Error!);
+ }
+}
\ No newline at end of file
diff --git a/src/Request.Result/Result.Transform.cs b/src/Request.Result/Result.Transform.cs
new file mode 100644
index 0000000..f9d1378
--- /dev/null
+++ b/src/Request.Result/Result.Transform.cs
@@ -0,0 +1,366 @@
+// Copyright (c) The Geekeey Authors
+// SPDX-License-Identifier: EUPL-1.2
+
+using System.Diagnostics.Contracts;
+
+namespace Geekeey.Request.Result;
+
+public readonly partial struct Result
+{
+ ///
+ /// Maps the success value of the result using a mapping function, or does nothing if the result is a failure.
+ ///
+ /// The function used to map the success value.
+ /// The type of the new value.
+ /// A new result containing either the mapped success value or the failure value of the original
+ /// result.
+ [Pure]
+ public Result Map(Func func)
+ {
+ return IsSuccess ? new Result(func(Value!)) : new Result(Error!);
+ }
+
+ ///
+ /// Tries to map the success value of the result using a mapping function, or does nothing if the result is a
+ /// failure. If the mapping function throws an exception, the exception will be returned wrapped in an
+ /// .
+ ///
+ /// The function used to map the success value.
+ /// The type of the new value.
+ /// A new result containing either the mapped value, the exception thrown by
+ /// wrapped in an , or the failure value of the original result.
+ [Pure]
+ public Result TryMap(Func func)
+ {
+ try
+ {
+ return Map(func);
+ }
+ catch (Exception exception)
+ {
+ return new Result(new ExceptionError(exception));
+ }
+ }
+
+ ///
+ /// Maps the success value of the result to a new result using a mapping function, or does nothing if the result is
+ /// a failure.
+ ///
+ /// The function used to map the success value to a new result.
+ /// The type of the new value.
+ /// A result which is either the mapped result or a new result containing the failure value of the original
+ /// result.
+ [Pure]
+ public Result Then(Func> func)
+ {
+ return IsSuccess ? func(Value!) : new Result(Error!);
+ }
+
+ ///
+ /// Tries to map the success value of the result to a new result using a mapping function, or does nothing if the result
+ /// is a failure. If the mapping function throws an exception, the exception will be returned wrapped in an
+ /// .
+ ///
+ /// The function used to map the success value to a new result.
+ /// The type of the new value.
+ /// A result which is either the mapped result, the exception thrown by wrapped in
+ /// an , or a new result containing the failure value of the original result.
+ [Pure]
+ public Result ThenTry(Func> func)
+ {
+ try
+ {
+ return Then(func);
+ }
+ catch (Exception exception)
+ {
+ return new Result(new ExceptionError(exception));
+ }
+ }
+}
+
+public readonly partial struct Result
+{
+ ///
+ /// Maps the success value of the result using an asynchronous mapping function, or does nothing if the result is
+ /// a failure.
+ ///
+ /// The function used to map the success value.
+ /// The type of the new value.
+ /// A which either completes asynchronously by invoking the mapping function on
+ /// the success value of the result and constructing a new result containing the mapped value, or completes
+ /// synchronously by returning a new result containing the failure value of the original result.
+ [Pure]
+ public Task> MapAsync(Func> func)
+ {
+ if (!IsSuccess)
+ {
+ return Task.FromResult(new Result(Error!));
+ }
+
+ var task = func(Value!);
+ return CreateResult(task);
+
+ static async Task> CreateResult(Task task)
+ {
+ var value = await task;
+ return new Result(value);
+ }
+ }
+
+ ///