Files
Just.Railway/Railway/ResultExtensions.cs
T
just 04c682d6fd
.NET Test / .NET tests (push) Successful in 2m53s
fixes and adjustments
2026-07-16 19:00:05 +04:00

318 lines
11 KiB
C#

using System.Collections.Immutable;
namespace Just.Railway;
public static partial class ResultExtensions
{
#region MapError
public static Result MapError(this in Result result, Func<Error, Error> func) => result.State switch
{
ResultState.Error => func(result.Error!),
ResultState.Success => result,
_ => throw new ResultNotInitializedException(nameof(result)),
};
public static Result<T> MapError<T>(this in Result<T> result, Func<Error, Error> func) => result.State switch
{
ResultState.Error => func(result.Error!),
ResultState.Success => result,
_ => throw new ResultNotInitializedException(nameof(result)),
};
public static async Task<Result> MapError(this Task<Result> resultTask, Func<Error, Error> func)
{
var result = await resultTask.ConfigureAwait(false);
return result.State switch
{
ResultState.Error => func(result.Error!),
ResultState.Success => result,
_ => throw new ResultNotInitializedException(nameof(result)),
};
}
public static async Task<Result> MapError(this Result result, Func<Error, Task<Error>> func)
{
return result.State switch
{
ResultState.Error => await func(result.Error!).ConfigureAwait(false),
ResultState.Success => result,
_ => throw new ResultNotInitializedException(nameof(result)),
};
}
public static async Task<Result> MapError(this Task<Result> resultTask, Func<Error, Task<Error>> func)
{
var result = await resultTask.ConfigureAwait(false);
return result.State switch
{
ResultState.Error => await func(result.Error!),
ResultState.Success => result,
_ => throw new ResultNotInitializedException(nameof(result)),
};
}
public static async Task<Result<T>> MapError<T>(this Task<Result<T>> resultTask, Func<Error, Error> func)
{
var result = await resultTask.ConfigureAwait(false);
return result.State switch
{
ResultState.Error => func(result.Error!),
ResultState.Success => result,
_ => throw new ResultNotInitializedException(nameof(result)),
};
}
public static async Task<Result<T>> MapError<T>(this Result<T> result, Func<Error, Task<Error>> func)
{
return result.State switch
{
ResultState.Error => await func(result.Error!).ConfigureAwait(false),
ResultState.Success => result,
_ => throw new ResultNotInitializedException(nameof(result)),
};
}
public static async Task<Result<T>> MapError<T>(this Task<Result<T>> resultTask, Func<Error, Task<Error>> func)
{
var result = await resultTask.ConfigureAwait(false);
return result.State switch
{
ResultState.Error => await func(result.Error!),
ResultState.Success => result,
_ => throw new ResultNotInitializedException(nameof(result)),
};
}
#endregion
#region Finally
public static Result Finally(this in Result result, Action action)
{
switch (result.State)
{
case ResultState.Success:
case ResultState.Error:
action.Invoke();
break;
default: throw new ResultNotInitializedException(nameof(result));
}
return result;
}
public static async Task<Result> Finally(this Task<Result> resultTask, Action action)
{
var result = await resultTask;
switch (result.State)
{
case ResultState.Success:
case ResultState.Error:
action.Invoke();
break;
default: throw new ResultNotInitializedException(nameof(resultTask));
}
return result;
}
public static async Task<Result> Finally(this Result result, Func<Task> action)
{
switch (result.State)
{
case ResultState.Success:
case ResultState.Error:
await action.Invoke();
break;
default: throw new ResultNotInitializedException(nameof(result));
}
return result;
}
public static async Task<Result> Finally(this Task<Result> resultTask, Func<Task> action)
{
var result = await resultTask;
switch (result.State)
{
case ResultState.Success:
case ResultState.Error:
await action.Invoke();
break;
default: throw new ResultNotInitializedException(nameof(resultTask));
}
return result;
}
#endregion
#region ValueOr
public static T ValueOr<T>(this in Result<T> result, T fallback)
{
return result.State switch
{
ResultState.Success => result.Value,
ResultState.Error => fallback,
_ => throw new ResultNotInitializedException(nameof(result))
};
}
public static T ValueOr<T>(this in Result<T> result, Func<Error, T> fallback)
{
return result.State switch
{
ResultState.Success => result.Value,
ResultState.Error => fallback(result.Error!),
_ => throw new ResultNotInitializedException(nameof(result))
};
}
public static async Task<T> ValueOr<T>(this Task<Result<T>> resultTask, T fallback)
{
var result = await resultTask.ConfigureAwait(false);
return result.State switch
{
ResultState.Success => result.Value,
ResultState.Error => fallback,
_ => throw new ResultNotInitializedException(nameof(resultTask))
};
}
public static async Task<T> ValueOr<T>(this Result<T> result, Func<Error, Task<T>> fallback)
{
return result.State switch
{
ResultState.Success => result.Value,
ResultState.Error => await fallback(result.Error!).ConfigureAwait(false),
_ => throw new ResultNotInitializedException(nameof(result))
};
}
public static async Task<T> ValueOr<T>(this Task<Result<T>> resultTask, Func<Error, T> fallback)
{
var result = await resultTask.ConfigureAwait(false);
return result.State switch
{
ResultState.Success => result.Value,
ResultState.Error => fallback(result.Error!),
_ => throw new ResultNotInitializedException(nameof(resultTask))
};
}
public static async Task<T> ValueOr<T>(this Task<Result<T>> resultTask, Func<Error, Task<T>> fallback)
{
var result = await resultTask.ConfigureAwait(false);
return result.State switch
{
ResultState.Success => result.Value,
ResultState.Error => await fallback(result.Error!).ConfigureAwait(false),
_ => throw new ResultNotInitializedException(nameof(resultTask))
};
}
#endregion
#region Merge
public static Result Merge(this IEnumerable<Result> results)
{
ImmutableArray<Error>.Builder? errors = null;
foreach (var result in results)
{
switch (result.State)
{
case ResultState.Success:
continue;
case ResultState.Error:
errors ??= ImmutableArray.CreateBuilder<Error>();
ManyErrors.AppendSanitized(errors, result.Error!);
continue;
default: throw new ResultNotInitializedException(nameof(results));
}
}
return errors switch
{
null => new Result(null),
{ Count: 1 } => errors[0],
_ => new(new ManyErrors(errors.ToImmutable()))
};
}
public static async Task<Result> Merge(this IEnumerable<Task<Result>> tasks)
{
var taskList = tasks.ToArray();
var results = new Result[taskList.Length];
// Wait for all tasks to complete (suppress AggregateException — we'll check each individually)
try { await Task.WhenAll(taskList).ConfigureAwait(false); } catch { /* handled per-task below */ }
for (int i = 0; i < taskList.Length; i++)
{
var task = taskList[i];
results[i] = task.Status switch
{
TaskStatus.RanToCompletion => task.Result,
TaskStatus.Faulted => Result.Failure(task.Exception!),
TaskStatus.Canceled => Result.Failure(Error.New("task_canceled", "Task was canceled")),
_ => Result.Failure(Error.New("task_unexpected", $"Task in unexpected state: {task.Status}"))
};
}
return results.Merge();
}
public static Result<IEnumerable<T>> Merge<T>(this IEnumerable<Result<T>> results)
{
ImmutableArray<Error>.Builder? errors = null;
ImmutableArray<T>.Builder? values = null;
foreach (var result in results)
{
switch (result.State)
{
case ResultState.Success:
values ??= ImmutableArray.CreateBuilder<T>();
values.Add(result.Value);
continue;
case ResultState.Error:
errors ??= ImmutableArray.CreateBuilder<Error>();
ManyErrors.AppendSanitized(errors, result.Error!);
continue;
default: throw new ResultNotInitializedException(nameof(results));
}
}
return errors switch
{
null => new Result<IEnumerable<T>>(values?.ToImmutable() ?? Enumerable.Empty<T>()),
{ Count: 1 } => errors[0],
_ => new(new ManyErrors(errors.ToImmutable()))
};
}
public static async Task<Result<IEnumerable<T>>> Merge<T>(this IEnumerable<Task<Result<T>>> tasks)
{
var taskList = tasks.ToArray();
var results = new Result<T>[taskList.Length];
// Wait for all tasks to complete (suppress AggregateException — we'll check each individually)
try { await Task.WhenAll(taskList).ConfigureAwait(false); } catch { /* handled per-task below */ }
for (int i = 0; i < taskList.Length; i++)
{
var task = taskList[i];
results[i] = task.Status switch
{
TaskStatus.RanToCompletion => task.Result,
TaskStatus.Faulted => Result.Failure<T>(task.Exception!),
TaskStatus.Canceled => Result.Failure<T>(Error.New("task_canceled", "Task was canceled")),
_ => Result.Failure<T>(Error.New("task_unexpected", $"Task in unexpected state: {task.Status}"))
};
}
return results.Merge();
}
#endregion
}