using System.Collections.Immutable; namespace Just.Railway; public static partial class ResultExtensions { #region MapError public static Result MapError(this in Result result, Func func) => result.State switch { ResultState.Error => func(result.Error!), ResultState.Success => result, _ => throw new ResultNotInitializedException(nameof(result)), }; public static Result MapError(this in Result result, Func func) => result.State switch { ResultState.Error => func(result.Error!), ResultState.Success => result, _ => throw new ResultNotInitializedException(nameof(result)), }; public static async Task MapError(this Task resultTask, Func 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 MapError(this Result result, Func> 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 MapError(this Task resultTask, Func> 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> MapError(this Task> resultTask, Func 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> MapError(this Result result, Func> 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> MapError(this Task> resultTask, Func> 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 Finally(this Task 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 Finally(this Result result, Func 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 Finally(this Task resultTask, Func 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(this in Result result, T fallback) { return result.State switch { ResultState.Success => result.Value, ResultState.Error => fallback, _ => throw new ResultNotInitializedException(nameof(result)) }; } public static T ValueOr(this in Result result, Func fallback) { return result.State switch { ResultState.Success => result.Value, ResultState.Error => fallback(result.Error!), _ => throw new ResultNotInitializedException(nameof(result)) }; } public static async Task ValueOr(this Task> 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 ValueOr(this Result result, Func> 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 ValueOr(this Task> resultTask, Func 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 ValueOr(this Task> resultTask, Func> 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 results) { ImmutableArray.Builder? errors = null; foreach (var result in results) { switch (result.State) { case ResultState.Success: continue; case ResultState.Error: errors ??= ImmutableArray.CreateBuilder(); 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 Merge(this IEnumerable> 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> Merge(this IEnumerable> results) { ImmutableArray.Builder? errors = null; ImmutableArray.Builder? values = null; foreach (var result in results) { switch (result.State) { case ResultState.Success: values ??= ImmutableArray.CreateBuilder(); values.Add(result.Value); continue; case ResultState.Error: errors ??= ImmutableArray.CreateBuilder(); ManyErrors.AppendSanitized(errors, result.Error!); continue; default: throw new ResultNotInitializedException(nameof(results)); } } return errors switch { null => new Result>(values?.ToImmutable() ?? Enumerable.Empty()), { Count: 1 } => errors[0], _ => new(new ManyErrors(errors.ToImmutable())) }; } public static async Task>> Merge(this IEnumerable>> 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(); } #endregion }