From f39b899514689a8c8ee3debbf30641e6d08061d0 Mon Sep 17 00:00:00 2001 From: JustFixMe Date: Thu, 7 Dec 2023 23:21:00 +0400 Subject: [PATCH] made all extensions source generated --- Railway.SourceGenerator/Constants.cs | 3 +- .../ResultAppendExecutor.cs | 431 ++++++++++ Railway.SourceGenerator/ResultBindExecutor.cs | 55 +- .../ResultCombineExecutor.cs | 32 +- .../ResultExtensionsExecutor.cs | 34 +- Railway.SourceGenerator/ResultMapExecutor.cs | 106 +-- .../ResultMatchExecutor.cs | 54 +- .../ResultMethodGenerator.cs | 1 + Railway.SourceGenerator/ResultTapExecutor.cs | 29 +- Railway/Railway.csproj | 1 + Railway/Result.cs | 33 +- Railway/ResultExtensions.cs | 795 +----------------- 12 files changed, 675 insertions(+), 899 deletions(-) create mode 100644 Railway.SourceGenerator/ResultAppendExecutor.cs diff --git a/Railway.SourceGenerator/Constants.cs b/Railway.SourceGenerator/Constants.cs index 04f6512..da20312 100644 --- a/Railway.SourceGenerator/Constants.cs +++ b/Railway.SourceGenerator/Constants.cs @@ -2,6 +2,5 @@ namespace Just.Railway.SourceGen; internal static class Constants { - public const int MaxResultTupleSize = 4; - + public const int MaxResultTupleSize = 5; } diff --git a/Railway.SourceGenerator/ResultAppendExecutor.cs b/Railway.SourceGenerator/ResultAppendExecutor.cs new file mode 100644 index 0000000..69aa3c9 --- /dev/null +++ b/Railway.SourceGenerator/ResultAppendExecutor.cs @@ -0,0 +1,431 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Just.Railway.SourceGen; + +internal sealed class ResultAppendExecutor : ResultExtensionsExecutor +{ + protected override string ExtensionType => "Append"; + + protected override void GenerateHelperMethods(StringBuilder sb) + { + sb.AppendLine(""" + private static IEnumerable GetBottom(ResultState r1, ResultState r2, string firstArg = "result", string secondArg = "next") + { + if (r1 == ResultState.Bottom) + yield return firstArg; + if (r2 == ResultState.Bottom) + yield return secondArg; + } + """); + } + + protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount) + { + var templateArgNames = Enumerable.Range(1, argCount) + .Select(i => $"T{i}") + .ToImmutableArray(); + + string resultTypeDef = GenerateResultTypeDef(templateArgNames); + string resultValueExpansion = GenerateResultValueExpansion(templateArgNames); + string methodTemplateDecl = GenerateTemplateDecl(templateArgNames); + + sb.AppendLine($"#region {resultTypeDef}"); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static {{resultTypeDef}} Append{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Result next) + { + Error? error = null; + if ((result.State & next.State) == ResultState.Bottom) + { + throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); + } + + if (result.IsFailure) + { + error += result.Error; + } + if (next.IsFailure) + { + error += next.Error; + } + + return error is null + ? Result.Success({{resultValueExpansion}}) + : error; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static {{resultTypeDef}} Append{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func nextFunc) + { + if (result.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(result)); + } + else if (result.IsFailure) + { + return result.Error!; + } + + var next = nextFunc(); + if (next.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(nextFunc)); + } + else if (next.IsFailure) + { + return next.Error!; + } + + return Result.Success({{resultValueExpansion}}); + } + """); + + GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion); + GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion); + + if (argCount < Constants.MaxResultTupleSize) + { + GenerateExpandedMethods(sb, templateArgNames, resultTypeDef, resultValueExpansion); + GenerateExpandedAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion); + GenerateExpandedAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion); + } + + sb.AppendLine("#endregion"); + } + + private void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray templateArgNames, string resultTypeDef, string resultValueExpansion) + { + string methodTemplateDecl = GenerateTemplateDecl(templateArgNames); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static async {{taskType}}<{{resultTypeDef}}> Append{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func nextFunc) + { + var result = await resultTask.ConfigureAwait(false); + if (result.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(result)); + } + else if (result.IsFailure) + { + return result.Error!; + } + + var next = nextFunc(); + if (next.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(nextFunc)); + } + else if (next.IsFailure) + { + return next.Error!; + } + + return Result.Success({{resultValueExpansion}}); + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static async {{taskType}}<{{resultTypeDef}}> Append{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func<{{taskType}}> nextFunc) + { + if (result.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(result)); + } + else if (result.IsFailure) + { + return result.Error!; + } + + var next = await nextFunc().ConfigureAwait(false); + if (next.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(nextFunc)); + } + else if (next.IsFailure) + { + return next.Error!; + } + + return Result.Success({{resultValueExpansion}}); + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static async {{taskType}}<{{resultTypeDef}}> Append{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<{{taskType}}> nextFunc) + { + var result = await resultTask.ConfigureAwait(false); + if (result.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(resultTask)); + } + else if (result.IsFailure) + { + return result.Error!; + } + + var next = await nextFunc().ConfigureAwait(false); + if (next.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(nextFunc)); + } + else if (next.IsFailure) + { + return next.Error!; + } + + return Result.Success({{resultValueExpansion}}); + } + """); + } + + private void GenerateExpandedAsyncMethods(string taskType, StringBuilder sb, ImmutableArray templateArgNames, string resultTypeDef, string resultValueExpansion) + { + var expandedTemplateArgNames = templateArgNames.Add("TNext"); + string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames); + string methodExpandedTemplateDecl = GenerateTemplateDecl(expandedTemplateArgNames); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func nextFunc) + { + var result = await resultTask.ConfigureAwait(false); + return result.State switch + { + ResultState.Success => Result.Success({{JoinArguments(resultValueExpansion, "nextFunc()")}}), + ResultState.Error => result.Error!, + _ => throw new ResultNotInitializedException(nameof(result)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{resultTypeDef}} result, Func<{{taskType}}> nextFunc) + { + return result.State switch + { + ResultState.Success => Result.Success({{JoinArguments(resultValueExpansion, "await nextFunc().ConfigureAwait(false)")}}), + ResultState.Error => result.Error!, + _ => throw new ResultNotInitializedException(nameof(result)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<{{taskType}}> nextFunc) + { + var result = await resultTask.ConfigureAwait(false); + return result.State switch + { + ResultState.Success => Result.Success({{JoinArguments(resultValueExpansion, "await nextFunc().ConfigureAwait(false)")}}), + ResultState.Error => result.Error!, + _ => throw new ResultNotInitializedException(nameof(result)) + }; + } + """); + + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func> nextFunc) + { + var result = await resultTask.ConfigureAwait(false); + if (result.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(resultTask)); + } + else if (result.IsFailure) + { + return result.Error!; + } + + var next = nextFunc(); + if (next.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(nextFunc)); + } + else if (next.IsFailure) + { + return next.Error!; + } + + return Result.Success({{JoinArguments(resultValueExpansion, "next.Value")}}); + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{resultTypeDef}} result, Func<{{taskType}}>> nextFunc) + { + if (result.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(result)); + } + else if (result.IsFailure) + { + return result.Error!; + } + + var next = await nextFunc().ConfigureAwait(false); + if (next.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(nextFunc)); + } + else if (next.IsFailure) + { + return next.Error!; + } + + return Result.Success({{JoinArguments(resultValueExpansion, "next.Value")}}); + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<{{taskType}}>> nextFunc) + { + var result = await resultTask.ConfigureAwait(false); + if (result.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(resultTask)); + } + else if (result.IsFailure) + { + return result.Error!; + } + + var next = await nextFunc().ConfigureAwait(false); + if (next.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(nextFunc)); + } + else if (next.IsFailure) + { + return next.Error!; + } + + return Result.Success({{JoinArguments(resultValueExpansion, "next.Value")}}); + } + """); + } + + private static void GenerateExpandedMethods(StringBuilder sb, ImmutableArray templateArgNames, string resultTypeDef, string resultValueExpansion) + { + var expandedTemplateArgNames = templateArgNames.Add("TNext"); + string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames); + string methodExpandedTemplateDecl = GenerateTemplateDecl(expandedTemplateArgNames); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static {{resultExpandedTypeDef}} Append{{methodExpandedTemplateDecl}}(this in {{resultTypeDef}} result, TNext next) + { + return result.State switch + { + ResultState.Success => Result.Success({{JoinArguments(resultValueExpansion, "next")}}), + ResultState.Error => result.Error!, + _ => throw new ResultNotInitializedException(nameof(result)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static {{resultExpandedTypeDef}} Append{{methodExpandedTemplateDecl}}(this in {{resultTypeDef}} result, Func nextFunc) + { + return result.State switch + { + ResultState.Success => Result.Success({{JoinArguments(resultValueExpansion, "nextFunc()")}}), + ResultState.Error => result.Error!, + _ => throw new ResultNotInitializedException(nameof(result)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static {{resultExpandedTypeDef}} Append{{methodExpandedTemplateDecl}}(this in {{resultTypeDef}} result, Result next) + { + Error? error = null; + if ((result.State & next.State) == ResultState.Bottom) + { + throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); + } + + if (result.IsFailure) + { + error += result.Error; + } + if (next.IsFailure) + { + error += next.Error; + } + + return error is null + ? Result.Success({{JoinArguments(resultValueExpansion, "next.Value")}}) + : error; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")] + public static {{resultExpandedTypeDef}} Append{{methodExpandedTemplateDecl}}(this in {{resultTypeDef}} result, Func> nextFunc) + { + if (result.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(result)); + } + else if (result.IsFailure) + { + return result.Error!; + } + + var next = nextFunc(); + if (next.State == ResultState.Bottom) + { + throw new ResultNotInitializedException(nameof(nextFunc)); + } + else if (next.IsFailure) + { + return next.Error!; + } + + return Result.Success({{JoinArguments(resultValueExpansion, "next.Value")}}); + } + """); + } + + internal static string JoinArguments(string arg1, string arg2) => (arg1, arg2) switch + { + ("", "") => "", + (string arg, "") => arg, + ("", string arg) => arg, + _ => $"{arg1}, {arg2}" + }; +} diff --git a/Railway.SourceGenerator/ResultBindExecutor.cs b/Railway.SourceGenerator/ResultBindExecutor.cs index 81bdf4d..0f0e7d2 100644 --- a/Railway.SourceGenerator/ResultBindExecutor.cs +++ b/Railway.SourceGenerator/ResultBindExecutor.cs @@ -14,17 +14,18 @@ internal sealed class ResultBindExecutor : ResultExtensionsExecutor var templateArgNames = Enumerable.Range(1, argCount) .Select(i => $"T{i}") .ToImmutableArray(); - string separatedTemplateArgs = string.Join(", ", templateArgNames); - sb.AppendLine($"#region <{separatedTemplateArgs}>"); - - string resultValueType = templateArgNames.Length == 1 ? separatedTemplateArgs : $"({separatedTemplateArgs})"; + string resultTypeDef = GenerateResultTypeDef(templateArgNames); string resultValueExpansion = GenerateResultValueExpansion(templateArgNames); + string methodTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("R")); + string bindTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("Result")); + + sb.AppendLine($"#region {resultTypeDef}"); sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultBindExecutor)}}", "1.0.0.0")] - public static Result Bind<{{separatedTemplateArgs}}, R>(this in Result<{{resultValueType}}> result, Func<{{separatedTemplateArgs}}, Result> binding) + public static Result Bind{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{bindTemplateDecl}} binding) { return result.State switch { @@ -35,24 +36,22 @@ internal sealed class ResultBindExecutor : ResultExtensionsExecutor } """); - sb.AppendLine($$""" - [PureAttribute] - [GeneratedCodeAttribute("{{nameof(ResultBindExecutor)}}", "1.0.0.0")] - public static Task> Bind<{{separatedTemplateArgs}}, R>(this in Result<{{resultValueType}}> result, Func<{{separatedTemplateArgs}}, Task>> binding) - { - return result.State switch - { - ResultState.Success => binding({{resultValueExpansion}}), - ResultState.Error => Task.FromResult>(result.Error!), - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - """); + GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion); + GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion); + + sb.AppendLine("#endregion"); + } + + private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray templateArgNames, string resultTypeDef, string resultValueExpansion) + { + string methodTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("R")); + string bindTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("Result")); + string asyncActionTemplateDecl = GenerateTemplateDecl(templateArgNames.Add($"{taskType}>")); sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultBindExecutor)}}", "1.0.0.0")] - public static async Task> Bind<{{separatedTemplateArgs}}, R>(this Task> resultTask, Func<{{separatedTemplateArgs}}, Result> binding) + public static async {{taskType}}> Bind{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{bindTemplateDecl}} binding) { var result = await resultTask.ConfigureAwait(false); return result.State switch @@ -67,7 +66,21 @@ internal sealed class ResultBindExecutor : ResultExtensionsExecutor sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultBindExecutor)}}", "1.0.0.0")] - public static async Task> Bind<{{separatedTemplateArgs}}, R>(this Task> resultTask, Func<{{separatedTemplateArgs}}, Task>> binding) + public static {{taskType}}> Bind{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} binding) + { + return result.State switch + { + ResultState.Success => binding({{resultValueExpansion}}), + ResultState.Error => {{taskType}}.FromResult>(result.Error!), + _ => throw new ResultNotInitializedException(nameof(result)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultBindExecutor)}}", "1.0.0.0")] + public static async {{taskType}}> Bind{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} binding) { var result = await resultTask.ConfigureAwait(false); return result.State switch @@ -78,7 +91,5 @@ internal sealed class ResultBindExecutor : ResultExtensionsExecutor }; } """); - - sb.AppendLine("#endregion"); } } diff --git a/Railway.SourceGenerator/ResultCombineExecutor.cs b/Railway.SourceGenerator/ResultCombineExecutor.cs index 9f36dc8..2b53d20 100644 --- a/Railway.SourceGenerator/ResultCombineExecutor.cs +++ b/Railway.SourceGenerator/ResultCombineExecutor.cs @@ -49,10 +49,19 @@ internal sealed class ResultCombineExecutor : IGeneratorExecutor GenerateGetBottomMethod(sb, argCount); - var argsResultTupleSizes = new List>(); - Span templateCounts = stackalloc int[argCount]; + var permutations = 1 << argCount; + var argsResultTupleSizes = new ImmutableArray[permutations]; - Permute(templateCounts, argsResultTupleSizes); + Span templateCounts = stackalloc int[argCount]; + for (int i = 0; i < permutations; i++) + { + templateCounts.Fill(0); + for (int j = 0; j < argCount; j++) + { + templateCounts[j] = (i & (1 << j)) > 0 ? 1 : 0; + } + argsResultTupleSizes[i] = templateCounts.ToImmutableArray(); + } foreach (var argResultTupleSizes in argsResultTupleSizes) { @@ -61,23 +70,6 @@ internal sealed class ResultCombineExecutor : IGeneratorExecutor sb.AppendLine("#endregion"); - static void Permute(Span templateCounts, ICollection> argsResultTupleSizes, int lvl = 0) - { - int sum = 0; - for (int i = 0; i < lvl; i++) - { - sum += templateCounts[i]; - } - for (templateCounts[lvl] = 0; templateCounts[lvl] <= Constants.MaxResultTupleSize - sum; templateCounts[lvl]++) - { - if (lvl == templateCounts.Length - 1) - { - argsResultTupleSizes.Add(templateCounts.ToImmutableArray()); - continue; - } - Permute(templateCounts, argsResultTupleSizes, lvl + 1); - } - } } private static void GenerateGetBottomMethod(StringBuilder sb, int argCount) diff --git a/Railway.SourceGenerator/ResultExtensionsExecutor.cs b/Railway.SourceGenerator/ResultExtensionsExecutor.cs index 7821f2d..b8df66e 100644 --- a/Railway.SourceGenerator/ResultExtensionsExecutor.cs +++ b/Railway.SourceGenerator/ResultExtensionsExecutor.cs @@ -31,7 +31,8 @@ internal abstract class ResultExtensionsExecutor : IGeneratorExecutor { var sb = new StringBuilder(); - for (int i = 1; i <= Constants.MaxResultTupleSize; i++) + GenerateHelperMethods(sb); + for (int i = 0; i <= Constants.MaxResultTupleSize; i++) { GenerateMethodsForArgCount(sb, argCount: i); } @@ -39,11 +40,32 @@ internal abstract class ResultExtensionsExecutor : IGeneratorExecutor return sb.ToString(); } - protected string GenerateResultValueExpansion(ImmutableArray templateArgNames) + protected static string GenerateTemplateDecl(ImmutableArray templateArgNames) => templateArgNames.Length > 0 + ? $"<{string.Join(", ", templateArgNames)}>" + : string.Empty; + + protected static string GenerateResultTypeDef(ImmutableArray templateArgNames) => templateArgNames.Length switch + { + 0 => "Result", + 1 => $"Result<{string.Join(", ", templateArgNames)}>", + _ => $"Result<({string.Join(", ", templateArgNames)})>", + }; + + protected static string GenerateResultValueExpansion(ImmutableArray templateArgNames) { string resultExpansion; - if (templateArgNames.Length > 1) + + switch (templateArgNames.Length) { + case 0: + resultExpansion = string.Empty; + break; + + case 1: + resultExpansion = "result.Value"; + break; + + default: var resultExpansionBuilder = new StringBuilder(); for (int i = 1; i <= templateArgNames.Length; i++) { @@ -51,10 +73,7 @@ internal abstract class ResultExtensionsExecutor : IGeneratorExecutor } resultExpansionBuilder.Remove(resultExpansionBuilder.Length - 2, 2); resultExpansion = resultExpansionBuilder.ToString(); - } - else - { - resultExpansion = "result.Value"; + break; } return resultExpansion; @@ -62,4 +81,5 @@ internal abstract class ResultExtensionsExecutor : IGeneratorExecutor protected abstract string ExtensionType { get; } protected abstract void GenerateMethodsForArgCount(StringBuilder sb, int argCount); + protected virtual void GenerateHelperMethods(StringBuilder sb) {} } diff --git a/Railway.SourceGenerator/ResultMapExecutor.cs b/Railway.SourceGenerator/ResultMapExecutor.cs index 454c5d9..65d62ea 100644 --- a/Railway.SourceGenerator/ResultMapExecutor.cs +++ b/Railway.SourceGenerator/ResultMapExecutor.cs @@ -13,17 +13,17 @@ internal sealed class ResultMapExecutor : ResultExtensionsExecutor var templateArgNames = Enumerable.Range(1, argCount) .Select(i => $"T{i}") .ToImmutableArray(); - string separatedTemplateArgs = string.Join(", ", templateArgNames); - sb.AppendLine($"#region <{separatedTemplateArgs}>"); - - string resultValueType = templateArgNames.Length == 1 ? separatedTemplateArgs : $"({separatedTemplateArgs})"; + string resultTypeDef = GenerateResultTypeDef(templateArgNames); string resultValueExpansion = GenerateResultValueExpansion(templateArgNames); + string methodTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("R")); + + sb.AppendLine($"#region {resultTypeDef}"); sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")] - public static Result Map<{{separatedTemplateArgs}}, R>(this in Result<{{resultValueType}}> result, Func<{{separatedTemplateArgs}}, R> mapping) + public static Result Map{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{methodTemplateDecl}} mapping) { return result.State switch { @@ -34,50 +34,60 @@ internal sealed class ResultMapExecutor : ResultExtensionsExecutor } """); - sb.AppendLine($$""" - [PureAttribute] - [GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")] - public static async Task> Map<{{separatedTemplateArgs}}, R>(this Result<{{resultValueType}}> result, Func<{{separatedTemplateArgs}}, Task> mapping) - { - return result.State switch - { - ResultState.Success => await mapping({{resultValueExpansion}}).ConfigureAwait(false), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - """); - - sb.AppendLine($$""" - [PureAttribute] - [GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")] - public static async Task> Map<{{separatedTemplateArgs}}, R>(this Task> resultTask, Func<{{separatedTemplateArgs}}, R> mapping) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => mapping({{resultValueExpansion}}), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - """); - - sb.AppendLine($$""" - [PureAttribute] - [GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")] - public static async Task> Map<{{separatedTemplateArgs}}, R>(this Task> resultTask, Func<{{separatedTemplateArgs}}, Task> mapping) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => await mapping({{resultValueExpansion}}).ConfigureAwait(false), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - """); + GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion); + GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion); sb.AppendLine("#endregion"); } + + private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray templateArgNames, string resultTypeDef, string resultValueExpansion) + { + var methodTemplateArgNames = templateArgNames.Add("R"); + string methodTemplateDecl = GenerateTemplateDecl(methodTemplateArgNames); + string asyncActionTemplateDecl = GenerateTemplateDecl(templateArgNames.Add($"{taskType}")); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")] + public static async {{taskType}}> Map{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{methodTemplateDecl}} mapping) + { + var result = await resultTask.ConfigureAwait(false); + return result.State switch + { + ResultState.Success => mapping({{resultValueExpansion}}), + ResultState.Error => result.Error!, + _ => throw new ResultNotInitializedException(nameof(resultTask)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")] + public static async {{taskType}}> Map{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} mapping) + { + return result.State switch + { + ResultState.Success => await mapping({{resultValueExpansion}}).ConfigureAwait(false), + ResultState.Error => result.Error!, + _ => throw new ResultNotInitializedException(nameof(result)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")] + public static async {{taskType}}> Map{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} mapping) + { + var result = await resultTask.ConfigureAwait(false); + return result.State switch + { + ResultState.Success => await mapping({{resultValueExpansion}}).ConfigureAwait(false), + ResultState.Error => result.Error!, + _ => throw new ResultNotInitializedException(nameof(resultTask)) + }; + } + """); + } } diff --git a/Railway.SourceGenerator/ResultMatchExecutor.cs b/Railway.SourceGenerator/ResultMatchExecutor.cs index 946e2cc..9376454 100644 --- a/Railway.SourceGenerator/ResultMatchExecutor.cs +++ b/Railway.SourceGenerator/ResultMatchExecutor.cs @@ -16,17 +16,17 @@ internal sealed class ResultMatchExecutor : ResultExtensionsExecutor var templateArgNames = Enumerable.Range(1, argCount) .Select(i => $"T{i}") .ToImmutableArray(); - string separatedTemplateArgs = string.Join(", ", templateArgNames); - sb.AppendLine($"#region <{separatedTemplateArgs}>"); - - string resultValueType = templateArgNames.Length == 1 ? separatedTemplateArgs : $"({separatedTemplateArgs})"; + string resultTypeDef = GenerateResultTypeDef(templateArgNames); string resultValueExpansion = GenerateResultValueExpansion(templateArgNames); + string methodTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("R")); + + sb.AppendLine($"#region {resultTypeDef}"); sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultMatchExecutor)}}", "1.0.0.0")] - public static R Match<{{separatedTemplateArgs}}, R>(this in Result<{{resultValueType}}> result, Func<{{separatedTemplateArgs}}, R> onSuccess, Func onFailure) + public static R Match{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{methodTemplateDecl}} onSuccess, Func onFailure) { return result.State switch { @@ -37,24 +37,22 @@ internal sealed class ResultMatchExecutor : ResultExtensionsExecutor } """); - sb.AppendLine($$""" - [PureAttribute] - [GeneratedCodeAttribute("{{nameof(ResultMatchExecutor)}}", "1.0.0.0")] - public static Task Match<{{separatedTemplateArgs}}, R>(this in Result<{{resultValueType}}> result, Func<{{separatedTemplateArgs}}, Task> onSuccess, Func> onFailure) - { - return result.State switch - { - ResultState.Success => onSuccess({{resultValueExpansion}}), - ResultState.Error => onFailure(result.Error!), - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - """); + GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion); + GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion); + + sb.AppendLine("#endregion"); + } + + private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray templateArgNames, string resultTypeDef, string resultValueExpansion) + { + var methodTemplateArgNames = templateArgNames.Add("R"); + string methodTemplateDecl = GenerateTemplateDecl(methodTemplateArgNames); + string asyncActionTemplateDecl = GenerateTemplateDecl(templateArgNames.Add($"{taskType}")); sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultMatchExecutor)}}", "1.0.0.0")] - public static async Task Match<{{separatedTemplateArgs}}, R>(this Task> resultTask, Func<{{separatedTemplateArgs}}, R> onSuccess, Func onFailure) + public static async {{taskType}} Match{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{methodTemplateDecl}} onSuccess, Func onFailure) { var result = await resultTask.ConfigureAwait(false); return result.State switch @@ -69,7 +67,21 @@ internal sealed class ResultMatchExecutor : ResultExtensionsExecutor sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultMatchExecutor)}}", "1.0.0.0")] - public static async Task Match<{{separatedTemplateArgs}}, R>(this Task> resultTask, Func<{{separatedTemplateArgs}}, Task> onSuccess, Func> onFailure) + public static {{taskType}} Match{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} onSuccess, Func> onFailure) + { + return result.State switch + { + ResultState.Success => onSuccess({{resultValueExpansion}}), + ResultState.Error => onFailure(result.Error!), + _ => throw new ResultNotInitializedException(nameof(result)) + }; + } + """); + + sb.AppendLine($$""" + [PureAttribute] + [GeneratedCodeAttribute("{{nameof(ResultMatchExecutor)}}", "1.0.0.0")] + public static async {{taskType}} Match{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} onSuccess, Func> onFailure) { var result = await resultTask.ConfigureAwait(false); var matchTask = result.State switch @@ -81,7 +93,5 @@ internal sealed class ResultMatchExecutor : ResultExtensionsExecutor return await matchTask.ConfigureAwait(false); } """); - - sb.AppendLine("#endregion"); } } diff --git a/Railway.SourceGenerator/ResultMethodGenerator.cs b/Railway.SourceGenerator/ResultMethodGenerator.cs index 913f1e4..8b62141 100644 --- a/Railway.SourceGenerator/ResultMethodGenerator.cs +++ b/Railway.SourceGenerator/ResultMethodGenerator.cs @@ -17,6 +17,7 @@ public class ResultMethodGenerator : IIncrementalGenerator new ResultMapExecutor(), new ResultBindExecutor(), new ResultTapExecutor(), + new ResultAppendExecutor(), }; public void Initialize(IncrementalGeneratorInitializationContext context) diff --git a/Railway.SourceGenerator/ResultTapExecutor.cs b/Railway.SourceGenerator/ResultTapExecutor.cs index 954dc2f..23cd335 100644 --- a/Railway.SourceGenerator/ResultTapExecutor.cs +++ b/Railway.SourceGenerator/ResultTapExecutor.cs @@ -14,17 +14,17 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor var templateArgNames = Enumerable.Range(1, argCount) .Select(i => $"T{i}") .ToImmutableArray(); - string separatedTemplateArgs = string.Join(", ", templateArgNames); - sb.AppendLine($"#region <{separatedTemplateArgs}>"); - - string resultValueType = templateArgNames.Length == 1 ? separatedTemplateArgs : $"({separatedTemplateArgs})"; + string methodTemplateDecl = GenerateTemplateDecl(templateArgNames); + string resultTypeDef = GenerateResultTypeDef(templateArgNames); string resultValueExpansion = GenerateResultValueExpansion(templateArgNames); + sb.AppendLine($"#region {resultTypeDef}"); + sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultTapExecutor)}}", "1.0.0.0")] - public static ref readonly Result<{{resultValueType}}> Tap<{{separatedTemplateArgs}}>(this in Result<{{resultValueType}}> result, Action<{{separatedTemplateArgs}}>? onSuccess = null, Action? onFailure = null) + public static ref readonly {{resultTypeDef}} Tap{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Action{{methodTemplateDecl}}? onSuccess = null, Action? onFailure = null) { switch (result.State) { @@ -41,10 +41,21 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor } """); + GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion); + GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion); + + sb.AppendLine("#endregion"); + } + + private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray templateArgNames, string resultTypeDef, string resultValueExpansion) + { + string methodTemplateDecl = GenerateTemplateDecl(templateArgNames); + string asyncActionTemplateDecl = GenerateTemplateDecl(templateArgNames.Add(taskType)); + sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultTapExecutor)}}", "1.0.0.0")] - public static async Task> Tap<{{separatedTemplateArgs}}>(this Task> resultTask, Action<{{separatedTemplateArgs}}>? onSuccess = null, Action? onFailure = null) + public static async {{taskType}}<{{resultTypeDef}}> Tap{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Action{{methodTemplateDecl}}? onSuccess = null, Action? onFailure = null) { var result = await resultTask.ConfigureAwait(false); switch (result.State) @@ -65,7 +76,7 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultTapExecutor)}}", "1.0.0.0")] - public static async Task> Tap<{{separatedTemplateArgs}}>(this Result<{{resultValueType}}> result, Func<{{separatedTemplateArgs}}, Task>? onSuccess = null, Func? onFailure = null) + public static async {{taskType}}<{{resultTypeDef}}> Tap{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}}? onSuccess = null, Func? onFailure = null) { switch (result.State) { @@ -87,7 +98,7 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor sb.AppendLine($$""" [PureAttribute] [GeneratedCodeAttribute("{{nameof(ResultTapExecutor)}}", "1.0.0.0")] - public static async Task> Tap<{{separatedTemplateArgs}}>(this Task> resultTask, Func<{{separatedTemplateArgs}}, Task>? onSuccess = null, Func? onFailure = null) + public static async {{taskType}}<{{resultTypeDef}}> Tap{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}}? onSuccess = null, Func? onFailure = null) { var result = await resultTask.ConfigureAwait(false); switch (result.State) @@ -106,7 +117,5 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor return result; } """); - - sb.AppendLine("#endregion"); } } diff --git a/Railway/Railway.csproj b/Railway/Railway.csproj index d30fa7d..6f45fc8 100644 --- a/Railway/Railway.csproj +++ b/Railway/Railway.csproj @@ -8,6 +8,7 @@ Just.Railway JustFixMe + Copyright (c) 2023 JustFixMe MIT https://gitea.jstdev.ru/just/Just.Railway/ diff --git a/Railway/Result.cs b/Railway/Result.cs index 83c94f0..7731858 100644 --- a/Railway/Result.cs +++ b/Railway/Result.cs @@ -17,13 +17,34 @@ public readonly partial struct Result : IEquatable State = error is null ? ResultState.Success : ResultState.Error; } - [Pure] public static Result Success() => new(null); - [Pure] public static Result Success(T value) => new(value); - [Pure] public static Result Failure(Error error) => new(error ?? throw new ArgumentNullException(nameof(error))); - [Pure] public static Result Failure(Error error) => new(error ?? throw new ArgumentNullException(nameof(error))); + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Result Success() => new(null); - [Pure] public static implicit operator Result(Error error) => new(error ?? throw new ArgumentNullException(nameof(error))); - [Pure] public static implicit operator Result(Result result) => result.State switch + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Result Success(T value) => new(value); + + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Result<(T1, T2)> Success(T1 value1, T2 value2) => new((value1, value2)); + + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Result<(T1, T2, T3)> Success(T1 value1, T2 value2, T3 value3) => new((value1, value2, value3)); + + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Result<(T1, T2, T3, T4)> Success(T1 value1, T2 value2, T3 value3, T4 value4) => new((value1, value2, value3, value4)); + + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Result<(T1, T2, T3, T4, T5)> Success(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) => new((value1, value2, value3, value4, value5)); + + + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Result Failure(Error error) => new(error ?? throw new ArgumentNullException(nameof(error))); + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Result Failure(Error error) => new(error ?? throw new ArgumentNullException(nameof(error))); + + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Result(Error error) => new(error ?? throw new ArgumentNullException(nameof(error))); + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Result(Result result) => result.State switch { ResultState.Success => new(new SuccessUnit()), ResultState.Error => new(result.Error!), diff --git a/Railway/ResultExtensions.cs b/Railway/ResultExtensions.cs index a7941da..58925e7 100644 --- a/Railway/ResultExtensions.cs +++ b/Railway/ResultExtensions.cs @@ -2,770 +2,41 @@ namespace Just.Railway; public static partial class ResultExtensions { - #region Match<> - - [Pure] - public static R Match(this in Result result, Func onSuccess, Func onFailure) - { - return result.State switch - { - ResultState.Success => onSuccess(), - ResultState.Error => onFailure(result.Error!), - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - - [Pure] - public static Task Match(this in Result result, Func> onSuccess, Func> onFailure) - { - return result.State switch - { - ResultState.Success => onSuccess(), - ResultState.Error => onFailure(result.Error!), - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - - [Pure] public static async Task Match(this Task resultTask, Func onSuccess, Func onFailure) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => onSuccess(), - ResultState.Error => onFailure(result.Error!), - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - [Pure] - public static async Task Match(this Task resultTask, Func> onSuccess, Func> onFailure) - { - var result = await resultTask.ConfigureAwait(false); - var matchTask = result.State switch - { - ResultState.Success => onSuccess(), - ResultState.Error => onFailure(result.Error!), - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - return await matchTask.ConfigureAwait(false); - } - - #endregion - - #region Map<> - - [Pure] - public static Result Map(this in Result result, Func mapping) - { - return result.State switch - { - ResultState.Success => mapping(), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - - [Pure] - public static async Task> Map(this Result result, Func> mapping) - { - return result.State switch - { - ResultState.Success => await mapping().ConfigureAwait(false), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - - [Pure] - public static async Task> Map(this Task resultTask, Func mapping) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => mapping(), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - - [Pure] - public static async Task> Map(this Task resultTask, Func> mapping) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => await mapping().ConfigureAwait(false), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - - #endregion - - #region Bind<> - - [Pure] - public static Result Bind(this in Result result, Func binding) - { - return result.State switch - { - ResultState.Success => binding(), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static Task Bind(this in Result result, Func> binding) - { - return result.State switch - { - ResultState.Success => binding(), - ResultState.Error => Task.FromResult(result.Error!), - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static async Task Bind(this Task resultTask, Func binding) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => binding(), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - [Pure] - public static async Task Bind(this Task resultTask, Func> binding) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => await binding().ConfigureAwait(false), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - - [Pure] - public static Result Bind(this in Result result, Func> binding) - { - return result.State switch - { - ResultState.Success => binding(), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static Task> Bind(this in Result result, Func>> binding) - { - return result.State switch - { - ResultState.Success => binding(), - ResultState.Error => Task.FromResult>(result.Error!), - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static async Task> Bind(this Task resultTask, Func> binding) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => binding(), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - [Pure] - public static async Task> Bind(this Task resultTask, Func>> binding) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => await binding().ConfigureAwait(false), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - - #endregion - - #region Append - - #region <> - - [Pure] public static Result Append(this in Result result, Result next) - { - Error? error = null; - - if ((result.State & next.State) == ResultState.Bottom) - { - throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); - - static IEnumerable GetBottom(ResultState r1, ResultState r2) - { - if (r1 == ResultState.Bottom) - yield return nameof(result); - if (r2 == ResultState.Bottom) - yield return nameof(next); - } - } - - if (result.IsFailure) - { - error += result.Error; - } - if (next.IsFailure) - { - error += next.Error; - } - return error is null - ? new(null) - : new(error); - } - - #endregion - - #region - - [Pure] public static Result Append(this in Result result, T value) - { - return result.State switch - { - ResultState.Success => value, - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] public static Result Append(this in Result result, Result next) - { - Error? error = null; - - if ((result.State & next.State) == ResultState.Bottom) - { - throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); - - static IEnumerable GetBottom(ResultState r1, ResultState r2) - { - if (r1 == ResultState.Bottom) - yield return nameof(result); - if (r2 == ResultState.Bottom) - yield return nameof(next); - } - } - - if (result.IsFailure) - { - error += result.Error; - } - if (next.IsFailure) - { - error += next.Error; - } - return error is null - ? new(next.Value) - : new(error); - } - [Pure] public static Result Append(this in Result result, Result next) - { - Error? error = null; - - if ((result.State & next.State) == ResultState.Bottom) - { - throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); - - static IEnumerable GetBottom(ResultState r1, ResultState r2) - { - if (r1 == ResultState.Bottom) - yield return nameof(result); - if (r2 == ResultState.Bottom) - yield return nameof(next); - } - } - - if (result.IsFailure) - { - error += result.Error; - } - if (next.IsFailure) - { - error += next.Error; - } - return error is null - ? new(result.Value) - : new(error); - } - [Pure] - public static Result Append(this in Result result, Func> next) - { - return result.State switch - { - ResultState.Success => next(), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - - [Pure] - public static Task> Append(this in Result result, Func>> next) - { - return result.State switch - { - ResultState.Success => next(), - ResultState.Error => Task.FromResult>(result.Error!), - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static async Task> Append(this Task resultTask, Func>> next) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => await next().ConfigureAwait(false), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - [Pure] - public static async Task> Append(this Task resultTask, Func> next) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => next(), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - - #endregion - - #region - - [Pure] public static Result<(T1, T2)> Append(this in Result result, T2 value) - { - return result.State switch - { - ResultState.Success => (result.Value, value), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] public static Result<(T1, T2)> Append(this in Result result, Result next) - { - Error? error = null; - - if ((result.State & next.State) == ResultState.Bottom) - { - throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); - - static IEnumerable GetBottom(ResultState r1, ResultState r2) - { - if (r1 == ResultState.Bottom) - yield return nameof(result); - if (r2 == ResultState.Bottom) - yield return nameof(next); - } - } - - if (result.IsFailure) - { - error += result.Error; - } - if (next.IsFailure) - { - error += next.Error; - } - return error is null - ? new((result.Value, next.Value)) - : new(error); - } - [Pure] public static Result<(T1, T2)> Append(this in Result<(T1, T2)> result, Result next) - { - Error? error = null; - - if ((result.State & next.State) == ResultState.Bottom) - { - throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); - - static IEnumerable GetBottom(ResultState r1, ResultState r2) - { - if (r1 == ResultState.Bottom) - yield return nameof(result); - if (r2 == ResultState.Bottom) - yield return nameof(next); - } - } - - if (result.IsFailure) - { - error += result.Error; - } - if (next.IsFailure) - { - error += next.Error; - } - return error is null - ? new(result.Value) - : new(error); - } - [Pure] - public static Result<(T1, T2)> Append(this in Result result, Func> next) - { - return result.State switch - { - ResultState.Success => result.Append(next()), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static async Task> Append(this Result result, Func>> next) - { - return result.State switch - { - ResultState.Success => result.Append(await next().ConfigureAwait(false)), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static async Task> Append(this Task> resultTask, Func>> next) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => result.Append(await next().ConfigureAwait(false)), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - [Pure] - public static async Task> Append(this Task> resultTask, Func> next) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => result.Append(next()), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - - #endregion - - #region - - [Pure] public static Result<(T1, T2, T3)> Append(this in Result<(T1, T2)> result, T3 value) - { - return result.State switch - { - ResultState.Success => (result.Value.Item1, result.Value.Item2, value), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] public static Result<(T1, T2, T3)> Append(this in Result<(T1, T2)> result, Result next) - { - Error? error = null; - - if ((result.State & next.State) == ResultState.Bottom) - { - throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); - - static IEnumerable GetBottom(ResultState r1, ResultState r2) - { - if (r1 == ResultState.Bottom) - yield return nameof(result); - if (r2 == ResultState.Bottom) - yield return nameof(next); - } - } - - if (result.IsFailure) - { - error += result.Error; - } - if (next.IsFailure) - { - error += next.Error; - } - return error is null - ? new((result.Value.Item1, result.Value.Item2, next.Value)) - : new(error); - } - [Pure] public static Result<(T1, T2, T3)> Append(this in Result<(T1, T2, T3)> result, Result next) - { - Error? error = null; - - if ((result.State & next.State) == ResultState.Bottom) - { - throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); - - static IEnumerable GetBottom(ResultState r1, ResultState r2) - { - if (r1 == ResultState.Bottom) - yield return nameof(result); - if (r2 == ResultState.Bottom) - yield return nameof(next); - } - } - - if (result.IsFailure) - { - error += result.Error; - } - if (next.IsFailure) - { - error += next.Error; - } - return error is null - ? new(result.Value) - : new(error); - } - [Pure] - public static Result<(T1, T2, T3)> Append(this in Result<(T1, T2)> result, Func> next) - { - return result.State switch - { - ResultState.Success => result.Append(next()), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static async Task> Append(this Result<(T1, T2)> result, Func>> next) - { - return result.State switch - { - ResultState.Success => result.Append(await next().ConfigureAwait(false)), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static async Task> Append(this Task> resultTask, Func>> next) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => result.Append(await next().ConfigureAwait(false)), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - [Pure] - public static async Task> Append(this Task> resultTask, Func> next) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => result.Append(next()), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - - #endregion - - #region - - [Pure] public static Result<(T1, T2, T3, T4)> Append(this in Result<(T1, T2, T3)> result, T4 value) - { - return result.State switch - { - ResultState.Success => (result.Value.Item1, result.Value.Item2, result.Value.Item3, value), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] public static Result<(T1, T2, T3, T4)> Append(this in Result<(T1, T2, T3)> result, Result next) - { - Error? error = null; - - if ((result.State & next.State) == ResultState.Bottom) - { - throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); - - static IEnumerable GetBottom(ResultState r1, ResultState r2) - { - if (r1 == ResultState.Bottom) - yield return nameof(result); - if (r2 == ResultState.Bottom) - yield return nameof(next); - } - } - - if (result.IsFailure) - { - error += result.Error; - } - if (next.IsFailure) - { - error += next.Error; - } - return error is null - ? new((result.Value.Item1, result.Value.Item2, result.Value.Item3, next.Value)) - : new(error); - } - [Pure] public static Result<(T1, T2, T3, T4)> Append(this in Result<(T1, T2, T3, T4)> result, Result next) - { - Error? error = null; - - if ((result.State & next.State) == ResultState.Bottom) - { - throw new ResultNotInitializedException(string.Join(';', GetBottom(result.State, next.State))); - - static IEnumerable GetBottom(ResultState r1, ResultState r2) - { - if (r1 == ResultState.Bottom) - yield return nameof(result); - if (r2 == ResultState.Bottom) - yield return nameof(next); - } - } - - if (result.IsFailure) - { - error += result.Error; - } - if (next.IsFailure) - { - error += next.Error; - } - return error is null - ? new(result.Value) - : new(error); - } - [Pure] - public static Result<(T1, T2, T3, T4)> Append(this in Result<(T1, T2, T3)> result, Func> next) - { - return result.State switch - { - ResultState.Success => result.Append(next()), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static async Task> Append(this Result<(T1, T2, T3)> result, Func>> next) - { - return result.State switch - { - ResultState.Success => result.Append(await next().ConfigureAwait(false)), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(result)) - }; - } - [Pure] - public static async Task> Append(this Task> resultTask, Func>> next) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => result.Append(await next().ConfigureAwait(false)), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - [Pure] - public static async Task> Append(this Task> resultTask, Func> next) - { - var result = await resultTask.ConfigureAwait(false); - return result.State switch - { - ResultState.Success => result.Append(next()), - ResultState.Error => result.Error!, - _ => throw new ResultNotInitializedException(nameof(resultTask)) - }; - } - - #endregion - - #endregion - - #region Tap<> - [Pure] - public static ref readonly Result Tap(this in Result result, Action? onSuccess = null, Action? onFailure = null) - { - switch (result.State) - { - case ResultState.Success: - onSuccess?.Invoke(); - break; - case ResultState.Error: - onFailure?.Invoke(result.Error!); - break; - - default: throw new ResultNotInitializedException(nameof(result)); - } - - return ref result; - } - [Pure] - public static async Task Tap(this Task resultTask, Action? onSuccess = null, Action? onFailure = null) - { - var result = await resultTask.ConfigureAwait(false); - switch (result.State) - { - case ResultState.Success: - onSuccess?.Invoke(); - break; - case ResultState.Error: - onFailure?.Invoke(result.Error!); - break; - - default: throw new ResultNotInitializedException(nameof(resultTask)); - } - - return result; - } - [Pure] - public static async Task Tap(this Result result, Func? onSuccess = null, Func? onFailure = null) - { - switch (result.State) - { - case ResultState.Success: - if (onSuccess is not null) - await onSuccess.Invoke().ConfigureAwait(false); - break; - case ResultState.Error: - if (onFailure is not null) - await onFailure.Invoke(result.Error!).ConfigureAwait(false); - break; - - default: throw new ResultNotInitializedException(nameof(result)); - } - - return result; - } - [Pure] - public static async Task Tap(this Task resultTask, Func? onSuccess = null, Func? onFailure = null) - { - var result = await resultTask.ConfigureAwait(false); - switch (result.State) - { - case ResultState.Success: - if (onSuccess is not null) - await onSuccess.Invoke().ConfigureAwait(false); - break; - case ResultState.Error: - if (onFailure is not null) - await onFailure.Invoke(result.Error!).ConfigureAwait(false); - break; - - default: throw new ResultNotInitializedException(nameof(resultTask)); - } - - return result; - } - -#endregion - #region Merge + public static Result Merge(this IEnumerable results) + { + List? errors = null; + bool hasErrors = false; + + foreach (var result in results.OrderBy(x => x.State)) + { + switch (result.State) + { + case ResultState.Error: + hasErrors = true; + errors ??= []; + errors.Add(result.Error!); + break; + + case ResultState.Success: + if (hasErrors) goto afterLoop; + break; + + default: throw new ResultNotInitializedException(nameof(results)); + } + } + afterLoop: + return hasErrors + ? new(new ManyErrors(errors!)) + : new(null); + } + public static async Task Merge(this IEnumerable> tasks) + { + var results = await Task.WhenAll(tasks).ConfigureAwait(false); + return results.Merge(); + } + public static Result> Merge(this IEnumerable> results) { List? values = null;