chore: initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.201" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
[*.cs]
|
||||
|
||||
dotnet_diagnostic.CA1000.severity = none
|
||||
dotnet_diagnostic.CA1031.severity = none
|
||||
dotnet_diagnostic.CA1510.severity = none
|
||||
dotnet_diagnostic.CA2225.severity = none
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace StevanFreeborn.Results
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents an error with a code, message, and optional metadata.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1716 // Identifiers should not match keywords
|
||||
public sealed record Error : IError
|
||||
#pragma warning restore CA1716 // Identifiers should not match keywords
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the error code.
|
||||
/// </summary>
|
||||
public string Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error message.
|
||||
/// </summary>
|
||||
public string Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets optional metadata associated with the error.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Error"/> class with the specified code, message, and optional metadata.
|
||||
/// </summary>
|
||||
/// <param name="code">The error code.</param>
|
||||
/// <param name="message">The error message.</param>
|
||||
/// <param name="metadata">Optional metadata associated with the error.</param>
|
||||
public Error(string code, string message, IReadOnlyDictionary<string, object>? metadata = null)
|
||||
{
|
||||
Code = code;
|
||||
Message = message;
|
||||
Metadata = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace StevanFreeborn.Results
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents an error with a code and message.
|
||||
/// </summary>
|
||||
public interface IError
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the error code.
|
||||
/// </summary>
|
||||
string Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error message.
|
||||
/// </summary>
|
||||
string Message { get; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
namespace StevanFreeborn.Results
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a result with a value and error type, used for operations that either succeed with a value or fail with an error.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <typeparam name="TError">The type of the error.</typeparam>
|
||||
public sealed class Result<T, TError> where TError : IError
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the result is successful.
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the result is a failure.
|
||||
/// </summary>
|
||||
public bool IsFailure => !IsSuccess;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the result.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when accessing Value on a failed result.</exception>
|
||||
public T Value => IsSuccess
|
||||
? _value
|
||||
: throw new InvalidOperationException($"Cannot access Value on a failed result. Error: [{Error.Code}] {Error.Message}");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error associated with the result.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when accessing Error on a successful result.</exception>
|
||||
public TError Error => _error ?? throw new InvalidOperationException("Cannot access Error on a successful result.");
|
||||
|
||||
private readonly T _value;
|
||||
private readonly TError? _error;
|
||||
|
||||
internal Result(bool isSuccess, T value, TError? error)
|
||||
{
|
||||
IsSuccess = isSuccess;
|
||||
_value = value;
|
||||
_error = error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful result with the specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>A successful <see cref="Result{T, TError}"/>.</returns>
|
||||
public static Result<T, TError> Ok(T value)
|
||||
{
|
||||
return new Result<T, TError>(true, value, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failed result with the specified error.
|
||||
/// </summary>
|
||||
/// <param name="error">The error.</param>
|
||||
/// <returns>A failed <see cref="Result{T, TError}"/>.</returns>
|
||||
public static Result<T, TError> Fail(TError error)
|
||||
{
|
||||
return new Result<T, TError>(false, default!, error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a value to a successful <see cref="Result{T, TError}"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to convert.</param>
|
||||
public static implicit operator Result<T, TError>(T value)
|
||||
{
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="IError"/> to a failed <see cref="Result{T, TError}"/>.
|
||||
/// </summary>
|
||||
/// <param name="error">The error to convert.</param>
|
||||
public static implicit operator Result<T, TError>(TError error)
|
||||
{
|
||||
return Fail(error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the value to a new type if the result is successful.
|
||||
/// </summary>
|
||||
/// <typeparam name="TNew">The new type.</typeparam>
|
||||
/// <param name="mapper">The function to map the value.</param>
|
||||
/// <returns>A new <see cref="Result{TNew, TError}"/> with the mapped value if successful, otherwise the current error.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when mapper is null.</exception>
|
||||
public Result<TNew, TError> Map<TNew>(Func<T, TNew> mapper)
|
||||
{
|
||||
if (mapper is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mapper));
|
||||
}
|
||||
|
||||
return IsSuccess ? Result<TNew, TError>.Ok(mapper(Value)) : Result<TNew, TError>.Fail(Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the error to a new error if the result is a failure.
|
||||
/// </summary>
|
||||
/// <param name="mapper">The function to map the error.</param>
|
||||
/// <returns>A new <see cref="Result{T, TNewError}"/> with the mapped error if failed, otherwise the current result.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when mapper is null.</exception>
|
||||
public Result<T, TNewError> MapError<TNewError>(Func<TError, TNewError> mapper) where TNewError : IError
|
||||
{
|
||||
if (mapper is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mapper));
|
||||
}
|
||||
|
||||
return IsFailure ? Result<T, TNewError>.Fail(mapper(Error)) : Result<T, TNewError>.Ok(Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds to a new result if the current result is successful.
|
||||
/// </summary>
|
||||
/// <typeparam name="TNew">The new result type.</typeparam>
|
||||
/// <param name="binder">The function to bind to on success.</param>
|
||||
/// <returns>The result of the binder function if successful, otherwise the current error.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when binder is null.</exception>
|
||||
public Result<TNew, TError> Bind<TNew>(Func<T, Result<TNew, TError>> binder)
|
||||
{
|
||||
if (binder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(binder));
|
||||
}
|
||||
|
||||
return IsSuccess ? binder(Value) : Result<TNew, TError>.Fail(Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches the result and returns a value based on success or failure.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the result.</typeparam>
|
||||
/// <param name="onSuccess">The function to execute on success.</param>
|
||||
/// <param name="onFailure">The function to execute on failure.</param>
|
||||
/// <returns>The result of the appropriate function.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when onSuccess or onFailure is null.</exception>
|
||||
public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<TError, TResult> onFailure)
|
||||
{
|
||||
if (onSuccess is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onSuccess));
|
||||
}
|
||||
|
||||
if (onFailure is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onFailure));
|
||||
}
|
||||
|
||||
return IsSuccess ? onSuccess(Value) : onFailure(Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches the result and executes the appropriate action.
|
||||
/// </summary>
|
||||
/// <param name="onSuccess">The action to execute on success.</param>
|
||||
/// <param name="onFailure">The action to execute on failure.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when onSuccess or onFailure is null.</exception>
|
||||
public void Match(Action<T> onSuccess, Action<TError> onFailure)
|
||||
{
|
||||
if (onSuccess is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onSuccess));
|
||||
}
|
||||
|
||||
if (onFailure is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onFailure));
|
||||
}
|
||||
|
||||
if (IsSuccess)
|
||||
{
|
||||
onSuccess(Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
onFailure(Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified action if the result is successful, and returns the current result.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to execute on success.</param>
|
||||
/// <returns>The current result.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when action is null.</exception>
|
||||
public Result<T, TError> Map(Action<T> action)
|
||||
{
|
||||
if (action is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
}
|
||||
|
||||
if (IsSuccess)
|
||||
{
|
||||
action(Value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified action if the result is successful, and returns the current result.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to execute on success.</param>
|
||||
/// <returns>The current result.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when action is null.</exception>
|
||||
public Result<T, TError> Map(Action action)
|
||||
{
|
||||
if (action is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
}
|
||||
|
||||
if (IsSuccess)
|
||||
{
|
||||
action();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified function and wraps the result in a <see cref="Result{T, TError}"/>.
|
||||
/// </summary>
|
||||
/// <param name="func">The function to execute.</param>
|
||||
/// <returns>A successful result with the return value if no exception is thrown; otherwise, a failed result.</returns>
|
||||
public static Result<T, TError> Try(Func<T> func)
|
||||
{
|
||||
return Try(func, ex => (TError)(IError)new Error("UnexpectedError", ex.Message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified function and wraps the result in a <see cref="Result{T, TError}"/> using the specified error handler.
|
||||
/// </summary>
|
||||
/// <param name="func">The function to execute.</param>
|
||||
/// <param name="errorHandler">The function to convert exceptions to errors.</param>
|
||||
/// <returns>A successful result with the return value if no exception is thrown; otherwise, a failed result.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when func or errorHandler is null.</exception>
|
||||
public static Result<T, TError> Try(Func<T> func, Func<Exception, TError> errorHandler)
|
||||
{
|
||||
if (func is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(func));
|
||||
}
|
||||
|
||||
if (errorHandler is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorHandler));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Ok(func());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Fail(errorHandler(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
namespace StevanFreeborn.Results
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides async extension methods for <see cref="Result{Unit, Error}"/> and <see cref="Result{T, Error}"/>.
|
||||
/// </summary>
|
||||
public static class ResultAsyncExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps the value to a new type asynchronously if the result is successful.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <typeparam name="TNew">The new type.</typeparam>
|
||||
/// <typeparam name="TError">The type of the error.</typeparam>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="mapper">The async function to map the value.</param>
|
||||
/// <returns>A task containing a new <see cref="Result{TNew, TError}"/> with the mapped value if successful, otherwise the current error.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when result or mapper is null.</exception>
|
||||
public static async Task<Result<TNew, TError>> MapAsync<T, TNew, TError>(this Result<T, TError> result, Func<T, Task<TNew>> mapper)
|
||||
where TError : IError
|
||||
{
|
||||
if (result is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (mapper is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mapper));
|
||||
}
|
||||
|
||||
return result.IsSuccess ? await mapper(result.Value).ConfigureAwait(false) : result.Error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the error to a new error asynchronously if the result is a failure.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <typeparam name="TError">The type of the error.</typeparam>
|
||||
/// <typeparam name="TNewError">The new error type.</typeparam>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="mapper">The async function to map the error.</param>
|
||||
/// <returns>A task containing a new <see cref="Result{T, TNewError}"/> with the mapped error if failed, otherwise the current result.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when result or mapper is null.</exception>
|
||||
public static async Task<Result<T, TNewError>> MapErrorAsync<T, TError, TNewError>(this Result<T, TError> result, Func<TError, Task<TNewError>> mapper)
|
||||
where TError : IError
|
||||
where TNewError : IError
|
||||
{
|
||||
if (result is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (mapper is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mapper));
|
||||
}
|
||||
|
||||
return result.IsFailure ? await mapper(result.Error).ConfigureAwait(false) : Result<T, TNewError>.Ok(result.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified async action if the result is successful, and returns the current result.
|
||||
/// </summary>
|
||||
/// <typeparam name="TError">The type of the error.</typeparam>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="onSuccess">The async action to execute on success.</param>
|
||||
/// <returns>A task containing the result.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when result or onSuccess is null.</exception>
|
||||
public static async Task<Result<Unit, TError>> MapAsync<TError>(this Result<Unit, TError> result, Func<Unit, Task> onSuccess)
|
||||
where TError : IError
|
||||
{
|
||||
if (result is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (onSuccess is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onSuccess));
|
||||
}
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
await onSuccess(result.Value).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds to a new async result if the current result is successful.
|
||||
/// </summary>
|
||||
/// <typeparam name="TError">The type of the error.</typeparam>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="onSuccess">The async function to execute and bind to on success.</param>
|
||||
/// <returns>A task containing the result of the binder function if successful, otherwise the current result.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when result or onSuccess is null.</exception>
|
||||
public static async Task<Result<Unit, TError>> BindAsync<TError>(this Result<Unit, TError> result, Func<Unit, Task<Result<Unit, TError>>> onSuccess)
|
||||
where TError : IError
|
||||
{
|
||||
if (result is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (onSuccess is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onSuccess));
|
||||
}
|
||||
|
||||
return result.IsSuccess ? await onSuccess(result.Value).ConfigureAwait(false) : result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches the result asynchronously and returns a value based on success or failure.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the result.</typeparam>
|
||||
/// <typeparam name="TError">The type of the error.</typeparam>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="onSuccess">The async function to execute on success.</param>
|
||||
/// <param name="onFailure">The async function to execute on failure.</param>
|
||||
/// <returns>A task containing the result of the appropriate function.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when result, onSuccess, or onFailure is null.</exception>
|
||||
public static async Task<TResult> MatchAsync<TResult, TError>(this Result<Unit, TError> result, Func<Unit, Task<TResult>> onSuccess, Func<TError, Task<TResult>> onFailure)
|
||||
where TError : IError
|
||||
{
|
||||
if (result is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (onSuccess is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onSuccess));
|
||||
}
|
||||
|
||||
if (onFailure is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onFailure));
|
||||
}
|
||||
|
||||
return result.IsSuccess
|
||||
? await onSuccess(result.Value).ConfigureAwait(false)
|
||||
: await onFailure(result.Error).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified async action and wraps the result in a <see cref="Result{Unit, Error}"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The async action to execute.</param>
|
||||
/// <returns>A task containing a successful result if no exception is thrown; otherwise, a failed result.</returns>
|
||||
public static Task<Result<Unit, Error>> TryAsync(Func<Task> action)
|
||||
{
|
||||
return TryAsync(action, ex => new Error("UnexpectedError", ex.Message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified async action and wraps the result in a <see cref="Result{Unit, Error}"/> using the specified error handler.
|
||||
/// </summary>
|
||||
/// <param name="action">The async action to execute.</param>
|
||||
/// <param name="errorHandler">The function to convert exceptions to errors.</param>
|
||||
/// <returns>A task containing a successful result if no exception is thrown; otherwise, a failed result.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when action or errorHandler is null.</exception>
|
||||
public static async Task<Result<Unit, Error>> TryAsync(Func<Task> action, Func<Exception, Error> errorHandler)
|
||||
{
|
||||
if (action is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
}
|
||||
|
||||
if (errorHandler is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorHandler));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await action().ConfigureAwait(false);
|
||||
return Result<Unit, Error>.Ok(default);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<Unit, Error>.Fail(errorHandler(ex));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds to a new async result if the current result is successful.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <typeparam name="TNew">The new result type.</typeparam>
|
||||
/// <typeparam name="TError">The type of the error.</typeparam>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="binder">The async function to bind to on success.</param>
|
||||
/// <returns>A task containing the result of the binder function if successful, otherwise the current error.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when result or binder is null.</exception>
|
||||
public static async Task<Result<TNew, TError>> BindAsync<T, TNew, TError>(this Result<T, TError> result, Func<T, Task<Result<TNew, TError>>> binder)
|
||||
where TError : IError
|
||||
{
|
||||
if (result is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (binder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(binder));
|
||||
}
|
||||
|
||||
return result.IsSuccess ? await binder(result.Value).ConfigureAwait(false) : Result<TNew, TError>.Fail(result.Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches the result asynchronously and returns a value based on success or failure.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <typeparam name="TResult">The type of the result.</typeparam>
|
||||
/// <typeparam name="TError">The type of the error.</typeparam>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="onSuccess">The async function to execute on success.</param>
|
||||
/// <param name="onFailure">The async function to execute on failure.</param>
|
||||
/// <returns>A task containing the result of the appropriate function.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when result, onSuccess, or onFailure is null.</exception>
|
||||
public static async Task<TResult> MatchAsync<T, TResult, TError>(this Result<T, TError> result, Func<T, Task<TResult>> onSuccess, Func<TError, Task<TResult>> onFailure)
|
||||
where TError : IError
|
||||
{
|
||||
if (result is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (onSuccess is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onSuccess));
|
||||
}
|
||||
|
||||
if (onFailure is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onFailure));
|
||||
}
|
||||
|
||||
return result.IsSuccess
|
||||
? await onSuccess(result.Value).ConfigureAwait(false)
|
||||
: await onFailure(result.Error).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified async function and wraps the result in a <see cref="Result{T, Error}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="func">The async function to execute.</param>
|
||||
/// <returns>A task containing a successful result with the return value if no exception is thrown; otherwise, a failed result.</returns>
|
||||
public static Task<Result<T, Error>> TryAsync<T>(Func<Task<T>> func)
|
||||
{
|
||||
return TryAsync(func, ex => new Error("UnexpectedError", ex.Message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified async function and wraps the result in a <see cref="Result{T, Error}"/> using the specified error handler.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="func">The async function to execute.</param>
|
||||
/// <param name="errorHandler">The function to convert exceptions to errors.</param>
|
||||
/// <returns>A task containing a successful result with the return value if no exception is thrown; otherwise, a failed result.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when func or errorHandler is null.</exception>
|
||||
public static async Task<Result<T, Error>> TryAsync<T>(Func<Task<T>> func, Func<Exception, Error> errorHandler)
|
||||
{
|
||||
if (func is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(func));
|
||||
}
|
||||
|
||||
if (errorHandler is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorHandler));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Result<T, Error>.Ok(await func().ConfigureAwait(false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<T, Error>.Fail(errorHandler(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.1;net10.0</TargetFrameworks>
|
||||
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
|
||||
<VerifyReferenceAotCompatibility>true</VerifyReferenceAotCompatibility>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>StevanFreeborn.Results</PackageId>
|
||||
<Version>0.0.0</Version>
|
||||
<Authors>StevanFreeborn</Authors>
|
||||
<Description>A minimalistic, AOT-compatible Result type library for railway-oriented programming and functional error handling.</Description>
|
||||
<PackageTags>result;railway;functional;error-handling;discriminated-union</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/StevanFreeborn/stevanfreeborn.results</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\README.md" Link="README.md" />
|
||||
<None Include="..\..\LICENSE.md" Link="LICENSE.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace StevanFreeborn.Results
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a void-like type for use with Result when no value is needed.
|
||||
/// </summary>
|
||||
public readonly struct Unit
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user