namespace StevanFreeborn.Results
{
///
/// Static factory methods for creating result instances.
///
public static class Result
{
///
/// Creates a successful result with the specified value.
///
/// The type of the value.
/// The type of the error.
/// The value.
/// A successful .
public static Result Ok(T value) where TError : IError
{
return new Result(true, value, default);
}
///
/// Creates a failed result with the specified error.
///
/// The type of the value.
/// The type of the error.
/// The error.
/// A failed .
public static Result Fail(TError error) where TError : IError
{
return new Result(false, default!, error);
}
///
/// Executes the specified function and wraps the result in a .
///
/// The type of the value.
/// The type of the error.
/// The function to execute.
/// A successful result with the return value if no exception is thrown; otherwise, a failed result.
public static Result Try(Func func) where TError : IError
{
return Try(func, ex => (TError)(IError)new Error("UnexpectedError", ex.Message));
}
///
/// Executes the specified function and wraps the result in a using the specified error handler.
///
/// The type of the value.
/// The type of the error.
/// The function to execute.
/// The function to convert exceptions to errors.
/// A successful result with the return value if no exception is thrown; otherwise, a failed result.
/// Thrown when func or errorHandler is null.
public static Result Try(Func func, Func errorHandler) where TError : IError
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(func);
#else
if (func is null)
{
throw new ArgumentNullException(nameof(func));
}
#endif
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(errorHandler);
#else
if (errorHandler is null)
{
throw new ArgumentNullException(nameof(errorHandler));
}
#endif
try
{
return Ok(func());
}
catch (Exception ex)
{
return Fail(errorHandler(ex));
}
}
}
///
/// Represents a result with a value and error type, used for operations that either succeed with a value or fail with an error.
///
/// The type of the value.
/// The type of the error.
public sealed class Result where TError : IError
{
///
/// Gets a value indicating whether the result is successful.
///
public bool IsSuccess { get; }
///
/// Gets a value indicating whether the result is a failure.
///
public bool IsFailure => !IsSuccess;
///
/// Gets the value of the result.
///
/// Thrown when accessing Value on a failed result.
public T Value => IsSuccess
? _value
: throw new InvalidOperationException($"Cannot access Value on a failed result. Error: [{Error.Code}] {Error.Message}");
///
/// Gets the error associated with the result.
///
/// Thrown when accessing Error on a successful result.
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;
}
///
/// Implicitly converts a value to a successful .
///
/// The value to convert.
public static implicit operator Result(T value)
{
return Result.Ok(value);
}
///
/// Implicitly converts an to a failed .
///
/// The error to convert.
public static implicit operator Result(TError error)
{
return Result.Fail(error);
}
///
/// Maps the value to a new type if the result is successful.
///
/// The new type.
/// The function to map the value.
/// A new with the mapped value if successful, otherwise the current error.
/// Thrown when mapper is null.
public Result Map(Func mapper)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(mapper);
#else
if (mapper is null)
{
throw new ArgumentNullException(nameof(mapper));
}
#endif
return IsSuccess ? Result.Ok(mapper(Value)) : Result.Fail(Error);
}
///
/// Maps the error to a new error if the result is a failure.
///
/// The function to map the error.
/// A new with the mapped error if failed, otherwise the current result.
/// Thrown when mapper is null.
public Result MapError(Func mapper) where TNewError : IError
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(mapper);
#else
if (mapper is null)
{
throw new ArgumentNullException(nameof(mapper));
}
#endif
return IsFailure ? Result.Fail(mapper(Error)) : Result.Ok(Value);
}
///
/// Binds to a new result if the current result is successful.
///
/// The new result type.
/// The function to bind to on success.
/// The result of the binder function if successful, otherwise the current error.
/// Thrown when binder is null.
public Result Bind(Func> binder)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(binder);
#else
if (binder is null)
{
throw new ArgumentNullException(nameof(binder));
}
#endif
return IsSuccess ? binder(Value) : Result.Fail(Error);
}
///
/// Matches the result and returns a value based on success or failure.
///
/// The type of the result.
/// The function to execute on success.
/// The function to execute on failure.
/// The result of the appropriate function.
/// Thrown when onSuccess or onFailure is null.
public TResult Match(Func onSuccess, Func onFailure)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(onSuccess);
#else
if (onSuccess is null)
{
throw new ArgumentNullException(nameof(onSuccess));
}
#endif
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(onFailure);
#else
if (onFailure is null)
{
throw new ArgumentNullException(nameof(onFailure));
}
#endif
return IsSuccess ? onSuccess(Value) : onFailure(Error);
}
///
/// Matches the result and executes the appropriate action.
///
/// The action to execute on success.
/// The action to execute on failure.
/// Thrown when onSuccess or onFailure is null.
public void Match(Action onSuccess, Action onFailure)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(onSuccess);
#else
if (onSuccess is null)
{
throw new ArgumentNullException(nameof(onSuccess));
}
#endif
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(onFailure);
#else
if (onFailure is null)
{
throw new ArgumentNullException(nameof(onFailure));
}
#endif
if (IsSuccess)
{
onSuccess(Value);
}
else
{
onFailure(Error);
}
}
///
/// Executes the specified action if the result is successful, and returns the current result.
///
/// The action to execute on success.
/// The current result.
/// Thrown when action is null.
public Result Map(Action action)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(action);
#else
if (action is null)
{
throw new ArgumentNullException(nameof(action));
}
#endif
if (IsSuccess)
{
action(Value);
}
return this;
}
///
/// Executes the specified action if the result is successful, and returns the current result.
///
/// The action to execute on success.
/// The current result.
/// Thrown when action is null.
public Result Map(Action action)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(action);
#else
if (action is null)
{
throw new ArgumentNullException(nameof(action));
}
#endif
if (IsSuccess)
{
action();
}
return this;
}
}
}