This commit is contained in:
+40
-6
@@ -9,6 +9,12 @@ public abstract class Error : IEquatable<Error>, IComparable<Error>
|
|||||||
{
|
{
|
||||||
protected internal Error(){}
|
protected internal Error(){}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A reusable singleton representing the absence of an error (null-object pattern).
|
||||||
|
/// </summary>
|
||||||
|
[Pure]
|
||||||
|
public static Error Empty => EmptyError.Instance;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create an <see cref="ExceptionalError"/>
|
/// Create an <see cref="ExceptionalError"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -99,11 +105,33 @@ public abstract class Error : IEquatable<Error>, IComparable<Error>
|
|||||||
[Pure] public abstract bool IsExpected { get; }
|
[Pure] public abstract bool IsExpected { get; }
|
||||||
[Pure] public abstract bool IsExceptional { get; }
|
[Pure] public abstract bool IsExceptional { get; }
|
||||||
|
|
||||||
/// <summary>
|
[Pure] public Error WithExtensionData(IEnumerable<KeyValuePair<string, string>> extensionData)
|
||||||
/// A reusable singleton representing the absence of an error (null-object pattern).
|
{
|
||||||
/// </summary>
|
return this switch
|
||||||
[Pure]
|
{
|
||||||
public static Error Empty => EmptyError.Instance;
|
{ IsEmpty: true } => Empty,
|
||||||
|
ExpectedError expected => new ExpectedError(expected.Type, expected.Message)
|
||||||
|
{
|
||||||
|
ExtensionData = extensionData
|
||||||
|
.UnionBy(expected.ExtensionData, x => x.Key)
|
||||||
|
.ToImmutableDictionary(),
|
||||||
|
},
|
||||||
|
ExceptionalError exceptional => new ExceptionalError(exceptional.Type, exceptional.Message, exceptional.Exception)
|
||||||
|
{
|
||||||
|
ExtensionData = extensionData
|
||||||
|
.UnionBy(exceptional.ExtensionData, x => x.Key)
|
||||||
|
.ToImmutableDictionary(),
|
||||||
|
},
|
||||||
|
ManyErrors manyErrors => new ManyErrors(manyErrors.AccessUnsafe())
|
||||||
|
{
|
||||||
|
ExtensionData = extensionData
|
||||||
|
.UnionBy(manyErrors.ExtensionData, x => x.Key)
|
||||||
|
.ToImmutableDictionary(),
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => throw new NotImplementedException(), // should not reach this path
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
[Pure] public Error Append(Error? next)
|
[Pure] public Error Append(Error? next)
|
||||||
{
|
{
|
||||||
@@ -179,7 +207,7 @@ public sealed class EmptyError : Error
|
|||||||
[Pure] public override bool IsExceptional => false;
|
[Pure] public override bool IsExceptional => false;
|
||||||
|
|
||||||
[Pure]
|
[Pure]
|
||||||
public override IEnumerable<Error> ToEnumerable() { yield break; }
|
public override IEnumerable<Error> ToEnumerable() => Enumerable.Empty<Error>();
|
||||||
|
|
||||||
[Pure]
|
[Pure]
|
||||||
public override Exception ToException() =>
|
public override Exception ToException() =>
|
||||||
@@ -250,6 +278,11 @@ public sealed class ExceptionalError : Error
|
|||||||
Exception = exception;
|
Exception = exception;
|
||||||
ExtensionData = ExtractExtensionData(exception);
|
ExtensionData = ExtractExtensionData(exception);
|
||||||
}
|
}
|
||||||
|
internal ExceptionalError(string type, string message, Exception? exception)
|
||||||
|
: this(type, message)
|
||||||
|
{
|
||||||
|
Exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
public ExceptionalError(string type, string message)
|
public ExceptionalError(string type, string message)
|
||||||
{
|
{
|
||||||
@@ -457,6 +490,7 @@ public sealed class ManyErrors : Error, IEnumerable<Error>, IReadOnlyList<Error>
|
|||||||
errors.Add(error);
|
errors.Add(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] internal ImmutableArray<Error> AccessUnsafe() => _errors;
|
||||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] internal override Error AccessUnsafe(int position) => _errors[position];
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] internal override Error AccessUnsafe(int position) => _errors[position];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,22 @@ namespace Just.Railway;
|
|||||||
|
|
||||||
internal static class ReflectionHelper
|
internal static class ReflectionHelper
|
||||||
{
|
{
|
||||||
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static R ObjectCast<R>(object value) where R: class => (R)value;
|
||||||
|
|
||||||
[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)]
|
||||||
|
/// <summary>
|
||||||
|
/// Test for potential nullability
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if T is reference type or Nullable value type.</returns>
|
||||||
|
public static bool IsNullable<T>() => TypeReflectionCache<T>.IsNullable;
|
||||||
|
|
||||||
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool IsNullableStruct<T>() => TypeReflectionCache<T>.IsNullableStruct;
|
||||||
|
|
||||||
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;
|
||||||
@@ -30,8 +43,14 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IsNullableStruct = isNullableStruct;
|
||||||
|
IsNullable = isNullableStruct || !type.IsValueType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsNullable { get; }
|
||||||
|
public static bool IsNullableStruct { get; }
|
||||||
|
|
||||||
#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);
|
||||||
|
|||||||
+20
-7
@@ -45,6 +45,9 @@ public readonly partial struct Result : IEquatable<Result>
|
|||||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result Failure(string error) => Error.New(error ?? throw new ArgumentNullException(nameof(error)));
|
public static Result Failure(string error) => Error.New(error ?? throw new ArgumentNullException(nameof(error)));
|
||||||
|
|
||||||
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Result Failure(string type, string error) => Error.New(type, error ?? throw new ArgumentNullException(nameof(error)));
|
||||||
|
|
||||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result Failure(Error error) => new(error ?? throw new ArgumentNullException(nameof(error)));
|
public static Result Failure(Error error) => new(error ?? throw new ArgumentNullException(nameof(error)));
|
||||||
|
|
||||||
@@ -54,6 +57,9 @@ public readonly partial struct Result : IEquatable<Result>
|
|||||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result<T> Failure<T>(string error) => Error.New(error ?? throw new ArgumentNullException(nameof(error)));
|
public static Result<T> Failure<T>(string error) => Error.New(error ?? throw new ArgumentNullException(nameof(error)));
|
||||||
|
|
||||||
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Result<T> Failure<T>(string type, string error) => Error.New(type, error ?? throw new ArgumentNullException(nameof(error)));
|
||||||
|
|
||||||
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result<T> Failure<T>(Error error) => new(error ?? throw new ArgumentNullException(nameof(error)));
|
public static Result<T> Failure<T>(Error error) => new(error ?? throw new ArgumentNullException(nameof(error)));
|
||||||
|
|
||||||
@@ -76,7 +82,10 @@ public readonly partial struct Result : IEquatable<Result>
|
|||||||
public static explicit operator Result(SuccessUnit _) => new(null);
|
public static explicit operator Result(SuccessUnit _) => new(null);
|
||||||
|
|
||||||
[Pure] public bool IsSuccess => State == ResultState.Success;
|
[Pure] public bool IsSuccess => State == ResultState.Success;
|
||||||
[Pure] public bool IsFailure => State == ResultState.Error;
|
|
||||||
|
[Pure]
|
||||||
|
[MemberNotNullWhen(true, nameof(Error))]
|
||||||
|
public bool IsFailure => State == ResultState.Error;
|
||||||
|
|
||||||
[Pure] public bool TryGetValue([MaybeNullWhen(false)]out SuccessUnit? u, [MaybeNullWhen(true), NotNullWhen(false)]out Error? error)
|
[Pure] public bool TryGetValue([MaybeNullWhen(false)]out SuccessUnit? u, [MaybeNullWhen(true), NotNullWhen(false)]out Error? error)
|
||||||
{
|
{
|
||||||
@@ -97,17 +106,18 @@ public readonly partial struct Result : IEquatable<Result>
|
|||||||
}
|
}
|
||||||
[Pure] public bool TryGetError([MaybeNullWhen(false)]out Error error)
|
[Pure] public bool TryGetError([MaybeNullWhen(false)]out Error error)
|
||||||
{
|
{
|
||||||
if (IsSuccess)
|
switch (State)
|
||||||
{
|
{
|
||||||
|
case ResultState.Success:
|
||||||
error = default;
|
error = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
if (IsFailure)
|
case ResultState.Error:
|
||||||
{
|
|
||||||
error = Error!;
|
error = Error!;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
default: throw new ResultNotInitializedException();
|
||||||
}
|
}
|
||||||
throw new ResultNotInitializedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Pure] public override string ToString() => State switch
|
[Pure] public override string ToString() => State switch
|
||||||
@@ -127,9 +137,12 @@ 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 || other.State == ResultState.Bottom)
|
if (State == ResultState.Bottom)
|
||||||
throw new ResultNotInitializedException();
|
throw new ResultNotInitializedException();
|
||||||
|
|
||||||
|
if (other.State == ResultState.Bottom)
|
||||||
|
throw new ResultNotInitializedException(nameof(other));
|
||||||
|
|
||||||
return Error == other.Error;
|
return Error == other.Error;
|
||||||
}
|
}
|
||||||
[Pure] public static bool operator ==(Result left, Result right) => left.Equals(right);
|
[Pure] public static bool operator ==(Result left, Result right) => left.Equals(right);
|
||||||
|
|||||||
+93
-10
@@ -4,6 +4,83 @@ namespace Just.Railway;
|
|||||||
|
|
||||||
public static partial class ResultExtensions
|
public static partial class ResultExtensions
|
||||||
{
|
{
|
||||||
|
#region MapError
|
||||||
|
|
||||||
|
public static Result MapError(this in Result result, Func<Error, Error> func) => result.State switch
|
||||||
|
{
|
||||||
|
ResultState.Error => func(result.Error!),
|
||||||
|
ResultState.Success => result,
|
||||||
|
_ => throw new ResultNotInitializedException(nameof(result)),
|
||||||
|
};
|
||||||
|
public static Result<T> MapError<T>(this in Result<T> result, Func<Error, Error> func) => result.State switch
|
||||||
|
{
|
||||||
|
ResultState.Error => func(result.Error!),
|
||||||
|
ResultState.Success => result,
|
||||||
|
_ => throw new ResultNotInitializedException(nameof(result)),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static async Task<Result> MapError(this Task<Result> resultTask, Func<Error, Error> func)
|
||||||
|
{
|
||||||
|
var result = await resultTask.ConfigureAwait(false);
|
||||||
|
return result.State switch
|
||||||
|
{
|
||||||
|
ResultState.Error => func(result.Error!),
|
||||||
|
ResultState.Success => result,
|
||||||
|
_ => throw new ResultNotInitializedException(nameof(result)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static async Task<Result> MapError(this Result result, Func<Error, Task<Error>> func)
|
||||||
|
{
|
||||||
|
return result.State switch
|
||||||
|
{
|
||||||
|
ResultState.Error => await func(result.Error!).ConfigureAwait(false),
|
||||||
|
ResultState.Success => result,
|
||||||
|
_ => throw new ResultNotInitializedException(nameof(result)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static async Task<Result> MapError(this Task<Result> resultTask, Func<Error, Task<Error>> func)
|
||||||
|
{
|
||||||
|
var result = await resultTask.ConfigureAwait(false);
|
||||||
|
return result.State switch
|
||||||
|
{
|
||||||
|
ResultState.Error => await func(result.Error!),
|
||||||
|
ResultState.Success => result,
|
||||||
|
_ => throw new ResultNotInitializedException(nameof(result)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Result<T>> MapError<T>(this Task<Result<T>> resultTask, Func<Error, Error> func)
|
||||||
|
{
|
||||||
|
var result = await resultTask.ConfigureAwait(false);
|
||||||
|
return result.State switch
|
||||||
|
{
|
||||||
|
ResultState.Error => func(result.Error!),
|
||||||
|
ResultState.Success => result,
|
||||||
|
_ => throw new ResultNotInitializedException(nameof(result)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static async Task<Result<T>> MapError<T>(this Result<T> result, Func<Error, Task<Error>> func)
|
||||||
|
{
|
||||||
|
return result.State switch
|
||||||
|
{
|
||||||
|
ResultState.Error => await func(result.Error!).ConfigureAwait(false),
|
||||||
|
ResultState.Success => result,
|
||||||
|
_ => throw new ResultNotInitializedException(nameof(result)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static async Task<Result<T>> MapError<T>(this Task<Result<T>> resultTask, Func<Error, Task<Error>> func)
|
||||||
|
{
|
||||||
|
var result = await resultTask.ConfigureAwait(false);
|
||||||
|
return result.State switch
|
||||||
|
{
|
||||||
|
ResultState.Error => await func(result.Error!),
|
||||||
|
ResultState.Success => result,
|
||||||
|
_ => throw new ResultNotInitializedException(nameof(result)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Finally
|
#region Finally
|
||||||
|
|
||||||
public static Result Finally(this in Result result, Action action)
|
public static Result Finally(this in Result result, Action action)
|
||||||
@@ -142,20 +219,23 @@ public static partial class ResultExtensions
|
|||||||
switch (result.State)
|
switch (result.State)
|
||||||
{
|
{
|
||||||
case ResultState.Success:
|
case ResultState.Success:
|
||||||
break;
|
continue;
|
||||||
|
|
||||||
case ResultState.Error:
|
case ResultState.Error:
|
||||||
errors ??= ImmutableArray.CreateBuilder<Error>();
|
errors ??= ImmutableArray.CreateBuilder<Error>();
|
||||||
ManyErrors.AppendSanitized(errors, result.Error!);
|
ManyErrors.AppendSanitized(errors, result.Error!);
|
||||||
break;
|
continue;
|
||||||
|
|
||||||
default: throw new ResultNotInitializedException(nameof(results));
|
default: throw new ResultNotInitializedException(nameof(results));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors is null
|
return errors switch
|
||||||
? new(null)
|
{
|
||||||
: new(new ManyErrors(errors.ToImmutable()));
|
null => new Result(null),
|
||||||
|
{ Count: 1 } => errors[0],
|
||||||
|
_ => 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)
|
||||||
{
|
{
|
||||||
@@ -192,20 +272,23 @@ public static partial class ResultExtensions
|
|||||||
case ResultState.Success:
|
case ResultState.Success:
|
||||||
values ??= ImmutableArray.CreateBuilder<T>();
|
values ??= ImmutableArray.CreateBuilder<T>();
|
||||||
values.Add(result.Value);
|
values.Add(result.Value);
|
||||||
break;
|
continue;
|
||||||
|
|
||||||
case ResultState.Error:
|
case ResultState.Error:
|
||||||
errors ??= ImmutableArray.CreateBuilder<Error>();
|
errors ??= ImmutableArray.CreateBuilder<Error>();
|
||||||
ManyErrors.AppendSanitized(errors, result.Error!);
|
ManyErrors.AppendSanitized(errors, result.Error!);
|
||||||
break;
|
continue;
|
||||||
|
|
||||||
default: throw new ResultNotInitializedException(nameof(results));
|
default: throw new ResultNotInitializedException(nameof(results));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors is null
|
return errors switch
|
||||||
? new(values?.ToImmutable() ?? Enumerable.Empty<T>())
|
{
|
||||||
: new(new ManyErrors(errors.ToImmutable()));
|
null => new Result<IEnumerable<T>>(values?.ToImmutable() ?? Enumerable.Empty<T>()),
|
||||||
|
{ Count: 1 } => errors[0],
|
||||||
|
_ => new(new ManyErrors(errors.ToImmutable()))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
public static async Task<Result<IEnumerable<T>>> Merge<T>(this IEnumerable<Task<Result<T>>> tasks)
|
public static async Task<Result<IEnumerable<T>>> Merge<T>(this IEnumerable<Task<Result<T>>> tasks)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user