using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using JetBrains.Lifetimes; using System.Threading.Tasks; using JetBrains.Diagnostics; using JetBrains.Threading; using System.Runtime.ExceptionServices; #nullable disable namespace JetBrains.Core { /// /// Helper methods for and building /// [PublicAPI] public static class Result { /// /// Message that is being applied to Result.Fail when no message provided /// public const string EmptyFailMessage = "<>"; /// /// Creates successful with value /// /// /// /// Result with == true public static Result Success(T value) { return new Result(value, null); } /// /// Creates failed /// /// /// Try to capture exception stack (if any), could be unwind by /// Result with == false. Returned type could be implicitly casted to any /// if is null public static Result Fail([NotNull] Exception exception, bool captureStackTrace = false) { if (exception == null) throw new ArgumentNullException(nameof(exception)); return new Result(null, captureStackTrace ? ExceptionDispatchInfo.Capture(exception) : (object) exception); } /// /// Creates failed with corresponding /// /// /// Special user-defined value provided for failed Result /// Try to capture exception stack (if any), could be unwind by /// Result with == false. Returned type could be implicitly casted to any /// if is null public static Result Fail([NotNull] Exception exception, TFailure failValue, bool captureStackTrace = false) { if (exception == null) throw new ArgumentNullException(nameof(exception)); return new Result(null, captureStackTrace ? ExceptionDispatchInfo.Capture(exception) : (object) exception, failValue); } /// /// Creates failed with that wraps provided /// /// Reason of failure. If not defined, is used. /// Result with == false. Returned type could be implicitly casted to any public static Result Fail(string message = null) { return Fail(new ResultException(message ?? EmptyFailMessage)); } /// /// Creates failed with that wraps provided /// /// Reason of failure. If not defined, is used. /// Special user-defined value provided for failed Result /// Result with == false. Returned type could be implicitly casted to any public static Result Fail(string message, TFailure failValue) { return Fail(new ResultException(message ?? EmptyFailMessage), failValue); } /// /// Creates failed with message= and user-defined failure parameter /// /// Special user-defined value provided for failed Result /// Result with == false. Returned type could be implicitly casted to any public static Result FailWithValue(TFailure failValue) { return Fail((string) null, failValue); } /// /// Creates special failed that wraps /// /// Result with == false and == true. Returned type could be implicitly casted to any public static Result Canceled() { return Canceled(new OperationCanceledException( Lifetime.Terminated )); } /// /// Creates special failed that wraps /// /// Captured OCE that lead to this cancellation /// Try to capture exception stack (if any), could be unwind by . /// Result with == false and == true. Returned type could be implicitly casted to any /// if is null public static Result Canceled([NotNull] OperationCanceledException exception, bool captureStackTrace = false) { return Fail(exception, captureStackTrace); } /// /// Void succeed result for type /// public static Result Unit = Success(Core.Unit.Instance); /// /// Wrap execution of () into . /// /// Function to execute /// type argument of returned Result /// Succeed result with == f() if no exception happened during execution. Failed result with corresponding exception otherwise #if !NETCOREAPP [System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions] #endif public static Result Wrap([NotNull] Func f) { try { return Success(f()); } catch (Exception e) { return Fail(e, true); } } /// /// Wrap execution of () into . /// /// Action to execute /// Succeed result with if no exception happened during execution. Failed result with corresponding exception otherwise #if !NETCOREAPP [System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions] #endif public static Result Wrap([NotNull] Action f) { try { f(); return Unit; } catch (Exception e) { return Fail(e, true); } } /// /// Wrap execution of () into . /// /// Action with parameter to execute /// function argument /// type /// Succeed result with if no exception happened during execution. Failed result with corresponding exception otherwise #if !NETCOREAPP [System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions] #endif public static Result Wrap([NotNull] Action f, T param) { try { f(param); return Unit; } catch (Exception e) { return Fail(e, true); } } /// /// Wrap execution of () into . /// /// Function with parameter to execute /// function argument /// type /// type argument of returned Result /// Succeed result with == f(param) if no exception happened during execution. Failed result with corresponding exception otherwise #if !NETCOREAPP [System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions] #endif public static Result Wrap([NotNull] Func f, T param) { try { return Success(f(param)); } catch (Exception e) { return Fail(e, true); } } /// /// Transforms this into . /// corresponds to in . /// corresponds to completed task with /// corresponds to completed task with and /// /// this /// that corresponds public static Task UnwrapTask(this Result result) { if (result.Succeed) return result.Value; if (!result.Canceled) return Task.FromException(result.Exception); if (result.Exception is OperationCanceledException oce) return Task.FromCanceled(oce.CancellationToken); else return Task.FromCanceled(Lifetime.Terminated); } /// /// Transforms this into . /// corresponds to in . /// corresponds to completed task with /// corresponds to completed task with and /// /// this /// type parameter of returning task /// that corresponds public static Task UnwrapTask(this Result> result) { if (result.Succeed) return result.Value; if (!result.Canceled) return Task.FromException(result.Exception); if (result.Exception is OperationCanceledException oce) return Task.FromCanceled(oce.CancellationToken); else return Task.FromCanceled(Lifetime.Terminated); } /// /// Wrap completed task's result into or throw is task is ! /// /// Must be finished (==true) or will be throws /// /// (task.) or (task.) /// in case of !task. public static Result FromCompletedTask(Task task) { if (!task.IsCompleted) throw new InvalidOperationException($"Task must be completed to convert into result but was in state: {task.Status}"); return task.Status == TaskStatus.RanToCompletion ? Success(task.Result) : Fail(task.Exception.NotNull($"Exception must always exist for task with status: {task.Status}")); } } /// /// Monad that can can have two states: and Fail (!). Also we distinct special type of Fail: . /// /// [PublicAPI] public readonly struct Result : IEquatable> { /// /// Value in case of , default(T) otherwise /// public readonly T Value; /// /// It this field not null, this Result is ! and vise versa. /// internal readonly object ExceptionOrExceptionDispatchInfo; /// /// Exception in case of (!), null otherwise /// public Exception Exception => ExceptionOrExceptionDispatchInfo is Exception ex ? ex : (ExceptionOrExceptionDispatchInfo as ExceptionDispatchInfo)?.SourceException; /// /// Exception message in case of (!), null otherwise /// public string FailMessage => Exception?.Message; /// /// Shouldn't be invoked in user's code /// /// /// internal Result(T success, object failure) { Value = success; ExceptionOrExceptionDispatchInfo = failure; } /// /// Is result successful /// public bool Succeed => ExceptionOrExceptionDispatchInfo == null; /// /// (!) and ( /// public bool FailedNotCanceled => !Succeed && !Canceled; /// /// Exception has specials type of or that has inside. /// public bool Canceled => Exception.IsOperationCanceled(); public static implicit operator Result(Result me) { return new Result(default(T), me.ExceptionOrExceptionDispatchInfo); } /// /// Transform this result into new one with given function. if !, stays untouched./> /// /// /// /// public Result Map(Func transform) { return Succeed ? Result.Wrap(transform, Value) : Result.Fail(Exception); } /// /// Map without lambda. Success{Anything} -> Success{}. Fail -> Fail /// /// In case of success we always create successful result with this value /// /// public Result Map(TRes successValue) { return Succeed ? Result.Success(successValue) : Result.Fail(Exception); } /// /// Returns if , throws otherwise /// /// if /// if ! public T Unwrap() { if (Succeed) return Value; switch (ExceptionOrExceptionDispatchInfo) { case Exception ex: throw ex; case ExceptionDispatchInfo edi: edi.Throw(); return Nothing.Unreachable(); default: return Nothing.Unreachable(); } } /// /// Transforms this into in state state. /// corresponds to . /// corresponds to /// corresponds to with /// /// in state public Task AsCompletedTask() { if (Succeed) return Task.FromResult(Value); if (!Canceled) return Task.FromException(Exception); if (Exception is OperationCanceledException oce) return Task.FromCanceled(oce.CancellationToken); else return Task.FromCanceled(Lifetime.Terminated); } public override string ToString() { var status = Succeed ? "Success(" + Value +")": Canceled ? "Canceled" : "Fail(" + FailMessage+")"; return "Result." + status; } public bool Equals(Result other) { return EqualityComparer.Default.Equals(Value, other.Value) && Equals(Exception, other.Exception); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is Result other && Equals(other); } public override int GetHashCode() { unchecked { return (EqualityComparer.Default.GetHashCode(Value) * 397) ^ (Exception != null ? Exception.GetHashCode() : 0); } } public static bool operator ==(Result left, Result right) { return left.Equals(right); } public static bool operator !=(Result left, Result right) { return !left.Equals(right); } } /// /// Special kind of to store custom in case of !. /// /// /// [PublicAPI] public readonly struct Result : IEquatable> { /// /// Value in case of , default(T) otherwise /// public readonly TSuccess Value; /// /// It this field not null, this Result is ! and vise versa. /// internal readonly object ExceptionOrExceptionDispatchInfo; /// /// Exception in case of (!), null otherwise /// public Exception Exception => ExceptionOrExceptionDispatchInfo is Exception ex ? ex : (ExceptionOrExceptionDispatchInfo as ExceptionDispatchInfo)?.SourceException; public string FailMessage => Exception?.Message; public readonly TFailure FailValue; internal Result(TSuccess success, object failure, TFailure failValue) { Value = success; ExceptionOrExceptionDispatchInfo = failure; FailValue = failValue; } public bool Succeed => ExceptionOrExceptionDispatchInfo == null; public bool FailedNotCanceled => !Succeed && !Canceled; public bool Canceled => Exception is OperationCanceledException ||((Exception as AggregateException)?.Flatten().InnerExceptions.Any(ex => ex is OperationCanceledException) ?? false) ; public static implicit operator Result(Result me) { return new Result(default(TSuccess), me.ExceptionOrExceptionDispatchInfo, me.FailValue); } public static implicit operator Result(Result me) { return new Result(me.Value, me.ExceptionOrExceptionDispatchInfo); } public static implicit operator Result(Result me) { return new Result(me.Value, me.ExceptionOrExceptionDispatchInfo, default(TFailure)); } public TSuccess Unwrap() { if (Succeed) return Value; switch (ExceptionOrExceptionDispatchInfo) { case Exception ex: throw ex; case ExceptionDispatchInfo edi: edi.Throw(); return Nothing.Unreachable(); default: return Nothing.Unreachable(); } } public override string ToString() { var status = Succeed ? "Success(" + Value +")": Canceled ? "Canceled" : $"Fail({FailMessage}, {FailValue})"; return "Result." + status; } public bool Equals(Result other) { return EqualityComparer.Default.Equals(Value, other.Value) && Equals(Exception, other.Exception) && EqualityComparer.Default.Equals(FailValue, other.FailValue); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is Result other && Equals(other); } public override int GetHashCode() { unchecked { var hashCode = EqualityComparer.Default.GetHashCode(Value); hashCode = (hashCode * 397) ^ (Exception != null ? Exception.GetHashCode() : 0); hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(FailValue); return hashCode; } } public static bool operator ==(Result left, Result right) { return left.Equals(right); } public static bool operator !=(Result left, Result right) { return !left.Equals(right); } } /// /// Exception arising in when do not specify exception explicitly: /// public class ResultException : Exception { public ResultException(string message) : base(message) { } } }