@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2023-2025 JustFixMe
|
||||
Copyright (c) 2023-2026 JustFixMe
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -88,27 +88,27 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
}
|
||||
""");
|
||||
|
||||
GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
|
||||
if (argCount < Constants.MaxResultTupleSize)
|
||||
{
|
||||
GenerateExpandedMethods(sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateExpandedAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateExpandedAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateExpandedAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateExpandedAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
}
|
||||
|
||||
sb.AppendLine("#endregion");
|
||||
}
|
||||
|
||||
private void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
private void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
{
|
||||
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames);
|
||||
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Append{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<Result> nextFunc)
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Append{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<Result> nextFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
if (result.State == ResultState.Bottom)
|
||||
@@ -137,7 +137,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Append{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func<{{taskType}}<Result>> nextFunc)
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Append{{methodSuffix}}{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func<{{taskType}}<Result>> nextFunc)
|
||||
{
|
||||
if (result.State == ResultState.Bottom)
|
||||
{
|
||||
@@ -165,7 +165,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Append{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<{{taskType}}<Result>> nextFunc)
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Append{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<{{taskType}}<Result>> nextFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
if (result.State == ResultState.Bottom)
|
||||
@@ -192,7 +192,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
""");
|
||||
}
|
||||
|
||||
private void GenerateExpandedAsyncMethods(string taskType, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
private void GenerateExpandedAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
{
|
||||
var expandedTemplateArgNames = templateArgNames.Add("TNext");
|
||||
string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames);
|
||||
@@ -201,7 +201,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, TNext next)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodSuffix}}{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, TNext next)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
@@ -216,7 +216,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Result<TNext> next)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodSuffix}}{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Result<TNext> next)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
if ((result.State & next.State) == ResultState.Bottom)
|
||||
@@ -243,7 +243,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<TNext> nextFunc)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodSuffix}}{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<TNext> nextFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
@@ -258,7 +258,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{resultTypeDef}} result, Func<{{taskType}}<TNext>> nextFunc)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodSuffix}}{{methodExpandedTemplateDecl}}(this {{resultTypeDef}} result, Func<{{taskType}}<TNext>> nextFunc)
|
||||
{
|
||||
return result.State switch
|
||||
{
|
||||
@@ -272,7 +272,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<{{taskType}}<TNext>> nextFunc)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodSuffix}}{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<{{taskType}}<TNext>> nextFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
@@ -288,7 +288,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<Result<TNext>> nextFunc)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodSuffix}}{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<Result<TNext>> nextFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
if (result.State == ResultState.Bottom)
|
||||
@@ -317,7 +317,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{resultTypeDef}} result, Func<{{taskType}}<Result<TNext>>> nextFunc)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodSuffix}}{{methodExpandedTemplateDecl}}(this {{resultTypeDef}} result, Func<{{taskType}}<Result<TNext>>> nextFunc)
|
||||
{
|
||||
if (result.State == ResultState.Bottom)
|
||||
{
|
||||
@@ -345,7 +345,7 @@ internal sealed class ResultAppendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultAppendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<{{taskType}}<Result<TNext>>> nextFunc)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Append{{methodSuffix}}{{methodExpandedTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<{{taskType}}<Result<TNext>>> nextFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
if (result.State == ResultState.Bottom)
|
||||
|
||||
@@ -36,13 +36,13 @@ internal sealed class ResultBindExecutor : ResultExtensionsExecutor
|
||||
}
|
||||
""");
|
||||
|
||||
GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
|
||||
sb.AppendLine("#endregion");
|
||||
}
|
||||
|
||||
private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
{
|
||||
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("R"));
|
||||
string bindTemplateDecl = GenerateTemplateDecl(templateArgNames.Add("Result<R>"));
|
||||
@@ -51,7 +51,7 @@ internal sealed class ResultBindExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultBindExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<Result<R>> Bind{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{bindTemplateDecl}} binding)
|
||||
public static async {{taskType}}<Result<R>> Bind{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{bindTemplateDecl}} binding)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
@@ -66,7 +66,7 @@ internal sealed class ResultBindExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultBindExecutor)}}", "1.0.0.0")]
|
||||
public static {{taskType}}<Result<R>> Bind{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} binding)
|
||||
public static {{taskType}}<Result<R>> Bind{{methodSuffix}}{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} binding)
|
||||
{
|
||||
return result.State switch
|
||||
{
|
||||
@@ -80,7 +80,7 @@ internal sealed class ResultBindExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultBindExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<Result<R>> Bind{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} binding)
|
||||
public static async {{taskType}}<Result<R>> Bind{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} binding)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
|
||||
@@ -57,13 +57,13 @@ internal sealed class ResultExtendExecutor : ResultExtensionsExecutor
|
||||
}
|
||||
""");
|
||||
|
||||
GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
|
||||
sb.AppendLine("#endregion");
|
||||
}
|
||||
|
||||
private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
{
|
||||
var expandedTemplateArgNames = templateArgNames.Add("R");
|
||||
string resultExpandedTypeDef = GenerateResultTypeDef(expandedTemplateArgNames);
|
||||
@@ -74,7 +74,7 @@ internal sealed class ResultExtendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultExtendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Extend{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{bindTemplateDecl}} extensionFunc)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Extend{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{bindTemplateDecl}} extensionFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
if (result.State == ResultState.Bottom)
|
||||
@@ -103,7 +103,7 @@ internal sealed class ResultExtendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultExtendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Extend{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} extensionFunc)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Extend{{methodSuffix}}{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} extensionFunc)
|
||||
{
|
||||
if (result.State == ResultState.Bottom)
|
||||
{
|
||||
@@ -131,7 +131,7 @@ internal sealed class ResultExtendExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultExtendExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Extend{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} extensionFunc)
|
||||
public static async {{taskType}}<{{resultExpandedTypeDef}}> Extend{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} extensionFunc)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
if (result.State == ResultState.Bottom)
|
||||
|
||||
@@ -34,13 +34,13 @@ internal sealed class ResultMapExecutor : ResultExtensionsExecutor
|
||||
}
|
||||
""");
|
||||
|
||||
GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
|
||||
sb.AppendLine("#endregion");
|
||||
}
|
||||
|
||||
private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
{
|
||||
var methodTemplateArgNames = templateArgNames.Add("R");
|
||||
string methodTemplateDecl = GenerateTemplateDecl(methodTemplateArgNames);
|
||||
@@ -49,7 +49,7 @@ internal sealed class ResultMapExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<Result<R>> Map{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{methodTemplateDecl}} mapping)
|
||||
public static async {{taskType}}<Result<R>> Map{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{methodTemplateDecl}} mapping)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
@@ -64,7 +64,7 @@ internal sealed class ResultMapExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<Result<R>> Map{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} mapping)
|
||||
public static async {{taskType}}<Result<R>> Map{{methodSuffix}}{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} mapping)
|
||||
{
|
||||
return result.State switch
|
||||
{
|
||||
@@ -78,7 +78,7 @@ internal sealed class ResultMapExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultMapExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<Result<R>> Map{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} mapping)
|
||||
public static async {{taskType}}<Result<R>> Map{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} mapping)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
|
||||
@@ -35,13 +35,13 @@ internal sealed class ResultMatchExecutor : ResultExtensionsExecutor
|
||||
}
|
||||
""");
|
||||
|
||||
GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
|
||||
sb.AppendLine("#endregion");
|
||||
}
|
||||
|
||||
private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
{
|
||||
var methodTemplateArgNames = templateArgNames.Add("R");
|
||||
string methodTemplateDecl = GenerateTemplateDecl(methodTemplateArgNames);
|
||||
@@ -50,7 +50,7 @@ internal sealed class ResultMatchExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultMatchExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<R> Match{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{methodTemplateDecl}} onSuccess, Func<Error, R> onFailure)
|
||||
public static async {{taskType}}<R> Match{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{methodTemplateDecl}} onSuccess, Func<Error, R> onFailure)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
@@ -65,7 +65,7 @@ internal sealed class ResultMatchExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultMatchExecutor)}}", "1.0.0.0")]
|
||||
public static {{taskType}}<R> Match{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} onSuccess, Func<Error, {{taskType}}<R>> onFailure)
|
||||
public static {{taskType}}<R> Match{{methodSuffix}}{{methodTemplateDecl}}(this in {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}} onSuccess, Func<Error, {{taskType}}<R>> onFailure)
|
||||
{
|
||||
return result.State switch
|
||||
{
|
||||
@@ -79,7 +79,7 @@ internal sealed class ResultMatchExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultMatchExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<R> Match{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} onSuccess, Func<Error, {{taskType}}<R>> onFailure)
|
||||
public static async {{taskType}}<R> Match{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}} onSuccess, Func<Error, {{taskType}}<R>> onFailure)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
var matchTask = result.State switch
|
||||
|
||||
@@ -41,13 +41,13 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor
|
||||
}
|
||||
""");
|
||||
|
||||
GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, resultValueExpansion);
|
||||
|
||||
sb.AppendLine("#endregion");
|
||||
}
|
||||
|
||||
private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string resultValueExpansion)
|
||||
{
|
||||
string methodTemplateDecl = GenerateTemplateDecl(templateArgNames);
|
||||
string asyncActionTemplateDecl = GenerateTemplateDecl(templateArgNames.Add(taskType));
|
||||
@@ -55,7 +55,7 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultTapExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Tap{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Action{{methodTemplateDecl}}? onSuccess = null, Action<Error>? onFailure = null)
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Tap{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Action{{methodTemplateDecl}}? onSuccess = null, Action<Error>? onFailure = null)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
switch (result.State)
|
||||
@@ -76,7 +76,7 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultTapExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Tap{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}}? onSuccess = null, Func<Error, {{taskType}}>? onFailure = null)
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Tap{{methodSuffix}}{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func{{asyncActionTemplateDecl}}? onSuccess = null, Func<Error, {{taskType}}>? onFailure = null)
|
||||
{
|
||||
switch (result.State)
|
||||
{
|
||||
@@ -98,7 +98,7 @@ internal sealed class ResultTapExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultTapExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Tap{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}}? onSuccess = null, Func<Error, {{taskType}}>? onFailure = null)
|
||||
public static async {{taskType}}<{{resultTypeDef}}> Tap{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func{{asyncActionTemplateDecl}}? onSuccess = null, Func<Error, {{taskType}}>? onFailure = null)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
switch (result.State)
|
||||
|
||||
@@ -33,17 +33,17 @@ internal sealed class ResultTryRecoverExecutor : ResultExtensionsExecutor
|
||||
}
|
||||
""");
|
||||
|
||||
GenerateAsyncMethods("Task", sb, templateArgNames, resultTypeDef, methodTemplateDecl);
|
||||
GenerateAsyncMethods("ValueTask", sb, templateArgNames, resultTypeDef, methodTemplateDecl);
|
||||
GenerateAsyncMethods("Task", string.Empty, sb, templateArgNames, resultTypeDef, methodTemplateDecl);
|
||||
GenerateAsyncMethods("ValueTask", "ValueTask", sb, templateArgNames, resultTypeDef, methodTemplateDecl);
|
||||
|
||||
sb.AppendLine("#endregion");
|
||||
}
|
||||
private static void GenerateAsyncMethods(string taskType, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string methodTemplateDecl)
|
||||
private static void GenerateAsyncMethods(string taskType, string methodSuffix, StringBuilder sb, ImmutableArray<string> templateArgNames, string resultTypeDef, string methodTemplateDecl)
|
||||
{
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultTryRecoverExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultTypeDef}}> TryRecover{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<Error, {{resultTypeDef}}> recover)
|
||||
public static async {{taskType}}<{{resultTypeDef}}> TryRecover{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<Error, {{resultTypeDef}}> recover)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
@@ -57,7 +57,7 @@ internal sealed class ResultTryRecoverExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultTryRecoverExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultTypeDef}}> TryRecover{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func<Error, {{taskType}}<{{resultTypeDef}}>> recover)
|
||||
public static async {{taskType}}<{{resultTypeDef}}> TryRecover{{methodSuffix}}{{methodTemplateDecl}}(this {{resultTypeDef}} result, Func<Error, {{taskType}}<{{resultTypeDef}}>> recover)
|
||||
{
|
||||
return result.State switch
|
||||
{
|
||||
@@ -70,7 +70,7 @@ internal sealed class ResultTryRecoverExecutor : ResultExtensionsExecutor
|
||||
sb.AppendLine($$"""
|
||||
[PureAttribute]
|
||||
[GeneratedCodeAttribute("{{nameof(ResultTryRecoverExecutor)}}", "1.0.0.0")]
|
||||
public static async {{taskType}}<{{resultTypeDef}}> TryRecover{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<Error, {{taskType}}<{{resultTypeDef}}>> recover)
|
||||
public static async {{taskType}}<{{resultTypeDef}}> TryRecover{{methodSuffix}}{{methodTemplateDecl}}(this {{taskType}}<{{resultTypeDef}}> resultTask, Func<Error, {{taskType}}<{{resultTypeDef}}>> recover)
|
||||
{
|
||||
var result = await resultTask.ConfigureAwait(false);
|
||||
return result.State switch
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace Just.Railway;
|
||||
|
||||
public static partial class Ensure
|
||||
{
|
||||
[return: NotNull]
|
||||
public delegate Error ErrorFactory(string valueExpression);
|
||||
|
||||
public const string DefaultErrorType = "EnsureFailed";
|
||||
|
||||
+13
-7
@@ -14,7 +14,13 @@ public abstract class Error : IEquatable<Error>, IComparable<Error>
|
||||
/// </summary>
|
||||
/// <param name="thisException">Exception</param>
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Error New(Exception thisException) => new ExceptionalError(thisException);
|
||||
public static Error New(Exception thisException) => thisException switch
|
||||
{
|
||||
AggregateException { InnerExceptions.Count: > 1 } aggegateException => new ManyErrors(aggegateException.InnerExceptions.Select(x => Error.New(x))),
|
||||
AggregateException { InnerExceptions.Count: 1 } aggegateException => new ExceptionalError(aggegateException.InnerExceptions[0]),
|
||||
_ => new ExceptionalError(thisException),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ExceptionalError"/> with an overriden detail. This can be useful for sanitising the display message
|
||||
/// when internally we're carrying the exception.
|
||||
@@ -85,7 +91,7 @@ public abstract class Error : IEquatable<Error>, IComparable<Error>
|
||||
[Pure] public abstract int Count { get; }
|
||||
[Pure] public abstract bool IsEmpty { get; }
|
||||
[Pure] public abstract bool IsExpected { get; }
|
||||
[Pure] public abstract bool IsExeptional { get; }
|
||||
[Pure] public abstract bool IsExceptional { get; }
|
||||
|
||||
[Pure] public Error Append(Error? next)
|
||||
{
|
||||
@@ -166,7 +172,7 @@ public sealed class ExpectedError : Error
|
||||
[Pure] public override int Count => 1;
|
||||
[Pure] public override bool IsEmpty => false;
|
||||
[Pure] public override bool IsExpected => true;
|
||||
[Pure] public override bool IsExeptional => false;
|
||||
[Pure] public override bool IsExceptional => false;
|
||||
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override IEnumerable<Error> ToEnumerable()
|
||||
@@ -208,7 +214,7 @@ public sealed class ExceptionalError : Error
|
||||
[Pure] public override int Count => 1;
|
||||
[Pure] public override bool IsEmpty => false;
|
||||
[Pure] public override bool IsExpected => false;
|
||||
[Pure] public override bool IsExeptional => true;
|
||||
[Pure] public override bool IsExceptional => true;
|
||||
|
||||
[Pure] public override Exception ToException() => Exception ?? base.ToException();
|
||||
|
||||
@@ -220,7 +226,7 @@ public sealed class ExceptionalError : Error
|
||||
|
||||
private static ImmutableDictionary<string, string> ExtractExtensionData(Exception exception)
|
||||
{
|
||||
if (!(exception.Data?.Count > 0))
|
||||
if (exception.Data is null || exception.Data.Count == 0)
|
||||
return ImmutableDictionary<string, string>.Empty;
|
||||
|
||||
var values = GetGenericExtData(exception);
|
||||
@@ -276,7 +282,7 @@ public sealed class ManyErrors : Error, IEnumerable<Error>, IReadOnlyList<Error>
|
||||
{
|
||||
var unpackedErrors = ImmutableArray.CreateBuilder<Error>();
|
||||
|
||||
foreach (var err in errors)
|
||||
foreach (var err in errors.Where(x => x is not null))
|
||||
{
|
||||
if (err.IsEmpty) continue;
|
||||
|
||||
@@ -309,7 +315,7 @@ public sealed class ManyErrors : Error, IEnumerable<Error>, IReadOnlyList<Error>
|
||||
[Pure] public override int Count => _errors.Length;
|
||||
[Pure] public override bool IsEmpty => _errors.IsEmpty;
|
||||
[Pure] public override bool IsExpected => _errors.All(static x => x.IsExpected);
|
||||
[Pure] public override bool IsExeptional => _errors.Any(static x => x.IsExeptional);
|
||||
[Pure] public override bool IsExceptional => _errors.Any(static x => x.IsExceptional);
|
||||
|
||||
[Pure] public Error this[int index] => _errors[index];
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ public sealed class ErrorJsonConverter : JsonConverter<Error>
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.StartObject => ToExpectedError(ReadOne(ref reader)),
|
||||
JsonTokenType.StartObject => ToExpectedError(ReadOne(ref reader)), // deserialization always produce ExpectedError by design
|
||||
JsonTokenType.StartArray => ReadMany(ref reader),
|
||||
JsonTokenType.None => null,
|
||||
JsonTokenType.Null => null,
|
||||
@@ -42,6 +42,7 @@ public sealed class ErrorJsonConverter : JsonConverter<Error>
|
||||
{
|
||||
errors.Add(ToExpectedError(ReadOne(ref reader)));
|
||||
}
|
||||
else if (reader.TokenType == JsonTokenType.EndArray) break;
|
||||
}
|
||||
return new ManyErrors(errors.ToImmutable());
|
||||
}
|
||||
@@ -73,11 +74,11 @@ public sealed class ErrorJsonConverter : JsonConverter<Error>
|
||||
if (string.IsNullOrEmpty(propvalue))
|
||||
break;
|
||||
|
||||
if (propname == "type" || string.Equals(propname, "type", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (string.Equals(propname, "type", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
type = propvalue;
|
||||
}
|
||||
else if (propname == "msg" || string.Equals(propname, "msg", StringComparison.InvariantCultureIgnoreCase))
|
||||
else if (string.Equals(propname, "msg", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
message = propvalue;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<Description>Base for railway-oriented programming in .NET. Package includes Result object, Error class and most of the common extensions.</Description>
|
||||
<PackageTags>railway-oriented;functional;result-pattern;result-object;error-handling</PackageTags>
|
||||
<Authors>JustFixMe</Authors>
|
||||
<Copyright>Copyright (c) 2023-2025 JustFixMe</Copyright>
|
||||
<Copyright>Copyright (c) 2023-2026 JustFixMe</Copyright>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/JustFixMe/Just.Railway/</RepositoryUrl>
|
||||
|
||||
+4
-4
@@ -64,7 +64,7 @@ public readonly partial struct Result : IEquatable<Result>
|
||||
public static implicit operator Result(Error error) => new(error ?? throw new ArgumentNullException(nameof(error)));
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator Result(Exception exception) => new(
|
||||
new ExceptionalError(exception ?? throw new ArgumentNullException(nameof(exception))));
|
||||
Error.New(exception ?? throw new ArgumentNullException(nameof(exception))));
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator Result<SuccessUnit>(Result result) => result.State switch
|
||||
{
|
||||
@@ -75,8 +75,8 @@ public readonly partial struct Result : IEquatable<Result>
|
||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static explicit operator Result(SuccessUnit _) => new(null);
|
||||
|
||||
[Pure] public bool IsSuccess => Error is null;
|
||||
[Pure] public bool IsFailure => Error is not null;
|
||||
[Pure] public bool IsSuccess => State == ResultState.Success;
|
||||
[Pure] public bool IsFailure => State == ResultState.Error;
|
||||
|
||||
[Pure] public bool TryGetValue([MaybeNullWhen(false)]out SuccessUnit? u, [MaybeNullWhen(true), NotNullWhen(false)]out Error? error)
|
||||
{
|
||||
@@ -236,7 +236,7 @@ public readonly struct Result<T> : IEquatable<Result<T>>
|
||||
if (typeof(R).IsAssignableFrom(typeof(T)) && Value is null)
|
||||
return default(R)!;
|
||||
|
||||
return (R)(object)Value!;
|
||||
return (R)(object)Value!; // throws InvalidCastException by design. should not use cast on incompatible types
|
||||
}
|
||||
|
||||
default: throw new ResultNotInitializedException();
|
||||
|
||||
@@ -98,7 +98,7 @@ public static partial class ResultExtensions
|
||||
case ResultState.Success:
|
||||
if (hasErrors) goto afterLoop;
|
||||
break;
|
||||
|
||||
|
||||
default: throw new ResultNotInitializedException(nameof(results));
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,19 @@ public static partial class ResultExtensions
|
||||
}
|
||||
public static async Task<Result<IEnumerable<T>>> Merge<T>(this IEnumerable<Task<Result<T>>> tasks)
|
||||
{
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
var taskList = tasks.ToList();
|
||||
var results = new Result<T>[taskList.Count];
|
||||
|
||||
for (int i = 0; i < taskList.Count; i++)
|
||||
{
|
||||
var task = await Task.WhenAny(taskList);
|
||||
taskList.Remove(task);
|
||||
|
||||
results[i] = task.IsCompletedSuccessfully
|
||||
? task.Result
|
||||
: task.Exception!;
|
||||
}
|
||||
|
||||
return results.Merge();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ public class Satisfy
|
||||
.LessThan(100)
|
||||
.Result();
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal(69, result.Value);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe(69);
|
||||
}
|
||||
[Fact]
|
||||
public void WhenRequirementWasNotSatisfied_ShouldBeFailureWithDefaultError()
|
||||
@@ -22,8 +22,8 @@ public class Satisfy
|
||||
.GreaterThan(100)
|
||||
.Result();
|
||||
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(error, result.Error);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -37,8 +37,8 @@ public class Satisfy
|
||||
.EqualTo("69")
|
||||
.Result();
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal("69", result.Value);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe("69");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -53,7 +53,7 @@ public class Satisfy
|
||||
.EqualTo("69")
|
||||
.Result();
|
||||
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(error, result.Error);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,572 @@
|
||||
namespace Raliway.Tests.Errors;
|
||||
|
||||
public class Construction
|
||||
{
|
||||
public class New_Factory
|
||||
{
|
||||
[Fact]
|
||||
public void With_string_creates_ExpectedError_with_default_type()
|
||||
{
|
||||
// Given
|
||||
// When
|
||||
var error = Error.New("something went wrong");
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ExpectedError>();
|
||||
error.Type.ShouldBe("error");
|
||||
error.Message.ShouldBe("something went wrong");
|
||||
error.IsExpected.ShouldBeTrue();
|
||||
error.IsExceptional.ShouldBeFalse();
|
||||
error.IsEmpty.ShouldBeFalse();
|
||||
error.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_type_and_message_creates_ExpectedError_with_custom_type()
|
||||
{
|
||||
// Given
|
||||
// When
|
||||
var error = Error.New("validation_error", "Name is required");
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ExpectedError>();
|
||||
error.Type.ShouldBe("validation_error");
|
||||
error.Message.ShouldBe("Name is required");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_Exception_creates_ExceptionalError()
|
||||
{
|
||||
// Given
|
||||
var exception = new InvalidOperationException("bad state");
|
||||
|
||||
// When
|
||||
var error = Error.New(exception);
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ExceptionalError>();
|
||||
error.Type.ShouldBe("System.InvalidOperationException");
|
||||
error.Message.ShouldBe("bad state");
|
||||
error.IsExpected.ShouldBeFalse();
|
||||
error.IsExceptional.ShouldBeTrue();
|
||||
error.IsEmpty.ShouldBeFalse();
|
||||
error.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_message_and_Exception_overrides_message_preserves_exception()
|
||||
{
|
||||
// Given
|
||||
var exception = new InvalidOperationException("internal detail");
|
||||
|
||||
// When
|
||||
var error = Error.New("sanitized message", exception);
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ExceptionalError>();
|
||||
error.Type.ShouldBe("System.InvalidOperationException");
|
||||
error.Message.ShouldBe("sanitized message");
|
||||
error.IsExceptional.ShouldBeTrue();
|
||||
var ex = error.ToException();
|
||||
ex.ShouldBe(exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_AggregateException_many_inners_creates_ManyErrors()
|
||||
{
|
||||
// Given
|
||||
var inner1 = new InvalidOperationException("first");
|
||||
var inner2 = new ArgumentException("second");
|
||||
var aggregate = new AggregateException(inner1, inner2);
|
||||
|
||||
// When
|
||||
var error = Error.New(aggregate);
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ManyErrors>();
|
||||
var many = (ManyErrors)error;
|
||||
many.Count.ShouldBe(2);
|
||||
many[0].Type.ShouldBe("System.InvalidOperationException");
|
||||
many[0].Message.ShouldBe("first");
|
||||
many[1].Type.ShouldBe("System.ArgumentException");
|
||||
many[1].Message.ShouldBe("second");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_AggregateException_single_inner_creates_ExceptionalError()
|
||||
{
|
||||
// Given
|
||||
var inner = new InvalidOperationException("only one");
|
||||
var aggregate = new AggregateException(inner);
|
||||
|
||||
// When
|
||||
var error = Error.New(aggregate);
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ExceptionalError>();
|
||||
error.Type.ShouldBe("System.InvalidOperationException");
|
||||
error.Message.ShouldBe("only one");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_AggregateException_zero_inners_creates_ExceptionalError()
|
||||
{
|
||||
// Given
|
||||
var aggregate = new AggregateException();
|
||||
|
||||
// When
|
||||
var error = Error.New(aggregate);
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ExceptionalError>();
|
||||
error.Type.ShouldBe("System.AggregateException");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_nested_AggregateException_recursively_unwraps()
|
||||
{
|
||||
// Given
|
||||
var inner1 = new InvalidOperationException("first");
|
||||
var inner2 = new ArgumentException("second");
|
||||
var nestedAggregate = new AggregateException(inner1, inner2);
|
||||
// Outer must have >1 inners to trigger ManyErrors path; one of them is itself an AggregateException
|
||||
var outerAggregate = new AggregateException(nestedAggregate, new InvalidOperationException("extra"));
|
||||
|
||||
// When
|
||||
var error = Error.New(outerAggregate);
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ManyErrors>();
|
||||
var many = (ManyErrors)error;
|
||||
many.Count.ShouldBe(3); // nested 2-flattened + extra
|
||||
}
|
||||
}
|
||||
|
||||
public class Many_Factory
|
||||
{
|
||||
[Fact]
|
||||
public void With_both_null_returns_empty_ManyErrors()
|
||||
{
|
||||
// Given
|
||||
// When
|
||||
var error = Error.Many(null!, null!);
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ManyErrors>();
|
||||
error.IsEmpty.ShouldBeTrue();
|
||||
error.Count.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_error_and_null_returns_the_error()
|
||||
{
|
||||
// Given
|
||||
var expected = Error.New("test");
|
||||
|
||||
// When
|
||||
var result = Error.Many(expected, null!);
|
||||
|
||||
// Then
|
||||
result.ShouldBe(expected);
|
||||
result.ShouldBeOfType<ExpectedError>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_null_and_error_returns_the_error()
|
||||
{
|
||||
// Given
|
||||
var expected = Error.New("test");
|
||||
|
||||
// When
|
||||
var result = Error.Many(null!, expected);
|
||||
|
||||
// Then
|
||||
result.ShouldBe(expected);
|
||||
result.ShouldBeOfType<ExpectedError>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Params_with_null_element_should_not_throw()
|
||||
{
|
||||
// Given
|
||||
Error?[] errors = [Error.New("first"), null!, Error.New("second")];
|
||||
|
||||
// When
|
||||
var exception = Record.Exception(() => Error.Many(errors!));
|
||||
|
||||
// Then
|
||||
exception.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Params_with_null_element_filters_null()
|
||||
{
|
||||
// Given
|
||||
Error?[] errors = [Error.New("first"), null!, Error.New("second")];
|
||||
|
||||
// When
|
||||
var result = Error.Many(errors!);
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ManyErrors>();
|
||||
var many = (ManyErrors)result;
|
||||
many.Count.ShouldBe(2);
|
||||
many[0].Message.ShouldBe("first");
|
||||
many[1].Message.ShouldBe("second");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Params_with_empty_array_returns_empty_ManyErrors()
|
||||
{
|
||||
// Given
|
||||
// When
|
||||
var result = Error.Many(Array.Empty<Error>());
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ManyErrors>();
|
||||
result.IsEmpty.ShouldBeTrue();
|
||||
result.Count.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_single_error_returns_it_unwrapped()
|
||||
{
|
||||
// Given
|
||||
var single = Error.New("only one");
|
||||
|
||||
// When
|
||||
var result = Error.Many(single);
|
||||
|
||||
// Then
|
||||
result.ShouldBe(single);
|
||||
result.ShouldBeOfType<ExpectedError>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_ManyErrors_and_error_flattens()
|
||||
{
|
||||
// Given
|
||||
var first = Error.New("first");
|
||||
var second = Error.New("second");
|
||||
var many = Error.Many(first, second);
|
||||
var third = Error.New("third");
|
||||
|
||||
// When
|
||||
var result = Error.Many(many, third);
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ManyErrors>();
|
||||
var resultMany = (ManyErrors)result;
|
||||
resultMany.Count.ShouldBe(3);
|
||||
resultMany[0].Message.ShouldBe("first");
|
||||
resultMany[1].Message.ShouldBe("second");
|
||||
resultMany[2].Message.ShouldBe("third");
|
||||
}
|
||||
}
|
||||
|
||||
public class Operator_Null_Safety
|
||||
{
|
||||
[Fact]
|
||||
public void Error_plus_null_returns_original_error()
|
||||
{
|
||||
// Given
|
||||
var error = Error.New("test");
|
||||
|
||||
// When
|
||||
var result = error + null;
|
||||
|
||||
// Then
|
||||
result.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Null_plus_error_returns_the_error()
|
||||
{
|
||||
// Given
|
||||
var error = Error.New("test");
|
||||
|
||||
// When
|
||||
var result = null + error;
|
||||
|
||||
// Then
|
||||
result.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Null_plus_null_is_null()
|
||||
{
|
||||
// Given
|
||||
// When
|
||||
var result = (Error?)null + (Error?)null;
|
||||
|
||||
// Then
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
public class Append_Edge_Cases
|
||||
{
|
||||
[Fact]
|
||||
public void Append_null_returns_original_error()
|
||||
{
|
||||
// Given
|
||||
var error = Error.New("test");
|
||||
|
||||
// When
|
||||
var result = error.Append(null);
|
||||
|
||||
// Then
|
||||
result.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Append_empty_ManyErrors_returns_original_error()
|
||||
{
|
||||
// Given
|
||||
var error = Error.New("test");
|
||||
var empty = Error.Many(Array.Empty<Error>());
|
||||
|
||||
// When
|
||||
var result = error.Append(empty);
|
||||
|
||||
// Then
|
||||
result.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_append_error_returns_the_appended_error()
|
||||
{
|
||||
// Given
|
||||
var empty = Error.Many(Array.Empty<Error>());
|
||||
var error = Error.New("test");
|
||||
|
||||
// When
|
||||
var result = empty.Append(error);
|
||||
|
||||
// Then
|
||||
result.ShouldBe(error);
|
||||
}
|
||||
}
|
||||
|
||||
public class Property_Correctness
|
||||
{
|
||||
[Fact]
|
||||
public void ExpectedError_IsEmpty_is_false()
|
||||
{
|
||||
var error = Error.New("test");
|
||||
error.IsEmpty.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionalError_IsEmpty_is_false()
|
||||
{
|
||||
var error = Error.New(new Exception("test"));
|
||||
error.IsEmpty.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_IsEmpty_when_empty()
|
||||
{
|
||||
var error = Error.Many(Array.Empty<Error>());
|
||||
error.IsEmpty.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_IsExpected_when_all_are_expected()
|
||||
{
|
||||
// Given
|
||||
var errors = Error.Many(
|
||||
Error.New("err1"),
|
||||
Error.New("err2"));
|
||||
|
||||
// When / Then
|
||||
errors.IsExpected.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_IsExpected_when_mixed_is_false()
|
||||
{
|
||||
// Given
|
||||
var errors = Error.Many(
|
||||
Error.New("err1"),
|
||||
Error.New(new InvalidOperationException("boom")));
|
||||
|
||||
// When / Then
|
||||
errors.IsExpected.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_IsExceptional_when_any_is_exceptional()
|
||||
{
|
||||
// Given
|
||||
var errors = Error.Many(
|
||||
Error.New("err1"),
|
||||
Error.New(new InvalidOperationException("boom")));
|
||||
|
||||
// When / Then
|
||||
errors.IsExceptional.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_IsExceptional_when_none_is_false()
|
||||
{
|
||||
// Given
|
||||
var errors = Error.Many(
|
||||
Error.New("err1"),
|
||||
Error.New("err2"));
|
||||
|
||||
// When / Then
|
||||
errors.IsExceptional.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
public class ToException
|
||||
{
|
||||
[Fact]
|
||||
public void ExceptionalError_ToException_returns_original_exception()
|
||||
{
|
||||
// Given
|
||||
var original = new InvalidOperationException("boom");
|
||||
var error = Error.New(original);
|
||||
|
||||
// When
|
||||
var result = error.ToException();
|
||||
|
||||
// Then
|
||||
result.ShouldBe(original);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionalError_without_exception_falls_back_to_ErrorException()
|
||||
{
|
||||
// Given
|
||||
// Use deserialization path — creates ExceptionalError without internal Exception
|
||||
var json = """{"type":"System.Exception","msg":"deserialized error"}""";
|
||||
var error = JsonSerializer.Deserialize<ExceptionalError>(json)!;
|
||||
|
||||
// When
|
||||
var result = error.ToException();
|
||||
|
||||
// Then
|
||||
result.ShouldNotBeOfType<InvalidOperationException>();
|
||||
result.ShouldBeOfType<ErrorException>();
|
||||
var errEx = (ErrorException)result;
|
||||
errEx.Type.ShouldBe("System.Exception");
|
||||
errEx.Message.ShouldBe("deserialized error");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_ToException_returns_AggregateException()
|
||||
{
|
||||
// Given
|
||||
var errors = Error.Many(
|
||||
Error.New("first"),
|
||||
Error.New("second"));
|
||||
|
||||
// When
|
||||
var result = errors.ToException();
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<AggregateException>();
|
||||
var aggregate = (AggregateException)result;
|
||||
aggregate.InnerExceptions.Count.ShouldBe(2);
|
||||
aggregate.InnerExceptions[0].Message.ShouldBe("first");
|
||||
aggregate.InnerExceptions[1].Message.ShouldBe("second");
|
||||
}
|
||||
}
|
||||
|
||||
public class ExtensionData
|
||||
{
|
||||
[Fact]
|
||||
public void Extracted_from_Exception_Data()
|
||||
{
|
||||
// Given
|
||||
var exception = new InvalidOperationException("boom");
|
||||
exception.Data["key1"] = "value1";
|
||||
exception.Data["key2"] = 42; // int value → ToString → "42"
|
||||
|
||||
// When
|
||||
var error = Error.New(exception);
|
||||
|
||||
// Then
|
||||
error.ExtensionData["key1"].ShouldBe("value1");
|
||||
error.ExtensionData["key2"].ShouldBe("42");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Exception_Data_with_null_value_is_skipped()
|
||||
{
|
||||
// Given
|
||||
var exception = new InvalidOperationException("boom");
|
||||
exception.Data["key"] = null;
|
||||
|
||||
// When
|
||||
var error = Error.New(exception);
|
||||
|
||||
// Then
|
||||
error.ExtensionData.Count.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Exception_without_Data_has_empty_ExtensionData()
|
||||
{
|
||||
// Given
|
||||
var exception = new InvalidOperationException("boom");
|
||||
|
||||
// When
|
||||
var error = Error.New(exception);
|
||||
|
||||
// Then
|
||||
error.ExtensionData.ShouldNotBeNull();
|
||||
error.ExtensionData.IsEmpty.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
public class Indexer
|
||||
{
|
||||
[Fact]
|
||||
public void Existing_key_returns_value()
|
||||
{
|
||||
// Given
|
||||
var exception = new InvalidOperationException("boom");
|
||||
exception.Data["mykey"] = "myvalue";
|
||||
var error = Error.New(exception);
|
||||
|
||||
// When
|
||||
var value = error["mykey"];
|
||||
|
||||
// Then
|
||||
value.ShouldBe("myvalue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Missing_key_returns_null()
|
||||
{
|
||||
// Given
|
||||
var error = Error.New("test");
|
||||
|
||||
// When
|
||||
var value = error["nonexistent"];
|
||||
|
||||
// Then
|
||||
value.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
public class Deconstruct
|
||||
{
|
||||
[Fact]
|
||||
public void Produces_Type_and_Message()
|
||||
{
|
||||
// Given
|
||||
var error = Error.New("custom_type", "custom message");
|
||||
|
||||
// When
|
||||
var (type, message) = error;
|
||||
|
||||
// Then
|
||||
type.ShouldBe("custom_type");
|
||||
message.ShouldBe("custom message");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
namespace Raliway.Tests.Errors;
|
||||
|
||||
public class Equality
|
||||
{
|
||||
public class Equals_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Identical_ExpectedErrors_are_equal()
|
||||
{
|
||||
var a = Error.New("custom_type", "some message");
|
||||
var b = Error.New("custom_type", "some message");
|
||||
|
||||
a.Equals(b).ShouldBeTrue();
|
||||
b.Equals(a).ShouldBeTrue();
|
||||
(a == b).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Different_type_ExpectedErrors_are_not_equal()
|
||||
{
|
||||
var a = Error.New("type_a", "same message");
|
||||
var b = Error.New("type_b", "same message");
|
||||
|
||||
a.Equals(b).ShouldBeFalse();
|
||||
(a == b).ShouldBeFalse();
|
||||
(a != b).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Different_message_ExpectedErrors_are_not_equal()
|
||||
{
|
||||
var a = Error.New("same_type", "message a");
|
||||
var b = Error.New("same_type", "message b");
|
||||
|
||||
a.Equals(b).ShouldBeFalse();
|
||||
(a == b).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Same_Type_and_Message_but_different_ExtensionData_are_still_equal()
|
||||
{
|
||||
var a = Error.New("type", "msg", new Dictionary<string, string> { ["key"] = "a" });
|
||||
var b = Error.New("type", "msg", new Dictionary<string, string> { ["key"] = "b" });
|
||||
|
||||
a.Equals(b).ShouldBeTrue();
|
||||
(a == b).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpectedError_and_ExceptionalError_with_same_Type_and_Message_are_equal()
|
||||
{
|
||||
Error expected = Error.New("shared_type", "shared message");
|
||||
Error exceptional = new ExceptionalError("shared_type", "shared message");
|
||||
|
||||
expected.Equals(exceptional).ShouldBeTrue();
|
||||
exceptional.Equals(expected).ShouldBeTrue();
|
||||
(expected == exceptional).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_with_same_elements_same_order_are_equal()
|
||||
{
|
||||
var a = Error.Many(Error.New("a"), Error.New("b"), Error.New("c"));
|
||||
var b = Error.Many(Error.New("a"), Error.New("b"), Error.New("c"));
|
||||
|
||||
a.Equals(b).ShouldBeTrue();
|
||||
(a == b).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_with_same_elements_different_order_are_not_equal()
|
||||
{
|
||||
var a = Error.Many(Error.New("a"), Error.New("b"));
|
||||
var b = Error.Many(Error.New("b"), Error.New("a"));
|
||||
|
||||
a.Equals(b).ShouldBeFalse();
|
||||
(a == b).ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
public class CompareTo_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void CompareTo_null_returns_minus_one()
|
||||
{
|
||||
var error = Error.New("type", "msg");
|
||||
|
||||
error.CompareTo(null).ShouldBe(-1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTo_same_returns_zero()
|
||||
{
|
||||
var a = Error.New("type", "msg");
|
||||
var b = Error.New("type", "msg");
|
||||
|
||||
a.CompareTo(b).ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTo_different_type_compares_by_type()
|
||||
{
|
||||
var a = Error.New("aaa", "msg");
|
||||
var b = Error.New("bbb", "msg");
|
||||
|
||||
a.CompareTo(b).ShouldBeLessThan(0);
|
||||
b.CompareTo(a).ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTo_same_type_different_message_compares_by_message()
|
||||
{
|
||||
var a = Error.New("type", "aaa");
|
||||
var b = Error.New("type", "bbb");
|
||||
|
||||
a.CompareTo(b).ShouldBeLessThan(0);
|
||||
b.CompareTo(a).ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_CompareTo_different_length_compares_by_count()
|
||||
{
|
||||
var shorter = Error.Many(Error.New("a"), Error.New("b"));
|
||||
var longer = Error.Many(Error.New("a"), Error.New("b"), Error.New("c"));
|
||||
|
||||
shorter.CompareTo(longer).ShouldBeLessThan(0);
|
||||
longer.CompareTo(shorter).ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_CompareTo_same_length_element_by_element()
|
||||
{
|
||||
var a = Error.Many(Error.New("a"), Error.New("b"));
|
||||
var b = Error.Many(Error.New("a"), Error.New("c")); // differs at position 1
|
||||
|
||||
a.CompareTo(b).ShouldBeLessThan(0);
|
||||
b.CompareTo(a).ShouldBeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public class IsSimilarTo_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Same_Type_IsSimilarTo_returns_true_ignoring_Message()
|
||||
{
|
||||
var a = Error.New("custom_type", "message one");
|
||||
var b = Error.New("custom_type", "message two");
|
||||
|
||||
a.IsSimilarTo(b).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Different_Type_IsSimilarTo_returns_false()
|
||||
{
|
||||
var a = Error.New("type_a", "same message");
|
||||
var b = Error.New("type_b", "same message");
|
||||
|
||||
a.IsSimilarTo(b).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsSimilarTo_null_returns_false()
|
||||
{
|
||||
var error = Error.New("type", "msg");
|
||||
|
||||
error.IsSimilarTo(null!).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_IsSimilarTo_different_count_returns_false()
|
||||
{
|
||||
var a = Error.Many(Error.New("a"), Error.New("b"));
|
||||
var b = Error.Many(Error.New("a"), Error.New("b"), Error.New("c"));
|
||||
|
||||
a.IsSimilarTo(b).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_IsSimilarTo_same_types_different_messages_returns_true()
|
||||
{
|
||||
var a = Error.Many(Error.New("type", "msg1"));
|
||||
var b = Error.Many(Error.New("type", "msg2"));
|
||||
|
||||
a.IsSimilarTo(b).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ManyErrors_IsSimilarTo_different_type_at_same_position_returns_false()
|
||||
{
|
||||
var a = Error.Many(Error.New("type_a", "msg"), Error.New("type_b", "msg"));
|
||||
var b = Error.Many(Error.New("type_a", "msg"), Error.New("type_c", "msg"));
|
||||
|
||||
a.IsSimilarTo(b).ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
public class GetHashCode_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Equal_errors_have_same_hashcode()
|
||||
{
|
||||
var a = Error.New("type", "msg");
|
||||
var b = Error.New("type", "msg");
|
||||
|
||||
a.GetHashCode().ShouldBe(b.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equal_ManyErrors_have_same_hashcode()
|
||||
{
|
||||
var a = Error.Many(Error.New("a"), Error.New("b"));
|
||||
var b = Error.Many(Error.New("a"), Error.New("b"));
|
||||
|
||||
a.GetHashCode().ShouldBe(b.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_ManyErrors_hashcode_is_zero()
|
||||
{
|
||||
var empty = Error.Many(Array.Empty<Error>());
|
||||
|
||||
empty.GetHashCode().ShouldBe(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Railway.Tests.Errors;
|
||||
namespace Raliway.Tests.Errors;
|
||||
|
||||
public class Serialization
|
||||
{
|
||||
@@ -8,18 +8,17 @@ public class Serialization
|
||||
// Given
|
||||
Error many_errors = new ManyErrors(
|
||||
[
|
||||
Error.New("err1", "msg1", new KeyValuePair<string, string>[]
|
||||
{
|
||||
Error.New("err1", "msg1",
|
||||
[
|
||||
new("ext", "ext_value"),
|
||||
}),
|
||||
]),
|
||||
Error.New(new Exception("msg2")),
|
||||
]);
|
||||
// When
|
||||
var result = JsonSerializer.Serialize(many_errors);
|
||||
// Then
|
||||
Assert.Equal(
|
||||
expected: "[{\"type\":\"err1\",\"msg\":\"msg1\",\"ext\":\"ext_value\"},{\"type\":\"System.Exception\",\"msg\":\"msg2\"}]",
|
||||
result);
|
||||
result.ShouldBe(
|
||||
"[{\"type\":\"err1\",\"msg\":\"msg1\",\"ext\":\"ext_value\"},{\"type\":\"System.Exception\",\"msg\":\"msg2\"}]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -30,23 +29,17 @@ public class Serialization
|
||||
// When
|
||||
var result = JsonSerializer.Deserialize<Error>(json);
|
||||
// Then
|
||||
Assert.IsType<ManyErrors>(result);
|
||||
result.ShouldBeOfType<ManyErrors>();
|
||||
ManyErrors manyErrors = (ManyErrors)result;
|
||||
|
||||
Assert.True(manyErrors.Count == 2);
|
||||
Assert.Equal(
|
||||
expected: Error.Many(
|
||||
manyErrors.Count.ShouldBe(2);
|
||||
manyErrors.ShouldBe(
|
||||
Error.Many(
|
||||
Error.New("err1", "msg1"),
|
||||
Error.New(new Exception("msg2"))
|
||||
).ToEnumerable(),
|
||||
manyErrors
|
||||
);
|
||||
Assert.Equal(
|
||||
expected: "ext_value1",
|
||||
manyErrors[0]["ext1"]);
|
||||
Assert.Equal(
|
||||
expected: "ext_value2",
|
||||
manyErrors[0]["ext2"]);
|
||||
).ToEnumerable());
|
||||
manyErrors[0]["ext1"].ShouldBe("ext_value1");
|
||||
manyErrors[0]["ext2"].ShouldBe("ext_value2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -57,20 +50,14 @@ public class Serialization
|
||||
// When
|
||||
var result = JsonSerializer.Deserialize<ManyErrors>(json);
|
||||
// Then
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.Count == 2);
|
||||
Assert.Equal(
|
||||
expected: Error.Many(
|
||||
result.ShouldNotBeNull();
|
||||
result.Count.ShouldBe(2);
|
||||
result.ShouldBe(
|
||||
Error.Many(
|
||||
Error.New("err1", "msg1"),
|
||||
Error.New(new Exception("msg2"))
|
||||
).ToEnumerable(),
|
||||
result
|
||||
);
|
||||
Assert.Equal(
|
||||
expected: "ext_value1",
|
||||
result[0]["ext1"]);
|
||||
Assert.Equal(
|
||||
expected: "ext_value2",
|
||||
result[0]["ext2"]);
|
||||
).ToEnumerable());
|
||||
result[0]["ext1"].ShouldBe("ext_value1");
|
||||
result[0]["ext2"].ShouldBe("ext_value2");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
namespace Raliway.Tests.Errors;
|
||||
|
||||
public class Serialization_RoundTrip
|
||||
{
|
||||
public class ExpectedError_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Round_trip_as_Error_preserves_Type_and_Message()
|
||||
{
|
||||
// Given
|
||||
Error original = Error.New("custom_type", "custom message");
|
||||
|
||||
// When
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
var deserialized = JsonSerializer.Deserialize<Error>(json);
|
||||
|
||||
// Then
|
||||
deserialized.ShouldBeOfType<ExpectedError>();
|
||||
deserialized!.Type.ShouldBe("custom_type");
|
||||
deserialized.Message.ShouldBe("custom message");
|
||||
deserialized.IsExpected.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Round_trip_as_ExpectedError_preserves_Type_and_Message()
|
||||
{
|
||||
// Given
|
||||
var original = new ExpectedError("custom_type", "custom message");
|
||||
|
||||
// When
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
var deserialized = JsonSerializer.Deserialize<ExpectedError>(json);
|
||||
|
||||
// Then
|
||||
deserialized!.Type.ShouldBe("custom_type");
|
||||
deserialized.Message.ShouldBe("custom message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_single_object_as_Error_produces_ExpectedError()
|
||||
{
|
||||
// Given
|
||||
var json = """{"type":"foo","msg":"bar"}""";
|
||||
|
||||
// When
|
||||
var error = JsonSerializer.Deserialize<Error>(json);
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ExpectedError>();
|
||||
error!.Type.ShouldBe("foo");
|
||||
error.Message.ShouldBe("bar");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unknown_properties_become_ExtensionData()
|
||||
{
|
||||
// Given
|
||||
var json = """{"type":"err","msg":"details","meta_key":"meta_value","flag":"1"}""";
|
||||
|
||||
// When
|
||||
var error = JsonSerializer.Deserialize<Error>(json);
|
||||
|
||||
// Then
|
||||
error.ShouldBeOfType<ExpectedError>();
|
||||
error!.ExtensionData["meta_key"].ShouldBe("meta_value");
|
||||
error.ExtensionData["flag"].ShouldBe("1");
|
||||
}
|
||||
}
|
||||
|
||||
public class ExceptionalError_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Round_trip_as_ExceptionalError_preserves_Type_and_Message()
|
||||
{
|
||||
// Given
|
||||
Error original = Error.New(new InvalidOperationException("boom"));
|
||||
|
||||
// When
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
var deserialized = JsonSerializer.Deserialize<ExceptionalError>(json);
|
||||
|
||||
// Then
|
||||
deserialized.ShouldBeOfType<ExceptionalError>();
|
||||
deserialized!.Type.ShouldBe("System.InvalidOperationException");
|
||||
deserialized.Message.ShouldBe("boom");
|
||||
deserialized.IsExceptional.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionalError_serialized_then_deserialized_as_Error_becomes_ExpectedError()
|
||||
{
|
||||
// ErrorJsonConverter.Read always produces ExpectedError for JSON objects.
|
||||
|
||||
// Given
|
||||
Error original = Error.New(new InvalidOperationException("boom"));
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
|
||||
// When — deserialize as the base Error type
|
||||
var deserialized = JsonSerializer.Deserialize<Error>(json);
|
||||
|
||||
// Then — should NOT be an ExceptionalError (known bug)
|
||||
deserialized.ShouldBeOfType<ExpectedError>();
|
||||
deserialized!.Type.ShouldBe("System.InvalidOperationException");
|
||||
deserialized.Message.ShouldBe("boom");
|
||||
deserialized.IsExceptional.ShouldBeFalse();
|
||||
deserialized.IsExpected.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
public class ManyErrors_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Round_trip_preserves_all_elements()
|
||||
{
|
||||
// Given
|
||||
Error original = Error.Many(
|
||||
Error.New("err1", "first error"),
|
||||
Error.New("err2", "second error"));
|
||||
|
||||
// When
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
var deserialized = JsonSerializer.Deserialize<Error>(json);
|
||||
|
||||
// Then
|
||||
deserialized.ShouldBeOfType<ManyErrors>();
|
||||
var many = (ManyErrors)deserialized!;
|
||||
many.Count.ShouldBe(2);
|
||||
many[0].Type.ShouldBe("err1");
|
||||
many[0].Message.ShouldBe("first error");
|
||||
many[1].Type.ShouldBe("err2");
|
||||
many[1].Message.ShouldBe("second error");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Round_trip_as_ManyErrors_preserves_elements()
|
||||
{
|
||||
// Given
|
||||
var original = new ManyErrors([
|
||||
Error.New("err1", "first"),
|
||||
Error.New("err2", "second")
|
||||
]);
|
||||
|
||||
// When
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
var deserialized = JsonSerializer.Deserialize<ManyErrors>(json);
|
||||
|
||||
// Then
|
||||
deserialized.ShouldBeOfType<ManyErrors>();
|
||||
deserialized!.Count.ShouldBe(2);
|
||||
deserialized[0].Type.ShouldBe("err1");
|
||||
deserialized[1].Type.ShouldBe("err2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_ManyErrors_round_trip()
|
||||
{
|
||||
// Given
|
||||
Error original = Error.Many(Array.Empty<Error>());
|
||||
|
||||
// When
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
var deserialized = JsonSerializer.Deserialize<Error>(json);
|
||||
|
||||
// Then
|
||||
deserialized.ShouldBeOfType<ManyErrors>();
|
||||
deserialized!.IsEmpty.ShouldBeTrue();
|
||||
deserialized.Count.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtensionData_round_trip_within_ManyErrors()
|
||||
{
|
||||
// Given
|
||||
Error original = Error.Many(
|
||||
Error.New("err", "msg", new Dictionary<string, string> { ["meta"] = "value" }),
|
||||
Error.New("err2", "msg2"));
|
||||
|
||||
// When
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
var deserialized = JsonSerializer.Deserialize<Error>(json);
|
||||
|
||||
// Then
|
||||
var many = (ManyErrors)deserialized!;
|
||||
many[0]["meta"].ShouldBe("value");
|
||||
}
|
||||
}
|
||||
|
||||
public class Null_Handling
|
||||
{
|
||||
[Fact]
|
||||
public void Deserialize_null_JSON_returns_null()
|
||||
{
|
||||
// Given
|
||||
var json = "null";
|
||||
|
||||
// When
|
||||
var result = JsonSerializer.Deserialize<Error>(json);
|
||||
|
||||
// Then
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
public class Error_Handling
|
||||
{
|
||||
[Fact]
|
||||
public void Non_string_property_value_throws_JsonException()
|
||||
{
|
||||
// Given
|
||||
var json = """{"type":"err","msg":"ok","bad":42}""";
|
||||
|
||||
// When / Then
|
||||
Should.Throw<JsonException>(() => JsonSerializer.Deserialize<Error>(json));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Malformed_JSON_throws_JsonException()
|
||||
{
|
||||
// Given
|
||||
var json = "{not valid json at all";
|
||||
|
||||
// When / Then
|
||||
Should.Throw<JsonException>(() => JsonSerializer.Deserialize<Error>(json));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,14 @@
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
|
||||
<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit.v3" Version="3.2.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit.v3" Version="3.2.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="10.0.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -11,8 +11,8 @@ public class Combine
|
||||
// When
|
||||
var result = Result.Combine(result1, result2);
|
||||
// Then
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(result2.Error, result.Error);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(result2.Error);
|
||||
}
|
||||
[Fact]
|
||||
public void TwoResultCombination_WhenThereAreTwoErrors()
|
||||
@@ -23,8 +23,8 @@ public class Combine
|
||||
// When
|
||||
var result = Result.Combine(result1, result2);
|
||||
// Then
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(result1.Error + result2.Error, result.Error);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(result1.Error + result2.Error);
|
||||
}
|
||||
[Fact]
|
||||
public void TwoResultCombination_WhenThereIsNoError()
|
||||
@@ -35,7 +35,7 @@ public class Combine
|
||||
// When
|
||||
var result = Result.Combine(result1, result2);
|
||||
// Then
|
||||
Assert.True(result.IsSuccess);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
}
|
||||
[Fact]
|
||||
public void ThreeResultCombination_WhenThereIsAnError()
|
||||
@@ -47,8 +47,8 @@ public class Combine
|
||||
// When
|
||||
Result<(int, double)> result = Result.Combine(result1, result2, result3);
|
||||
// Then
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(result3.Error, result.Error);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(result3.Error);
|
||||
}
|
||||
[Fact]
|
||||
public void ThreeResultCombination_WhenThereAreTwoErrors()
|
||||
@@ -60,8 +60,8 @@ public class Combine
|
||||
// When
|
||||
Result<(int?, double)> result = Result.Combine(result1, result2, result3);
|
||||
// Then
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(result1.Error + result3.Error, result.Error);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(result1.Error + result3.Error);
|
||||
}
|
||||
[Fact]
|
||||
public void ThreeResultCombination_WhenThereIsNoError()
|
||||
@@ -73,6 +73,6 @@ public class Combine
|
||||
// When
|
||||
var result = Result.Combine(result1, result2, result3);
|
||||
// Then
|
||||
Assert.True(result.IsSuccess);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ public class GeneralUsage
|
||||
onSuccess: s => s.ToUpper(),
|
||||
onFailure: _ =>
|
||||
{
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return "";
|
||||
}
|
||||
);
|
||||
|
||||
Assert.Equal("TEST_1;SOME", result);
|
||||
result.ShouldBe("TEST_1;SOME");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -39,30 +39,30 @@ public class GeneralUsage
|
||||
.Append("test")
|
||||
.Map((i, s) =>
|
||||
{
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return "";
|
||||
})
|
||||
.Append("some")
|
||||
.Bind((s1, s2) =>
|
||||
.Bind((s1, s2) =>
|
||||
{
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return Result.Success("");
|
||||
})
|
||||
.Match(
|
||||
onSuccess: _ =>
|
||||
onSuccess: _ =>
|
||||
{
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return "";
|
||||
},
|
||||
onFailure: err =>
|
||||
{
|
||||
Assert.Equal(error, err);
|
||||
err.ShouldBe(error);
|
||||
return "satisfied";
|
||||
}
|
||||
);
|
||||
|
||||
// Then
|
||||
Assert.Equal("satisfied", result);
|
||||
result.ShouldBe("satisfied");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -72,7 +72,7 @@ public class GeneralUsage
|
||||
|
||||
// When
|
||||
var result = await Result.Success()
|
||||
.Append(() => ValueTask.FromResult(Result.Success(1)))
|
||||
.Append(() => Task.FromResult(Result.Success(1)))
|
||||
.Append("test")
|
||||
.Map((i, s) => $"{s}_{i}")
|
||||
.Append("some")
|
||||
@@ -81,12 +81,12 @@ public class GeneralUsage
|
||||
onSuccess: s => s.ToUpper(),
|
||||
onFailure: _ =>
|
||||
{
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return "";
|
||||
}
|
||||
);
|
||||
|
||||
Assert.Equal("TEST_1;SOME", result);
|
||||
result.ShouldBe("TEST_1;SOME");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -102,31 +102,31 @@ public class GeneralUsage
|
||||
.Append("test")
|
||||
.Map((i, s) =>
|
||||
{
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return "";
|
||||
})
|
||||
.Append("some")
|
||||
.Bind(async (s1, s2) =>
|
||||
{
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
await Task.CompletedTask;
|
||||
return Result.Success("");
|
||||
})
|
||||
.Match(
|
||||
onSuccess: _ =>
|
||||
onSuccess: _ =>
|
||||
{
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return "";
|
||||
},
|
||||
onFailure: err =>
|
||||
{
|
||||
Assert.Equal(error, err);
|
||||
err.ShouldBe(error);
|
||||
return "satisfied";
|
||||
}
|
||||
);
|
||||
|
||||
// Then
|
||||
Assert.Equal("satisfied", result);
|
||||
result.ShouldBe("satisfied");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -137,17 +137,17 @@ public class GeneralUsage
|
||||
// When
|
||||
var result = failed.TryRecover(err =>
|
||||
{
|
||||
Assert.IsType<NotImplementedException>(err.ToException());
|
||||
err.ToException().ShouldBeOfType<NotImplementedException>();
|
||||
|
||||
if (err.Type == "System.NotImplementedException")
|
||||
return "recovered";
|
||||
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return "";
|
||||
});
|
||||
// Then
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal("recovered", result.Value);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe("recovered");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -162,12 +162,12 @@ public class GeneralUsage
|
||||
if (err.Type == "System.NotImplementedException")
|
||||
return error;
|
||||
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return "";
|
||||
});
|
||||
// Then
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(error, result.Error);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -179,8 +179,8 @@ public class GeneralUsage
|
||||
var result = success
|
||||
.Extend((i, s) => Result.Success($"{i} + {s}"));
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal((1, "2", "1 + 2"), result.Value);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe((1, "2", "1 + 2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -192,12 +192,12 @@ public class GeneralUsage
|
||||
var result = failure
|
||||
.Extend((i, s) =>
|
||||
{
|
||||
Assert.Fail();
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
return Result.Success("");
|
||||
});
|
||||
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(Error.New("failure"), result.Error);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(Error.New("failure"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -209,7 +209,7 @@ public class GeneralUsage
|
||||
var result = success
|
||||
.Extend((i, s) => Result.Failure<string>("failure"));
|
||||
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Equal(Error.New("failure"), result.Error);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(Error.New("failure"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
global using System.Text.Json;
|
||||
global using Xunit;
|
||||
global using Shouldly;
|
||||
global using Just.Railway;
|
||||
|
||||
Reference in New Issue
Block a user