+2
-2
@@ -2,11 +2,11 @@ using System.Collections.Generic;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors;
|
||||||
|
|
||||||
public sealed class EnsureExtensionsExecutor : IGeneratorExecutor
|
public sealed class EnsureExtensionsExecutor : IGeneratorExecutor
|
||||||
{
|
{
|
||||||
public void Execute(SourceProductionContext context, Compilation source)
|
public void Execute(IncrementalGeneratorPostInitializationContext context)
|
||||||
{
|
{
|
||||||
var methods = GenerateMethods();
|
var methods = GenerateMethods();
|
||||||
var code = $$"""
|
var code = $$"""
|
||||||
+6
-6
@@ -3,11 +3,13 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
|
||||||
internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
internal class ResultAppendExecutor : ResultExtensionsExecutor
|
||||||
{
|
{
|
||||||
protected override string ExtensionType => "Append";
|
protected override string ExtensionType => "Append";
|
||||||
|
protected override string NamespaceName => "Just.Railway";
|
||||||
|
protected override string PartialClassName => "ResultExtensions";
|
||||||
|
|
||||||
protected override void GenerateHelperMethods(StringBuilder sb)
|
protected override void GenerateHelperMethods(StringBuilder sb)
|
||||||
{
|
{
|
||||||
@@ -89,19 +91,17 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
|||||||
""");
|
""");
|
||||||
|
|
||||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
|
||||||
|
|
||||||
if (argCount < Constants.MaxResultTupleSize)
|
if (argCount < Constants.MaxResultTupleSize)
|
||||||
{
|
{
|
||||||
GenerateExpandedMethods(sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
GenerateExpandedMethods(sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
GenerateExpandedAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
GenerateExpandedAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
GenerateExpandedAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine("#endregion");
|
sb.AppendLine("#endregion");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
protected static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||||
{
|
{
|
||||||
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames);
|
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames);
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
|||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateExpandedAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
protected static void GenerateExpandedAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||||
{
|
{
|
||||||
var expandedTemplateArgNames = templateArgNames.Add("TNext");
|
var expandedTemplateArgNames = templateArgNames.Add("TNext");
|
||||||
string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames);
|
string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames);
|
||||||
+5
-4
@@ -3,11 +3,13 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
|
||||||
internal sealed class ResultBindExecutor : ResultExtensionsExecutor
|
internal class ResultBindExecutor : ResultExtensionsExecutor
|
||||||
{
|
{
|
||||||
protected override string ExtensionType => "Bind";
|
protected override string ExtensionType => "Bind";
|
||||||
|
protected override string NamespaceName => "Just.Railway";
|
||||||
|
protected override string PartialClassName => "ResultExtensions";
|
||||||
|
|
||||||
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
||||||
{
|
{
|
||||||
@@ -37,12 +39,11 @@ internal sealed class ResultBindExecutor : ResultExtensionsExecutor
|
|||||||
""");
|
""");
|
||||||
|
|
||||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
|
||||||
|
|
||||||
sb.AppendLine("#endregion");
|
sb.AppendLine("#endregion");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
protected static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||||
{
|
{
|
||||||
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("R"));
|
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("R"));
|
||||||
string bindTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("Result<R>"));
|
string bindTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("Result<R>"));
|
||||||
+2
-2
@@ -5,11 +5,11 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
|
||||||
internal sealed class ResultCombineExecutor : IGeneratorExecutor
|
internal sealed class ResultCombineExecutor : IGeneratorExecutor
|
||||||
{
|
{
|
||||||
public void Execute(SourceProductionContext context, Compilation source)
|
public void Execute(IncrementalGeneratorPostInitializationContext context)
|
||||||
{
|
{
|
||||||
var methods = GenerateCombineMethods();
|
var methods = GenerateCombineMethods();
|
||||||
var code = $$"""
|
var code = $$"""
|
||||||
+5
-4
@@ -3,11 +3,13 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
|
||||||
internal sealed class ResultExtendExecutor : ResultExtensionsExecutor
|
internal class ResultExtendExecutor : ResultExtensionsExecutor
|
||||||
{
|
{
|
||||||
protected override string ExtensionType => "Extend";
|
protected override string ExtensionType => "Extend";
|
||||||
|
protected override string NamespaceName => "Just.Railway";
|
||||||
|
protected override string PartialClassName => "ResultExtensions";
|
||||||
|
|
||||||
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
||||||
{
|
{
|
||||||
@@ -58,12 +60,11 @@ internal sealed class ResultExtendExecutor : ResultExtensionsExecutor
|
|||||||
""");
|
""");
|
||||||
|
|
||||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
|
||||||
|
|
||||||
sb.AppendLine("#endregion");
|
sb.AppendLine("#endregion");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
protected static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||||
{
|
{
|
||||||
var expandedTemplateArgNames = templateArgNames.Add("R");
|
var expandedTemplateArgNames = templateArgNames.Add("R");
|
||||||
string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames);
|
string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames);
|
||||||
+7
-4
@@ -1,13 +1,17 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Just.Railway.SourceGen.Executors.ResultExtensions;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors;
|
||||||
|
|
||||||
internal sealed class ResultMapExecutor : ResultExtensionsExecutor
|
internal class ResultMapExecutor : ResultExtensionsExecutor
|
||||||
{
|
{
|
||||||
protected override string ExtensionType => "Map";
|
protected override string ExtensionType => "Map";
|
||||||
|
protected override string NamespaceName => "Just.Railway";
|
||||||
|
protected override string PartialClassName => "ResultExtensions";
|
||||||
|
|
||||||
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
||||||
{
|
{
|
||||||
var templateArgNames = Enumerable.Range(1, argCount)
|
var templateArgNames = Enumerable.Range(1, argCount)
|
||||||
@@ -35,12 +39,11 @@ internal sealed class ResultMapExecutor : ResultExtensionsExecutor
|
|||||||
""");
|
""");
|
||||||
|
|
||||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
|
||||||
|
|
||||||
sb.AppendLine("#endregion");
|
sb.AppendLine("#endregion");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
protected static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||||
{
|
{
|
||||||
var methodTemplateArgNames = templateArgNames.Add("R");
|
var methodTemplateArgNames = templateArgNames.Add("R");
|
||||||
string methodTemplateDecl = GenerateTemplateDecl(methodTemplateArgNames);
|
string methodTemplateDecl = GenerateTemplateDecl(methodTemplateArgNames);
|
||||||
+5
-4
@@ -3,11 +3,13 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
|
||||||
internal sealed class ResultMatchExecutor : ResultExtensionsExecutor
|
internal class ResultMatchExecutor : ResultExtensionsExecutor
|
||||||
{
|
{
|
||||||
protected override string ExtensionType => "Match";
|
protected override string ExtensionType => "Match";
|
||||||
|
protected override string NamespaceName => "Just.Railway";
|
||||||
|
protected override string PartialClassName => "ResultExtensions";
|
||||||
|
|
||||||
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
||||||
{
|
{
|
||||||
@@ -36,12 +38,11 @@ internal sealed class ResultMatchExecutor : ResultExtensionsExecutor
|
|||||||
""");
|
""");
|
||||||
|
|
||||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
|
||||||
|
|
||||||
sb.AppendLine("#endregion");
|
sb.AppendLine("#endregion");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
protected static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||||
{
|
{
|
||||||
var methodTemplateArgNames = templateArgNames.Add("R");
|
var methodTemplateArgNames = templateArgNames.Add("R");
|
||||||
string methodTemplateDecl = GenerateTemplateDecl(methodTemplateArgNames);
|
string methodTemplateDecl = GenerateTemplateDecl(methodTemplateArgNames);
|
||||||
+7
-6
@@ -3,11 +3,13 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
|
||||||
internal sealed class ResultTapExecutor : ResultExtensionsExecutor
|
internal class ResultTapExecutor : ResultExtensionsExecutor
|
||||||
{
|
{
|
||||||
protected override string ExtensionType => "Tap";
|
protected override string ExtensionType => "Tap";
|
||||||
|
protected override string NamespaceName => "Just.Railway";
|
||||||
|
protected override string PartialClassName => "ResultExtensions";
|
||||||
|
|
||||||
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
||||||
{
|
{
|
||||||
@@ -24,7 +26,7 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor
|
|||||||
sb.AppendLine($$"""
|
sb.AppendLine($$"""
|
||||||
[PureAttribute]
|
[PureAttribute]
|
||||||
[GeneratedCodeAttribute("{{nameof(ResultTapExecutor)}}", "1.0.0.0")]
|
[GeneratedCodeAttribute("{{nameof(ResultTapExecutor)}}", "1.0.0.0")]
|
||||||
public static ref readonly {{resultTypeDef}} Tap{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Action{{methodTemplateDecl}}? onSuccess = null, Action<Error>? onFailure = null)
|
public static {{resultTypeDef}} Tap{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Action{{methodTemplateDecl}}? onSuccess = null, Action<Error>? onFailure = null)
|
||||||
{
|
{
|
||||||
switch (result.State)
|
switch (result.State)
|
||||||
{
|
{
|
||||||
@@ -37,17 +39,16 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor
|
|||||||
|
|
||||||
default: throw new ResultNotInitializedException(nameof(result));
|
default: throw new ResultNotInitializedException(nameof(result));
|
||||||
}
|
}
|
||||||
return ref result;
|
return result;
|
||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
|
|
||||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
|
||||||
|
|
||||||
sb.AppendLine("#endregion");
|
sb.AppendLine("#endregion");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
protected static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||||
{
|
{
|
||||||
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames);
|
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames);
|
||||||
string asyncActionTemplateDecl = GenerateTemplateDecl(templateArgNames.Add(taskType));
|
string asyncActionTemplateDecl = GenerateTemplateDecl(templateArgNames.Add(taskType));
|
||||||
+7
-4
@@ -2,11 +2,14 @@ using System.Collections.Immutable;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
|
||||||
internal sealed class ResultTryRecoverExecutor : ResultExtensionsExecutor
|
internal class ResultTryRecoverExecutor : ResultExtensionsExecutor
|
||||||
{
|
{
|
||||||
protected override string ExtensionType => "TryRecover";
|
protected override string ExtensionType => "TryRecover";
|
||||||
|
protected override string NamespaceName => "Just.Railway";
|
||||||
|
protected override string PartialClassName => "ResultExtensions";
|
||||||
|
|
||||||
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
||||||
{
|
{
|
||||||
if (argCount > 1) return;
|
if (argCount > 1) return;
|
||||||
@@ -34,11 +37,11 @@ internal sealed class ResultTryRecoverExecutor : ResultExtensionsExecutor
|
|||||||
""");
|
""");
|
||||||
|
|
||||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, methodTemplateDecl);
|
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, methodTemplateDecl);
|
||||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, methodTemplateDecl);
|
|
||||||
|
|
||||||
sb.AppendLine("#endregion");
|
sb.AppendLine("#endregion");
|
||||||
}
|
}
|
||||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string methodTemplateDecl)
|
|
||||||
|
protected static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string methodTemplateDecl)
|
||||||
{
|
{
|
||||||
sb.AppendLine($$"""
|
sb.AppendLine($$"""
|
||||||
[PureAttribute]
|
[PureAttribute]
|
||||||
+7
-5
@@ -2,11 +2,11 @@ using System.Collections.Immutable;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions;
|
||||||
|
|
||||||
internal abstract class ResultExtensionsExecutor : IGeneratorExecutor
|
internal abstract class ResultExtensionsExecutor : IGeneratorExecutor
|
||||||
{
|
{
|
||||||
public void Execute(SourceProductionContext context, Compilation source)
|
public void Execute(IncrementalGeneratorPostInitializationContext context)
|
||||||
{
|
{
|
||||||
var methods = GenerateMethods();
|
var methods = GenerateMethods();
|
||||||
var code = $$"""
|
var code = $$"""
|
||||||
@@ -16,15 +16,15 @@ internal abstract class ResultExtensionsExecutor : IGeneratorExecutor
|
|||||||
using System.Diagnostics.Contracts;
|
using System.Diagnostics.Contracts;
|
||||||
using System.CodeDom.Compiler;
|
using System.CodeDom.Compiler;
|
||||||
|
|
||||||
namespace Just.Railway;
|
namespace {{NamespaceName}};
|
||||||
|
|
||||||
public static partial class ResultExtensions
|
public static partial class {{PartialClassName}}
|
||||||
{
|
{
|
||||||
{{methods}}
|
{{methods}}
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
context.AddSource($"ResultExtensions.{ExtensionType}.g.cs", code);
|
context.AddSource($"{PartialClassName}.{ExtensionType}.g.cs", code);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GenerateMethods()
|
private string GenerateMethods()
|
||||||
@@ -87,6 +87,8 @@ internal abstract class ResultExtensionsExecutor : IGeneratorExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract string ExtensionType { get; }
|
protected abstract string ExtensionType { get; }
|
||||||
|
protected abstract string NamespaceName { get; }
|
||||||
|
protected abstract string PartialClassName { get; }
|
||||||
protected abstract void GenerateMethodsForArgCount(StringBuilder sb, int argCount);
|
protected abstract void GenerateMethodsForArgCount(StringBuilder sb, int argCount);
|
||||||
protected virtual void GenerateHelperMethods(StringBuilder sb) {}
|
protected virtual void GenerateHelperMethods(StringBuilder sb) {}
|
||||||
}
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.ValueTaskExtensions;
|
||||||
|
|
||||||
|
internal class ResultValueTaskAppendExecutor : ResultAppendExecutor
|
||||||
|
{
|
||||||
|
protected override string NamespaceName => "Just.Railway.ValueTaskExtensions";
|
||||||
|
protected override string PartialClassName => "ResultValueTaskExtensions";
|
||||||
|
|
||||||
|
protected override void GenerateHelperMethods(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.AppendLine("""
|
||||||
|
private static IEnumerable<string> 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);
|
||||||
|
|
||||||
|
sb.AppendLine($"#region {resultTypeDef}");
|
||||||
|
|
||||||
|
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
|
|
||||||
|
if (argCount < Constants.MaxResultTupleSize)
|
||||||
|
{
|
||||||
|
GenerateExpandedAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("#endregion");
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.ValueTaskExtensions;
|
||||||
|
|
||||||
|
internal class ResultValueTaskBindExecutor : ResultBindExecutor
|
||||||
|
{
|
||||||
|
protected override string NamespaceName => "Just.Railway.ValueTaskExtensions";
|
||||||
|
protected override string PartialClassName => "ResultValueTaskExtensions";
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
sb.AppendLine($"#region {resultTypeDef}");
|
||||||
|
|
||||||
|
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
|
|
||||||
|
sb.AppendLine("#endregion");
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.ValueTaskExtensions;
|
||||||
|
|
||||||
|
internal sealed class ResultValueTaskExtendExecutor : ResultExtendExecutor
|
||||||
|
{
|
||||||
|
protected override string NamespaceName => "Just.Railway.ValueTaskExtensions";
|
||||||
|
protected override string PartialClassName => "ResultValueTaskExtensions";
|
||||||
|
|
||||||
|
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
||||||
|
{
|
||||||
|
if (argCount == 0 || argCount == Constants.MaxResultTupleSize)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var templateArgNames = Enumerable.Range(1, argCount)
|
||||||
|
.Select(i => $"T{i}")
|
||||||
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
var expandedTemplateArgNames = templateArgNames.Add("R");
|
||||||
|
string resultTypeDef = GenerateResultTypeDef(templateArgNames);
|
||||||
|
string resultValueExpansion = GenerateResultValueExpansion(templateArgNames);
|
||||||
|
|
||||||
|
sb.AppendLine($"#region {resultTypeDef}");
|
||||||
|
|
||||||
|
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
|
|
||||||
|
sb.AppendLine("#endregion");
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.ValueTaskExtensions;
|
||||||
|
|
||||||
|
internal sealed class ResultValueTaskMapExecutor : ResultMapExecutor
|
||||||
|
{
|
||||||
|
protected override string NamespaceName => "Just.Railway.ValueTaskExtensions";
|
||||||
|
protected override string PartialClassName => "ResultValueTaskExtensions";
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
sb.AppendLine($"#region {resultTypeDef}");
|
||||||
|
|
||||||
|
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
|
|
||||||
|
sb.AppendLine("#endregion");
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.ValueTaskExtensions;
|
||||||
|
|
||||||
|
internal class ResultValueTaskMatchExecutor : ResultMatchExecutor
|
||||||
|
{
|
||||||
|
protected override string NamespaceName => "Just.Railway.ValueTaskExtensions";
|
||||||
|
protected override string PartialClassName => "ResultValueTaskExtensions";
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
sb.AppendLine($"#region {resultTypeDef}");
|
||||||
|
|
||||||
|
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
|
|
||||||
|
sb.AppendLine("#endregion");
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.ValueTaskExtensions;
|
||||||
|
|
||||||
|
internal class ResultValueTaskTapExecutor : ResultTapExecutor
|
||||||
|
{
|
||||||
|
protected override string NamespaceName => "Just.Railway.ValueTaskExtensions";
|
||||||
|
protected override string PartialClassName => "ResultValueTaskExtensions";
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
sb.AppendLine($"#region {resultTypeDef}");
|
||||||
|
|
||||||
|
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||||
|
|
||||||
|
sb.AppendLine("#endregion");
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
|
||||||
|
namespace Just.Railway.SourceGen.Executors.ResultExtensions.ValueTaskExtensions;
|
||||||
|
|
||||||
|
internal class ResultValueTaskTryRecoverExecutor : ResultTryRecoverExecutor
|
||||||
|
{
|
||||||
|
protected override string NamespaceName => "Just.Railway.ValueTaskExtensions";
|
||||||
|
protected override string PartialClassName => "ResultValueTaskExtensions";
|
||||||
|
|
||||||
|
|
||||||
|
protected override void GenerateMethodsForArgCount(StringBuilder sb, int argCount)
|
||||||
|
{
|
||||||
|
if (argCount > 1) return;
|
||||||
|
|
||||||
|
var templateArgNames = Enumerable.Repeat("T", argCount)
|
||||||
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames);
|
||||||
|
string resultTypeDef = GenerateResultTypeDef(templateArgNames);
|
||||||
|
|
||||||
|
sb.AppendLine($"#region {resultTypeDef}");
|
||||||
|
|
||||||
|
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, methodTemplateDecl);
|
||||||
|
|
||||||
|
sb.AppendLine("#endregion");
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-9
@@ -3,11 +3,11 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen.Executors;
|
||||||
|
|
||||||
public sealed class TryExtensionsExecutor : IGeneratorExecutor
|
public sealed class TryExtensionsExecutor : IGeneratorExecutor
|
||||||
{
|
{
|
||||||
public void Execute(SourceProductionContext context, Compilation source)
|
public void Execute(IncrementalGeneratorPostInitializationContext context)
|
||||||
{
|
{
|
||||||
var methods = GenerateMethods();
|
var methods = GenerateMethods();
|
||||||
var code = $$"""
|
var code = $$"""
|
||||||
@@ -123,13 +123,13 @@ public sealed class TryExtensionsExecutor : IGeneratorExecutor
|
|||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
|
|
||||||
GenerateAsyncMethods(sb, templateArgNames, actionTemplateDecl, funcTemplateDecl, argumentsDeclExpansion, argumentsExpansion, "Task");
|
GenerateAsyncMethods(sb, templateArgNames, actionTemplateDecl, funcTemplateDecl, argumentsDeclExpansion, argumentsExpansion, "Task", string.Empty);
|
||||||
GenerateAsyncMethods(sb, templateArgNames, actionTemplateDecl, funcTemplateDecl, argumentsDeclExpansion, argumentsExpansion, "ValueTask");
|
GenerateAsyncMethods(sb, templateArgNames, actionTemplateDecl, funcTemplateDecl, argumentsDeclExpansion, argumentsExpansion, "ValueTask", "ValueTask");
|
||||||
|
|
||||||
sb.AppendLine("#endregion");
|
sb.AppendLine("#endregion");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateAsyncMethods(StringBuilder sb, ImmutableArray<string> templateArgNames, string actionTemplateDecl, string funcTemplateDecl, string argumentsDeclExpansion, string argumentsExpansion, string taskType)
|
private static void GenerateAsyncMethods(StringBuilder sb, ImmutableArray<string> templateArgNames, string actionTemplateDecl, string funcTemplateDecl, string argumentsDeclExpansion, string argumentsExpansion, string taskType, string methodSuffix)
|
||||||
{
|
{
|
||||||
string actionTaskTemplateDecl = GenerateTemplateDecl(templateArgNames.Add(taskType));
|
string actionTaskTemplateDecl = GenerateTemplateDecl(templateArgNames.Add(taskType));
|
||||||
string resultActionTaskTemplateDecl = GenerateTemplateDecl(templateArgNames.Add($"{taskType}<Result>"));
|
string resultActionTaskTemplateDecl = GenerateTemplateDecl(templateArgNames.Add($"{taskType}<Result>"));
|
||||||
@@ -138,7 +138,7 @@ public sealed class TryExtensionsExecutor : IGeneratorExecutor
|
|||||||
sb.AppendLine($$"""
|
sb.AppendLine($$"""
|
||||||
[PureAttribute]
|
[PureAttribute]
|
||||||
[GeneratedCodeAttribute("{{nameof(TryExtensionsExecutor)}}", "1.0.0.0")]
|
[GeneratedCodeAttribute("{{nameof(TryExtensionsExecutor)}}", "1.0.0.0")]
|
||||||
public static async {{taskType}}<Result> Run{{actionTemplateDecl}}(Func{{actionTaskTemplateDecl}} action{{TrailingArguments(argumentsDeclExpansion)}})
|
public static async {{taskType}}<Result> Run{{methodSuffix}}{{actionTemplateDecl}}(Func{{actionTaskTemplateDecl}} action{{TrailingArguments(argumentsDeclExpansion)}})
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -155,7 +155,7 @@ public sealed class TryExtensionsExecutor : IGeneratorExecutor
|
|||||||
sb.AppendLine($$"""
|
sb.AppendLine($$"""
|
||||||
[PureAttribute]
|
[PureAttribute]
|
||||||
[GeneratedCodeAttribute("{{nameof(TryExtensionsExecutor)}}", "1.0.0.0")]
|
[GeneratedCodeAttribute("{{nameof(TryExtensionsExecutor)}}", "1.0.0.0")]
|
||||||
public static async {{taskType}}<Result> Run{{actionTemplateDecl}}(Func{{resultActionTaskTemplateDecl}} action{{TrailingArguments(argumentsDeclExpansion)}})
|
public static async {{taskType}}<Result> Run{{methodSuffix}}{{actionTemplateDecl}}(Func{{resultActionTaskTemplateDecl}} action{{TrailingArguments(argumentsDeclExpansion)}})
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -171,7 +171,7 @@ public sealed class TryExtensionsExecutor : IGeneratorExecutor
|
|||||||
sb.AppendLine($$"""
|
sb.AppendLine($$"""
|
||||||
[PureAttribute]
|
[PureAttribute]
|
||||||
[GeneratedCodeAttribute("{{nameof(TryExtensionsExecutor)}}", "1.0.0.0")]
|
[GeneratedCodeAttribute("{{nameof(TryExtensionsExecutor)}}", "1.0.0.0")]
|
||||||
public static async {{taskType}}<Result<TResult>> Run{{funcTemplateDecl}}(Func{{funcTaskTemplateDecl}} func{{TrailingArguments(argumentsDeclExpansion)}})
|
public static async {{taskType}}<Result<TResult>> Run{{methodSuffix}}{{funcTemplateDecl}}(Func{{funcTaskTemplateDecl}} func{{TrailingArguments(argumentsDeclExpansion)}})
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -187,7 +187,7 @@ public sealed class TryExtensionsExecutor : IGeneratorExecutor
|
|||||||
sb.AppendLine($$"""
|
sb.AppendLine($$"""
|
||||||
[PureAttribute]
|
[PureAttribute]
|
||||||
[GeneratedCodeAttribute("{{nameof(TryExtensionsExecutor)}}", "1.0.0.0")]
|
[GeneratedCodeAttribute("{{nameof(TryExtensionsExecutor)}}", "1.0.0.0")]
|
||||||
public static async {{taskType}}<Result<TResult>> Run{{funcTemplateDecl}}(Func{{resultFuncTaskTemplateDecl}} func{{TrailingArguments(argumentsDeclExpansion)}})
|
public static async {{taskType}}<Result<TResult>> Run{{methodSuffix}}{{funcTemplateDecl}}(Func{{resultFuncTaskTemplateDecl}} func{{TrailingArguments(argumentsDeclExpansion)}})
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Just.Railway.SourceGen.Executors;
|
||||||
|
using Just.Railway.SourceGen.Executors.ResultExtensions.MainExtensions;
|
||||||
|
using Just.Railway.SourceGen.Executors.ResultExtensions.ValueTaskExtensions;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace Just.Railway.SourceGen;
|
namespace Just.Railway.SourceGen;
|
||||||
@@ -6,9 +9,10 @@ namespace Just.Railway.SourceGen;
|
|||||||
[Generator]
|
[Generator]
|
||||||
public class ExtensionsMethodGenerator : IIncrementalGenerator
|
public class ExtensionsMethodGenerator : IIncrementalGenerator
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<IGeneratorExecutor> _executors = new IGeneratorExecutor[]
|
private readonly IEnumerable<IGeneratorExecutor> _executors =
|
||||||
{
|
[
|
||||||
new ResultCombineExecutor(),
|
new ResultCombineExecutor(),
|
||||||
|
|
||||||
new ResultMatchExecutor(),
|
new ResultMatchExecutor(),
|
||||||
new ResultMapExecutor(),
|
new ResultMapExecutor(),
|
||||||
new ResultBindExecutor(),
|
new ResultBindExecutor(),
|
||||||
@@ -16,15 +20,24 @@ public class ExtensionsMethodGenerator : IIncrementalGenerator
|
|||||||
new ResultExtendExecutor(),
|
new ResultExtendExecutor(),
|
||||||
new ResultTryRecoverExecutor(),
|
new ResultTryRecoverExecutor(),
|
||||||
new ResultAppendExecutor(),
|
new ResultAppendExecutor(),
|
||||||
|
|
||||||
|
new ResultValueTaskMatchExecutor(),
|
||||||
|
new ResultValueTaskMapExecutor(),
|
||||||
|
new ResultValueTaskBindExecutor(),
|
||||||
|
new ResultValueTaskTapExecutor(),
|
||||||
|
new ResultValueTaskExtendExecutor(),
|
||||||
|
new ResultValueTaskTryRecoverExecutor(),
|
||||||
|
new ResultValueTaskAppendExecutor(),
|
||||||
|
|
||||||
new TryExtensionsExecutor(),
|
new TryExtensionsExecutor(),
|
||||||
new EnsureExtensionsExecutor(),
|
new EnsureExtensionsExecutor(),
|
||||||
};
|
];
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
{
|
{
|
||||||
foreach (var executor in _executors)
|
foreach (var executor in _executors)
|
||||||
{
|
{
|
||||||
context.RegisterSourceOutput(context.CompilationProvider, executor.Execute);
|
context.RegisterPostInitializationOutput(executor.Execute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ namespace Just.Railway.SourceGen;
|
|||||||
|
|
||||||
internal interface IGeneratorExecutor
|
internal interface IGeneratorExecutor
|
||||||
{
|
{
|
||||||
public abstract void Execute(SourceProductionContext context, Compilation source);
|
public abstract void Execute(IncrementalGeneratorPostInitializationContext context);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -206,6 +206,7 @@ public sealed class ExpectedError : Error
|
|||||||
{
|
{
|
||||||
public ExpectedError(string type, string message)
|
public ExpectedError(string type, string message)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(type);
|
||||||
Type = type;
|
Type = type;
|
||||||
Message = message;
|
Message = message;
|
||||||
}
|
}
|
||||||
@@ -252,6 +253,7 @@ public sealed class ExceptionalError : Error
|
|||||||
|
|
||||||
public ExceptionalError(string type, string message)
|
public ExceptionalError(string type, string message)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(type);
|
||||||
Type = type;
|
Type = type;
|
||||||
Message = message;
|
Message = message;
|
||||||
}
|
}
|
||||||
@@ -343,7 +345,7 @@ public sealed class ManyErrors : Error, IEnumerable<Error>, IReadOnlyList<Error>
|
|||||||
[Pure] public override string Type => "many_errors";
|
[Pure] public override string Type => "many_errors";
|
||||||
|
|
||||||
private string? _lazyMessage = null;
|
private string? _lazyMessage = null;
|
||||||
[Pure] public override string Message => _lazyMessage ??= ToFullArrayString(_errors);
|
[Pure] public override string Message => _lazyMessage ??= ToFullArrayString(_errors); // not thread safe. wontfix
|
||||||
|
|
||||||
[Pure] private static string ToFullArrayString(in ImmutableArray<Error> errors)
|
[Pure] private static string ToFullArrayString(in ImmutableArray<Error> errors)
|
||||||
{
|
{
|
||||||
@@ -428,7 +430,7 @@ public sealed class ManyErrors : Error, IEnumerable<Error>, IReadOnlyList<Error>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int? _lazyHashCode = null;
|
private int? _lazyHashCode = null;
|
||||||
[Pure] public override int GetHashCode() => _lazyHashCode ??= CalcHashCode(_errors);
|
[Pure] public override int GetHashCode() => _lazyHashCode ??= CalcHashCode(_errors); // not thread safe. wontfix
|
||||||
private static int CalcHashCode(in ImmutableArray<Error> errors)
|
private static int CalcHashCode(in ImmutableArray<Error> errors)
|
||||||
{
|
{
|
||||||
if (errors.IsEmpty)
|
if (errors.IsEmpty)
|
||||||
|
|||||||
@@ -7,13 +7,9 @@ internal static class ReflectionHelper
|
|||||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool IsEqual<T>(T? left, T? right) => TypeReflectionCache<T>.IsEqualFunc(left, right);
|
public static bool IsEqual<T>(T? left, T? right) => TypeReflectionCache<T>.IsEqualFunc(left, right);
|
||||||
|
|
||||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int Compare<T>(T? left, T? right) => TypeReflectionCache<T>.CompareFunc(left, right);
|
|
||||||
|
|
||||||
private static class TypeReflectionCache<T>
|
private static class TypeReflectionCache<T>
|
||||||
{
|
{
|
||||||
public static readonly Func<T?, T?, bool> IsEqualFunc;
|
public static readonly Func<T?, T?, bool> IsEqualFunc;
|
||||||
public static readonly Func<T?, T?, int> CompareFunc;
|
|
||||||
|
|
||||||
static TypeReflectionCache()
|
static TypeReflectionCache()
|
||||||
{
|
{
|
||||||
@@ -34,33 +30,11 @@ internal static class ReflectionHelper
|
|||||||
{
|
{
|
||||||
IsEqualFunc = static (left, right) => left is null ? right is null : left.Equals(right);
|
IsEqualFunc = static (left, right) => left is null ? right is null : left.Equals(right);
|
||||||
}
|
}
|
||||||
|
|
||||||
var comparableType = typeof(IComparable<>).MakeGenericType(underlyingType);
|
|
||||||
if (comparableType.IsAssignableFrom(underlyingType))
|
|
||||||
{
|
|
||||||
var compareFunc = thisType.GetMethod(isNullableStruct ? nameof(CompareNullable) : nameof(Compare), BindingFlags.Static | BindingFlags.Public)
|
|
||||||
!.MakeGenericMethod(underlyingType);
|
|
||||||
|
|
||||||
CompareFunc = (Func<T?, T?, int>)Delegate.CreateDelegate(typeof(Func<T?, T?, int>), compareFunc);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CompareFunc = static (left, right) => left is null
|
|
||||||
? right is null ? 0 : -1
|
|
||||||
: right is null ? 1 : left.GetHashCode().CompareTo(right.GetHashCode());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS8604 // Possible null reference argument.
|
#pragma warning disable CS8604 // Possible null reference argument.
|
||||||
[Pure] public static bool IsEqual<R>(R? left, R? right) where R : notnull, IEquatable<R>, T => left is null ? right is null : left.Equals(right);
|
[Pure] public static bool IsEqual<R>(R? left, R? right) where R : notnull, IEquatable<R>, T => left is null ? right is null : left.Equals(right);
|
||||||
[Pure] public static bool IsEqualNullable<R>(R? left, R? right) where R : struct, IEquatable<R> => left is null ? right is null : right is not null && left.Value.Equals(right.Value);
|
[Pure] public static bool IsEqualNullable<R>(R? left, R? right) where R : struct, IEquatable<R> => left is null ? right is null : right is not null && left.Value.Equals(right.Value);
|
||||||
|
|
||||||
[Pure] public static int Compare<R>(R? left, R? right) where R : notnull, IComparable<R>, T => left is null
|
|
||||||
? right is null ? 0 : -1
|
|
||||||
: right is null ? 1 : left.CompareTo(right);
|
|
||||||
[Pure] public static int CompareNullable<R>(R? left, R? right) where R : struct, IComparable<R> => left is null
|
|
||||||
? right is null ? 0 : -1
|
|
||||||
: right is null ? 1 : left.Value.CompareTo(right.Value);
|
|
||||||
#pragma warning restore CS8604 // Possible null reference argument.
|
#pragma warning restore CS8604 // Possible null reference argument.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -127,7 +127,7 @@ public readonly partial struct Result : IEquatable<Result>
|
|||||||
[Pure] public override bool Equals(object? obj) => obj is Result other && Equals(other);
|
[Pure] public override bool Equals(object? obj) => obj is Result other && Equals(other);
|
||||||
[Pure] public bool Equals(Result other)
|
[Pure] public bool Equals(Result other)
|
||||||
{
|
{
|
||||||
if (State == ResultState.Bottom)
|
if (State == ResultState.Bottom || other.State == ResultState.Bottom)
|
||||||
throw new ResultNotInitializedException();
|
throw new ResultNotInitializedException();
|
||||||
|
|
||||||
return Error == other.Error;
|
return Error == other.Error;
|
||||||
@@ -260,7 +260,7 @@ public readonly struct Result<T> : IEquatable<Result<T>>
|
|||||||
[Pure] public override bool Equals(object? obj) => obj is Result<T> other && Equals(other);
|
[Pure] public override bool Equals(object? obj) => obj is Result<T> other && Equals(other);
|
||||||
[Pure] public bool Equals(Result<T> other)
|
[Pure] public bool Equals(Result<T> other)
|
||||||
{
|
{
|
||||||
if (State == ResultState.Bottom)
|
if (State == ResultState.Bottom || other.State == ResultState.Bottom)
|
||||||
throw new ResultNotInitializedException();
|
throw new ResultNotInitializedException();
|
||||||
|
|
||||||
if (IsSuccess != other.IsSuccess)
|
if (IsSuccess != other.IsSuccess)
|
||||||
|
|||||||
+141
-72
@@ -4,9 +4,82 @@ namespace Just.Railway;
|
|||||||
|
|
||||||
public static partial class ResultExtensions
|
public static partial class ResultExtensions
|
||||||
{
|
{
|
||||||
#region Match (with fallback)
|
#region Finally
|
||||||
|
|
||||||
public static T Match<T>(this in Result<T> result, Func<Error, T> fallback)
|
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
|
return result.State switch
|
||||||
{
|
{
|
||||||
@@ -16,7 +89,17 @@ public static partial class ResultExtensions
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<T> Match<T>(this Result<T> result, Func<Error, Task<T>> fallback)
|
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
|
return result.State switch
|
||||||
{
|
{
|
||||||
@@ -25,7 +108,7 @@ public static partial class ResultExtensions
|
|||||||
_ => throw new ResultNotInitializedException(nameof(result))
|
_ => throw new ResultNotInitializedException(nameof(result))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public static async Task<T> Match<T>(this Task<Result<T>> resultTask, Func<Error, T> fallback)
|
public static async Task<T> ValueOr<T>(this Task<Result<T>> resultTask, Func<Error, T> fallback)
|
||||||
{
|
{
|
||||||
var result = await resultTask.ConfigureAwait(false);
|
var result = await resultTask.ConfigureAwait(false);
|
||||||
return result.State switch
|
return result.State switch
|
||||||
@@ -35,37 +118,7 @@ public static partial class ResultExtensions
|
|||||||
_ => throw new ResultNotInitializedException(nameof(resultTask))
|
_ => throw new ResultNotInitializedException(nameof(resultTask))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public static async Task<T> Match<T>(this Task<Result<T>> resultTask, Func<Error, Task<T>> fallback)
|
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))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async ValueTask<T> Match<T>(this Result<T> result, Func<Error, ValueTask<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 ValueTask<T> Match<T>(this ValueTask<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 ValueTask<T> Match<T>(this ValueTask<Result<T>> resultTask, Func<Error, ValueTask<T>> fallback)
|
|
||||||
{
|
{
|
||||||
var result = await resultTask.ConfigureAwait(false);
|
var result = await resultTask.ConfigureAwait(false);
|
||||||
return result.State switch
|
return result.State switch
|
||||||
@@ -83,79 +136,95 @@ public static partial class ResultExtensions
|
|||||||
public static Result Merge(this IEnumerable<Result> results)
|
public static Result Merge(this IEnumerable<Result> results)
|
||||||
{
|
{
|
||||||
ImmutableArray<Error>.Builder? errors = null;
|
ImmutableArray<Error>.Builder? errors = null;
|
||||||
bool hasErrors = false;
|
|
||||||
|
|
||||||
foreach (var result in results.OrderBy(x => x.State))
|
foreach (var result in results)
|
||||||
{
|
{
|
||||||
switch (result.State)
|
switch (result.State)
|
||||||
{
|
{
|
||||||
case ResultState.Error:
|
case ResultState.Success:
|
||||||
hasErrors = true;
|
|
||||||
errors ??= ImmutableArray.CreateBuilder<Error>();
|
|
||||||
ManyErrors.AppendSanitized(errors, result.Error!);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ResultState.Success:
|
case ResultState.Error:
|
||||||
if (hasErrors) goto afterLoop;
|
errors ??= ImmutableArray.CreateBuilder<Error>();
|
||||||
|
ManyErrors.AppendSanitized(errors, result.Error!);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: throw new ResultNotInitializedException(nameof(results));
|
default: throw new ResultNotInitializedException(nameof(results));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
afterLoop:
|
|
||||||
return hasErrors
|
return errors is null
|
||||||
? new(new ManyErrors(errors!.ToImmutable()))
|
? new(null)
|
||||||
: new(null);
|
: new(new ManyErrors(errors.ToImmutable()));
|
||||||
}
|
}
|
||||||
public static async Task<Result> Merge(this IEnumerable<Task<Result>> tasks)
|
public static async Task<Result> Merge(this IEnumerable<Task<Result>> tasks)
|
||||||
{
|
{
|
||||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
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();
|
return results.Merge();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<IEnumerable<T>> Merge<T>(this IEnumerable<Result<T>> results)
|
public static Result<IEnumerable<T>> Merge<T>(this IEnumerable<Result<T>> results)
|
||||||
{
|
{
|
||||||
ImmutableList<T>.Builder? values = null;
|
|
||||||
ImmutableArray<Error>.Builder? errors = null;
|
ImmutableArray<Error>.Builder? errors = null;
|
||||||
bool hasErrors = false;
|
ImmutableArray<T>.Builder? values = null;
|
||||||
|
|
||||||
foreach (var result in results.OrderBy(x => x.State))
|
foreach (var result in results)
|
||||||
{
|
{
|
||||||
switch (result.State)
|
switch (result.State)
|
||||||
{
|
{
|
||||||
case ResultState.Error:
|
case ResultState.Success:
|
||||||
hasErrors = true;
|
values ??= ImmutableArray.CreateBuilder<T>();
|
||||||
errors ??= ImmutableArray.CreateBuilder<Error>();
|
values.Add(result.Value);
|
||||||
ManyErrors.AppendSanitized(errors, result.Error!);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ResultState.Success:
|
case ResultState.Error:
|
||||||
if (hasErrors) goto afterLoop;
|
errors ??= ImmutableArray.CreateBuilder<Error>();
|
||||||
values ??= ImmutableList.CreateBuilder<T>();
|
ManyErrors.AppendSanitized(errors, result.Error!);
|
||||||
values.Add(result.Value);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: throw new ResultNotInitializedException(nameof(results));
|
default: throw new ResultNotInitializedException(nameof(results));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
afterLoop:
|
|
||||||
return hasErrors
|
return errors is null
|
||||||
? new(new ManyErrors(errors!.ToImmutable()))
|
? new(values?.ToImmutable() ?? Enumerable.Empty<T>())
|
||||||
: new(values is not null ? values.ToImmutable() : ImmutableList<T>.Empty);
|
: new(new ManyErrors(errors.ToImmutable()));
|
||||||
}
|
}
|
||||||
public static async Task<Result<IEnumerable<T>>> Merge<T>(this IEnumerable<Task<Result<T>>> tasks)
|
public static async Task<Result<IEnumerable<T>>> Merge<T>(this IEnumerable<Task<Result<T>>> tasks)
|
||||||
{
|
{
|
||||||
var taskList = tasks.ToList();
|
var taskList = tasks.ToArray();
|
||||||
var results = new Result<T>[taskList.Count];
|
var results = new Result<T>[taskList.Length];
|
||||||
|
|
||||||
for (int i = 0; i < taskList.Count; i++)
|
// 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 = await Task.WhenAny(taskList);
|
var task = taskList[i];
|
||||||
taskList.Remove(task);
|
results[i] = task.Status switch
|
||||||
|
{
|
||||||
results[i] = task.IsCompletedSuccessfully
|
TaskStatus.RanToCompletion => task.Result,
|
||||||
? task.Result
|
TaskStatus.Faulted => Result.Failure<T>(task.Exception!),
|
||||||
: 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();
|
return results.Merge();
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
namespace Just.Railway;
|
namespace Just.Railway;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result based try/catch
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>All extension methods are source generated.</remarks>
|
||||||
public static partial class Try
|
public static partial class Try
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
namespace Just.Railway.ValueTaskExtensions;
|
||||||
|
|
||||||
|
public static partial class ResultValueTaskExtensions
|
||||||
|
{
|
||||||
|
#region Finally
|
||||||
|
|
||||||
|
public static async ValueTask<Result> ValueTaskFinally(this ValueTask<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 ValueTask<Result> ValueTaskFinally(this Result result, Func<ValueTask> 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 ValueTask<Result> ValueTaskFinally(this ValueTask<Result> resultTask, Func<ValueTask> 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 async ValueTask<T> ValueTaskValueOr<T>(this ValueTask<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 ValueTask<T> ValueTaskValueOr<T>(this Result<T> result, Func<Error, ValueTask<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 ValueTask<T> ValueTaskValueOr<T>(this ValueTask<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 ValueTask<T> ValueTaskValueOr<T>(this ValueTask<Result<T>> resultTask, Func<Error, ValueTask<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
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
namespace Raliway.Tests.ReflectionHelper;
|
||||||
|
|
||||||
|
// --- Test types ---
|
||||||
|
|
||||||
|
file sealed record EquatableRecord(string Name, int Value);
|
||||||
|
|
||||||
|
file sealed class NonEquatableClass
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public NonEquatableClass(string name) => Name = name;
|
||||||
|
public override bool Equals(object? obj) => obj is NonEquatableClass other && Name == other.Name;
|
||||||
|
public override int GetHashCode() => Name.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
file readonly struct EquatableStruct : IEquatable<EquatableStruct>
|
||||||
|
{
|
||||||
|
public int Value { get; }
|
||||||
|
public EquatableStruct(int value) => Value = value;
|
||||||
|
public bool Equals(EquatableStruct other) => Value == other.Value;
|
||||||
|
public override bool Equals(object? obj) => obj is EquatableStruct s && Equals(s);
|
||||||
|
public override int GetHashCode() => Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- IsEqual tests ---
|
||||||
|
|
||||||
|
public class IsEqual_Tests
|
||||||
|
{
|
||||||
|
public class ValueType_IEquatable
|
||||||
|
{
|
||||||
|
[Fact] public void Equal_values() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(42, 42).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Different_values() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(42, 43).ShouldBeFalse();
|
||||||
|
|
||||||
|
[Fact] public void Null_args() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(default(int), default(int)).ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NullableValueType_IEquatable
|
||||||
|
{
|
||||||
|
[Fact] public void Both_have_same_value() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<int?>(42, 42).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Different_values() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<int?>(42, 43).ShouldBeFalse();
|
||||||
|
|
||||||
|
[Fact] public void Both_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<int?>(null, null).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void One_null_one_value() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<int?>(42, null).ShouldBeFalse();
|
||||||
|
|
||||||
|
[Fact] public void Null_and_value_reversed() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<int?>(null, 42).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReferenceType_IEquatable_String
|
||||||
|
{
|
||||||
|
[Fact] public void Equal_strings() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual("hello", "hello").ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Different_strings() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual("hello", "world").ShouldBeFalse();
|
||||||
|
|
||||||
|
[Fact] public void Both_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<string>(null, null).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Left_null_right_value() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<string>(null, "hello").ShouldBeFalse();
|
||||||
|
|
||||||
|
[Fact] public void Left_value_right_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual("hello", null).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReferenceType_IEquatable_Error
|
||||||
|
{
|
||||||
|
[Fact] public void Equal_errors() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(
|
||||||
|
Error.New("type", "msg"),
|
||||||
|
Error.New("type", "msg")
|
||||||
|
).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Different_errors() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(
|
||||||
|
Error.New("type_a", "msg"),
|
||||||
|
Error.New("type_b", "msg")
|
||||||
|
).ShouldBeFalse();
|
||||||
|
|
||||||
|
[Fact] public void Both_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<Error>(null, null).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void One_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(Error.New("type", "msg"), null).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RecordType_IEquatable
|
||||||
|
{
|
||||||
|
[Fact] public void Equal_records() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(
|
||||||
|
new EquatableRecord("a", 1),
|
||||||
|
new EquatableRecord("a", 1)
|
||||||
|
).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Different_records() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(
|
||||||
|
new EquatableRecord("a", 1),
|
||||||
|
new EquatableRecord("b", 2)
|
||||||
|
).ShouldBeFalse();
|
||||||
|
|
||||||
|
[Fact] public void Both_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<EquatableRecord>(null, null).ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NonIEquatable_Fallback
|
||||||
|
{
|
||||||
|
[Fact] public void Equal_by_Equals_override() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(
|
||||||
|
new NonEquatableClass("a"),
|
||||||
|
new NonEquatableClass("a")
|
||||||
|
).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Not_equal() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(
|
||||||
|
new NonEquatableClass("a"),
|
||||||
|
new NonEquatableClass("b")
|
||||||
|
).ShouldBeFalse();
|
||||||
|
|
||||||
|
[Fact] public void Both_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<NonEquatableClass>(null, null).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Left_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<NonEquatableClass>(null, new NonEquatableClass("a"))
|
||||||
|
.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomEquatableStruct
|
||||||
|
{
|
||||||
|
[Fact] public void Equal() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(
|
||||||
|
new EquatableStruct(1),
|
||||||
|
new EquatableStruct(1)
|
||||||
|
).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Not_equal() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual(
|
||||||
|
new EquatableStruct(1),
|
||||||
|
new EquatableStruct(2)
|
||||||
|
).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomNullableEquatableStruct
|
||||||
|
{
|
||||||
|
[Fact] public void Both_have_same_value() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<EquatableStruct?>(
|
||||||
|
new EquatableStruct(1),
|
||||||
|
new EquatableStruct(1)
|
||||||
|
).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void Different_values() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<EquatableStruct?>(
|
||||||
|
new EquatableStruct(1),
|
||||||
|
new EquatableStruct(2)
|
||||||
|
).ShouldBeFalse();
|
||||||
|
|
||||||
|
[Fact] public void Both_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<EquatableStruct?>(null, null).ShouldBeTrue();
|
||||||
|
|
||||||
|
[Fact] public void One_null() =>
|
||||||
|
Just.Railway.ReflectionHelper.IsEqual<EquatableStruct?>(new EquatableStruct(1), null)
|
||||||
|
.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
namespace Raliway.Tests.Results;
|
||||||
|
|
||||||
|
public class Bugs
|
||||||
|
{
|
||||||
|
#region BUG-1: Result.Equals asymmetry
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Result_Success_ShouldNotEqual_Default()
|
||||||
|
{
|
||||||
|
// default(Result) has State=Bottom — it's uninitialized.
|
||||||
|
// Equals should throw because one operand is Bottom.
|
||||||
|
var success = Result.Success();
|
||||||
|
Result bottom = default;
|
||||||
|
|
||||||
|
// Should throw ResultNotInitializedException, NOT return true
|
||||||
|
Should.Throw<ResultNotInitializedException>(() => success.Equals(bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Result_Default_ShouldNotEqual_Success()
|
||||||
|
{
|
||||||
|
Result bottom = default;
|
||||||
|
var success = Result.Success();
|
||||||
|
|
||||||
|
Should.Throw<ResultNotInitializedException>(() => bottom.Equals(success));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Result_Success_EqualsOperator_WithDefault()
|
||||||
|
{
|
||||||
|
var success = Result.Success();
|
||||||
|
Result bottom = default;
|
||||||
|
|
||||||
|
Should.Throw<ResultNotInitializedException>(() => success == bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Result_Error_Equals_WithBottom_ShouldThrow()
|
||||||
|
{
|
||||||
|
var error = Result.Failure("test");
|
||||||
|
Result bottom = default;
|
||||||
|
|
||||||
|
Should.Throw<ResultNotInitializedException>(() => error.Equals(bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Result_Bottom_Equals_WithSuccess_ShouldThrow()
|
||||||
|
{
|
||||||
|
Result bottom = default;
|
||||||
|
var success = Result.Success();
|
||||||
|
|
||||||
|
Should.Throw<ResultNotInitializedException>(() => bottom.Equals(success));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region BUG-2: Result<T>.Equals asymmetry
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResultOfT_Success_ShouldNotEqual_Default()
|
||||||
|
{
|
||||||
|
var success = Result.Success(42);
|
||||||
|
Result<int> bottom = default;
|
||||||
|
|
||||||
|
Should.Throw<ResultNotInitializedException>(() => success.Equals(bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResultOfT_Error_ShouldNotEqual_Default()
|
||||||
|
{
|
||||||
|
var error = Result.Failure<int>("test");
|
||||||
|
Result<int> bottom = default;
|
||||||
|
|
||||||
|
Should.Throw<ResultNotInitializedException>(() => error.Equals(bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResultOfT_Default_ShouldNotEqual_Success()
|
||||||
|
{
|
||||||
|
Result<int> bottom = default;
|
||||||
|
var success = Result.Success(42);
|
||||||
|
|
||||||
|
Should.Throw<ResultNotInitializedException>(() => bottom.Equals(success));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region BUG-3: Task.WhenAny Merge — faulted task
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Merge_Tasks_WhenOneFaults_ShouldReturnError_NotThrow()
|
||||||
|
{
|
||||||
|
var tasks = new Task<Result>[]
|
||||||
|
{
|
||||||
|
Task.FromResult(Result.Success()),
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("boom");
|
||||||
|
#pragma warning disable CS0162 // Unreachable code
|
||||||
|
return Result.Success();
|
||||||
|
#pragma warning restore CS0162
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Should not throw AggregateException escaping the Result error channel
|
||||||
|
var result = await tasks.Merge();
|
||||||
|
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Merge_Tasks_WhenOneIsCanceled_ShouldReturnError_NotThrow()
|
||||||
|
{
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
cts.Cancel();
|
||||||
|
var canceledTask = Task.FromCanceled<Result>(cts.Token);
|
||||||
|
|
||||||
|
var tasks = new Task<Result>[]
|
||||||
|
{
|
||||||
|
Task.FromResult(Result.Success()),
|
||||||
|
canceledTask,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Should not throw — canceled tasks have null Exception
|
||||||
|
var result = await tasks.Merge();
|
||||||
|
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region BUG-4: Task.WhenAny Merge<T> — canceled task + O(n²)
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MergeOfT_Tasks_WhenOneIsCanceled_ShouldReturnError_NotThrow()
|
||||||
|
{
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
cts.Cancel();
|
||||||
|
var canceledTask = Task.FromCanceled<Result<int>>(cts.Token);
|
||||||
|
|
||||||
|
var tasks = new Task<Result<int>>[]
|
||||||
|
{
|
||||||
|
Task.FromResult(Result.Success(1)),
|
||||||
|
canceledTask,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Should not crash on canceled task (task.Exception is null → null ref in implicit operator)
|
||||||
|
var result = await tasks.Merge();
|
||||||
|
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MergeOfT_Tasks_WhenOneFaults_ShouldReturnError_NotThrow()
|
||||||
|
{
|
||||||
|
var tasks = new Task<Result<int>>[]
|
||||||
|
{
|
||||||
|
Task.FromResult(Result.Success(1)),
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("boom");
|
||||||
|
#pragma warning disable CS0162 // Unreachable code
|
||||||
|
return Result.Success(2);
|
||||||
|
#pragma warning restore CS0162
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await tasks.Merge();
|
||||||
|
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region BUG-5: Error.New null type
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Error_New_WithNullType_ShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
// Null type should throw immediately at construction, not crash later in CompareTo/IsSimilarTo
|
||||||
|
Should.Throw<ArgumentNullException>(() => Error.New(null!, "some message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ public class GeneralUsage
|
|||||||
var result = Result.Success()
|
var result = Result.Success()
|
||||||
.Append(() => Result.Failure<int>(error))
|
.Append(() => Result.Failure<int>(error))
|
||||||
.Append("test")
|
.Append("test")
|
||||||
|
.Tap(onSuccess: (i, s) => true.ShouldBeFalse("should not reach this path"))
|
||||||
.Map((i, s) =>
|
.Map((i, s) =>
|
||||||
{
|
{
|
||||||
true.ShouldBeFalse("should not reach this path");
|
true.ShouldBeFalse("should not reach this path");
|
||||||
|
|||||||
@@ -0,0 +1,558 @@
|
|||||||
|
namespace Raliway.Tests.TryExtensions;
|
||||||
|
|
||||||
|
public class RunTests
|
||||||
|
{
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Action (void) — sync
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Action_Void
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void No_args_success()
|
||||||
|
{
|
||||||
|
var result = Try.Run(() => { });
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void No_args_exception()
|
||||||
|
{
|
||||||
|
var result = Try.Run(new Action(() => throw new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
result.Error!.Type.ShouldBe("System.InvalidOperationException");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void One_arg_success()
|
||||||
|
{
|
||||||
|
var captured = 0;
|
||||||
|
var result = Try.Run<int>(x => captured = x, 42);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
captured.ShouldBe(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void One_arg_exception()
|
||||||
|
{
|
||||||
|
var result = Try.Run(new Action<int>(_ => throw new InvalidOperationException("boom")), 42);
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Five_args_success()
|
||||||
|
{
|
||||||
|
var captured = "";
|
||||||
|
var result = Try.Run<int, string, bool, double, char>(
|
||||||
|
(a, b, c, d, e) => captured = $"{a}{b}{c}{d}{e}",
|
||||||
|
1, "x", true, 3.14, 'z');
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
captured.ShouldBe("1xTrue3.14z");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Five_args_exception()
|
||||||
|
{
|
||||||
|
var result = Try.Run(
|
||||||
|
new Action<int, string, bool, double, char>((_, _, _, _, _) => throw new InvalidOperationException("boom")),
|
||||||
|
1, "x", true, 3.14, 'z');
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Func<Result> — sync
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Func_Result
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void No_args_returns_success()
|
||||||
|
{
|
||||||
|
var result = Try.Run(() => Result.Success());
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void No_args_returns_failure()
|
||||||
|
{
|
||||||
|
var error = Error.New("nope");
|
||||||
|
var result = Try.Run(() => Result.Failure(error));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error.ShouldBe(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void No_args_exception_caught()
|
||||||
|
{
|
||||||
|
var result = Try.Run(new Func<Result>(() => throw new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void One_arg_passes_through()
|
||||||
|
{
|
||||||
|
var result = Try.Run<int>(x => Result.Success(x), 42);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Func<T> — sync
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Func_T
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void No_args_success()
|
||||||
|
{
|
||||||
|
var result = Try.Run(() => 42);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void No_args_exception()
|
||||||
|
{
|
||||||
|
var result = Try.Run(new Func<int>(() => throw new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void One_arg_success()
|
||||||
|
{
|
||||||
|
var result = Try.Run(x => $"val:{x}", 42);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe("val:42");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void One_arg_exception()
|
||||||
|
{
|
||||||
|
var result = Try.Run(new Func<int, string>(_ => throw new InvalidOperationException("boom")), 42);
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Null_value_is_preserved()
|
||||||
|
{
|
||||||
|
var result = Try.Run(() => (string?)null);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Func<Result<T>> — sync
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Func_ResultT
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void No_args_returns_success()
|
||||||
|
{
|
||||||
|
var result = Try.Run(() => Result.Success(42));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void No_args_returns_failure()
|
||||||
|
{
|
||||||
|
var error = Error.New("nope");
|
||||||
|
var result = Try.Run(() => Result.Failure<int>(error));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error.ShouldBe(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void No_args_exception_caught()
|
||||||
|
{
|
||||||
|
var result = Try.Run(new Func<Result<int>>(() => throw new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void One_arg_passes_through()
|
||||||
|
{
|
||||||
|
var result = Try.Run(x => Result.Success($"val:{x}"), 42);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe("val:42");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Task async — Action (void)
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Task_Action
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_success()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(() => Task.CompletedTask);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_exception()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(() => Task.FromException(new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task One_arg_success()
|
||||||
|
{
|
||||||
|
var captured = 0;
|
||||||
|
var result = await Try.Run(x => { captured = x; return Task.CompletedTask; }, 42);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
captured.ShouldBe(42);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Task async — Func<Task<Result>>
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Task_Func_Result
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_returns_success()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(() => Task.FromResult(Result.Success()));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_returns_failure()
|
||||||
|
{
|
||||||
|
var error = Error.New("nope");
|
||||||
|
var result = await Try.Run(() => Task.FromResult(Result.Failure(error)));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error.ShouldBe(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_exception_caught()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(new Func<Task<Result>>(() => throw new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Task async — Func<Task<T>>
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Task_Func_T
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_success()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(() => Task.FromResult(42));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_exception()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(new Func<Task<int>>(() => throw new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Task async — Func<Task<Result<T>>>
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Task_Func_ResultT
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_returns_success()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(() => Task.FromResult(Result.Success(42)));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_returns_failure()
|
||||||
|
{
|
||||||
|
var error = Error.New("nope");
|
||||||
|
var result = await Try.Run(() => Task.FromResult(Result.Failure<int>(error)));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error.ShouldBe(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_exception_caught()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(new Func<Task<Result<int>>>(() => throw new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// ValueTask async — Action (void)
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class ValueTask_Action
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_success()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask(() => ValueTask.CompletedTask);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_exception()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask(() => ValueTask.FromException(new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task One_arg_success()
|
||||||
|
{
|
||||||
|
var captured = 0;
|
||||||
|
var result = await Try.RunValueTask(x => { captured = x; return ValueTask.CompletedTask; }, 42);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
captured.ShouldBe(42);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// ValueTask async — Func<ValueTask<Result>>
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class ValueTask_Func_Result
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_returns_success()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask(() => ValueTask.FromResult(Result.Success()));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_returns_failure()
|
||||||
|
{
|
||||||
|
var error = Error.New("nope");
|
||||||
|
var result = await Try.RunValueTask(() => ValueTask.FromResult(Result.Failure(error)));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error.ShouldBe(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_exception_caught()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask(new Func<ValueTask<Result>>(() => throw new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// ValueTask async — Func<ValueTask<T>>
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class ValueTask_Func_T
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_success()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask(() => ValueTask.FromResult(42));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_exception()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask<int>(() => throw new InvalidOperationException("boom"));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// ValueTask async — Func<ValueTask<Result<T>>>
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class ValueTask_Func_ResultT
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_returns_success()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask(() => ValueTask.FromResult(Result.Success(42)));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_returns_failure()
|
||||||
|
{
|
||||||
|
var error = Error.New("nope");
|
||||||
|
var result = await Try.RunValueTask(() => ValueTask.FromResult(Result.Failure<int>(error)));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error.ShouldBe(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task No_args_exception_caught()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask(new Func<ValueTask<Result<int>>>(() => throw new InvalidOperationException("boom")));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("boom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// AggregateException unwrapping
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class AggregateException_Unwrapping
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Single_inner_exception_is_unwrapped()
|
||||||
|
{
|
||||||
|
var result = Try.Run(new Func<int>(() => throw new AggregateException(new InvalidOperationException("inner"))));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Type.ShouldBe("System.InvalidOperationException");
|
||||||
|
result.Error!.Message.ShouldBe("inner");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Multiple_inner_exceptions_become_ManyErrors()
|
||||||
|
{
|
||||||
|
var result = Try.Run(new Func<int>(() => throw new AggregateException(
|
||||||
|
new InvalidOperationException("first"),
|
||||||
|
new ArgumentException("second"))));
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Type.ShouldBe("many_errors");
|
||||||
|
var many = (ManyErrors)result.Error!;
|
||||||
|
many.Count.ShouldBe(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Async exception in task body (not Task.FromException)
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Async_Exception_In_Body
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Task_throws_in_async_body()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
throw new InvalidOperationException("async boom");
|
||||||
|
});
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("async boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValueTask_throws_in_async_body()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask(async () =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
throw new InvalidOperationException("async boom vt");
|
||||||
|
});
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("async boom vt");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Task_FuncT_throws_in_async_body()
|
||||||
|
{
|
||||||
|
var result = await Try.Run<int>(async () =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
throw new InvalidOperationException("async boom");
|
||||||
|
});
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("async boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValueTask_ResultT_throws_in_async_body()
|
||||||
|
{
|
||||||
|
var result = await Try.RunValueTask<int>(async () =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
throw new InvalidOperationException("async boom vt");
|
||||||
|
});
|
||||||
|
result.IsFailure.ShouldBeTrue();
|
||||||
|
result.Error!.Message.ShouldBe("async boom vt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// Edge cases
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class Edge_Cases
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Reference_type_null_success_value_is_preserved()
|
||||||
|
{
|
||||||
|
var result = Try.Run(() => Result.Success<string?>(null));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Large_ValueTuple_is_preserved()
|
||||||
|
{
|
||||||
|
var result = Try.Run(() => (1, 2, 3, 4, 5));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe((1, 2, 3, 4, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Async_large_ValueTuple_is_preserved()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(() => Task.FromResult((1, 2, 3, 4, 5)));
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe((1, 2, 3, 4, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Three_args_all_passed_correctly()
|
||||||
|
{
|
||||||
|
var result = Try.Run(
|
||||||
|
(a, b, c) => $"{a}-{b}-{c}",
|
||||||
|
1, "two", true);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe("1-two-True");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Task_with_three_args()
|
||||||
|
{
|
||||||
|
var result = await Try.Run(
|
||||||
|
(a, b, c) => Task.FromResult($"{a}-{b}-{c}"),
|
||||||
|
1, "two", true);
|
||||||
|
result.IsSuccess.ShouldBeTrue();
|
||||||
|
result.Value.ShouldBe("1-two-True");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user