From d2923572d17dc3cf84219b8d3918f8167248e40c Mon Sep 17 00:00:00 2001 From: Aaron Salisbury Date: Sat, 19 Sep 2026 11:31:50 -0500 Subject: [PATCH 1/6] Clarify ProcessResult exception semantics --- .../Core/ProcessResult.cs | 289 ++++++++++-------- 1 file changed, 155 insertions(+), 134 deletions(-) diff --git a/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs b/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs index 56830a1..e9996e3 100644 --- a/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs +++ b/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs @@ -1,134 +1,155 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; - -namespace RunnethOverStudio.AppToolkit.Core; - -/// -/// Represents a result of an operation which can be the actual result or exception. -/// -/// The type of the value stored in the Result. -/// -/// Heavily inspired by the Result type from .NEXT (dotNext). -/// -[Serializable] -public class ProcessResult -{ - private readonly T _value; - private readonly ExceptionDispatchInfo? _exception; - - /// - /// Initializes a new successful result. - /// - /// The value to be stored as result. - public ProcessResult(T value) => this._value = value; - - /// - /// Initializes a new unsuccessful result. - /// - /// The exception representing error. Cannot be . - public ProcessResult(Exception error) : this(ExceptionDispatchInfo.Capture(error)) { } - - /// - /// Extracts the actual result. - /// - /// This result is not successful. - public T Value - { - get - { - Validate(); - return _value; - } - } - - /// - /// Gets the value if present; otherwise return default value. - /// - /// The value, if present, otherwise default. - public T? ValueOrDefault => _value; - - /// - /// Gets exception associated with this result. - /// - public Exception? Error => _exception?.SourceException; - - /// - /// Indicates that the result is successful. - /// - /// if this result is successful; if this result represents exception. - [MemberNotNullWhen(false, nameof(Error))] - public bool IsSuccessful => _exception is null; - - /// - /// Returns a string that represents the current result, indicating success or failure and the associated value or error. - /// - public override string ToString() => IsSuccessful ? $"Success({_value})" : $"Failure({Error})"; - - /// - /// Creates a successful containing the specified value. - /// - /// The value to store in the successful result. - /// A representing a successful operation. - public static ProcessResult Success(T value) => new(value); - - /// - /// Creates a failed containing the specified exception. - /// - /// The exception representing the failure. Cannot be . - /// A representing a failed operation. - public static ProcessResult Failure(Exception error) => new(error); - - /// - /// Logs a failure message and exception using the specified logger and log level, so long as logging is enabled. - /// Then returns a failed containing a new exception with the provided message and - /// the original exception as its inner exception. - /// - /// The message to log and to use as the new exception's message. - /// The original exception to be wrapped and logged. - /// The severity level at which to log the message. - /// The logger to use for logging the failure. - /// - /// A failed containing a new exception with the specified message and the original exception as its inner exception. - /// - public static ProcessResult LogAndForwardException(string message, Exception error, ILogger logger, LogLevel logLevel = LogLevel.Error) - { - if (logger.IsEnabled(logLevel)) - { - logger.Log(logLevel, error, "{Message}", message); - } - - return Failure(new Exception(message, innerException: error)); - } - - /// - /// Defines an implicit conversion from to . - /// - /// The result to evaluate. - /// - /// true if the result is successful; otherwise, false. - /// - public static implicit operator bool(ProcessResult result) => result.IsSuccessful; - - /// - /// Defines an explicit conversion from to the underlying value of type . - /// - /// The result to extract the value from. - /// The value contained in the result if it is successful. - /// - /// Thrown if the result is not successful and an attempt is made to extract the value. - /// - public static explicit operator T(ProcessResult result) => result.Value; - - [StackTraceHidden] - private void Validate() => _exception?.Throw(); - - private ProcessResult(ExceptionDispatchInfo dispatchInfo) - { - Unsafe.SkipInit(out _value); - _exception = dispatchInfo; - } -} +using Microsoft.Extensions.Logging; +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; + +namespace RunnethOverStudio.AppToolkit.Core; + +/// +/// Represents the result of an operation that either produced a value or intentionally captured an exception. +/// +/// The type of the value stored in the result. +/// +/// +/// Use when an exception is part of the operation's explicit result contract, such as +/// when a boundary deliberately captures an exception so the caller can inspect, forward, or defer it. +/// +/// +/// Expected application or domain outcomes should not be converted into exceptions merely to fit this type. +/// When callers are expected to branch on a known set of non-exceptional failure states, use +/// instead. Unexpected or exceptional failures should normally continue to +/// propagate as exceptions unless the boundary has a concrete reason to capture them. +/// +/// +/// Heavily inspired by the Result type +/// from .NEXT (dotNext). +/// +/// +[Serializable] +public class ProcessResult +{ + private readonly T _value; + private readonly ExceptionDispatchInfo? _exception; + + /// + /// Initializes a new successful result. + /// + /// The value to be stored as result. + public ProcessResult(T value) => this._value = value; + + /// + /// Initializes a new unsuccessful result containing a captured exception. + /// + /// The exception representing the failure. Cannot be . + /// + /// This constructor is intended for failures that are exceptional in nature. Prefer + /// for expected failure states that are part of normal application flow. + /// + public ProcessResult(Exception error) : this(ExceptionDispatchInfo.Capture(error)) { } + + /// + /// Extracts the successful value or rethrows the captured exception. + /// + /// This result contains a captured exception. + public T Value + { + get + { + Validate(); + return _value; + } + } + + /// + /// Gets the value if present; otherwise returns the default value. + /// + /// The value, if present; otherwise, default. + public T? ValueOrDefault => _value; + + /// + /// Gets the exception associated with this result, or when successful. + /// + public Exception? Error => _exception?.SourceException; + + /// + /// Indicates whether the result contains a successful value rather than a captured exception. + /// + /// when successful; otherwise, . + [MemberNotNullWhen(false, nameof(Error))] + public bool IsSuccessful => _exception is null; + + /// + /// Returns a string that represents the current result, indicating success or failure and the associated value or error. + /// + public override string ToString() => IsSuccessful ? $"Success({_value})" : $"Failure({Error})"; + + /// + /// Creates a successful containing the specified value. + /// + /// The value to store in the successful result. + /// A representing a successful operation. + public static ProcessResult Success(T value) => new(value); + + /// + /// Creates a failed containing the specified exception. + /// + /// The exception representing the failure. Cannot be . + /// A representing an exceptional failure. + /// + /// Prefer when the failure is an expected outcome that callers + /// should handle without exception semantics. + /// + public static ProcessResult Failure(Exception error) => new(error); + + /// + /// Logs a failure message and exception using the specified logger and log level, so long as logging is enabled. + /// Then returns a failed containing a new exception with the provided message and + /// the original exception as its inner exception. + /// + /// The message to log and to use as the new exception's message. + /// The original exception to be wrapped and logged. + /// The severity level at which to log the message. + /// The logger to use for logging the failure. + /// + /// A failed containing a new exception with the specified message and the original exception as its inner exception. + /// + public static ProcessResult LogAndForwardException(string message, Exception error, ILogger logger, LogLevel logLevel = LogLevel.Error) + { + if (logger.IsEnabled(logLevel)) + { + logger.Log(logLevel, error, "{Message}", message); + } + + return Failure(new Exception(message, innerException: error)); + } + + /// + /// Defines an implicit conversion from to . + /// + /// The result to evaluate. + /// + /// if the result is successful; otherwise, . + /// + public static implicit operator bool(ProcessResult result) => result.IsSuccessful; + + /// + /// Defines an explicit conversion from to the underlying value of type . + /// + /// The result to extract the value from. + /// The value contained in the result if it is successful. + /// + /// Thrown if the result is not successful and an attempt is made to extract the value. + /// + public static explicit operator T(ProcessResult result) => result.Value; + + [StackTraceHidden] + private void Validate() => _exception?.Throw(); + + private ProcessResult(ExceptionDispatchInfo dispatchInfo) + { + Unsafe.SkipInit(out _value); + _exception = dispatchInfo; + } +} From 5b5e264637025c0d763920331a4dd9c55248d99d Mon Sep 17 00:00:00 2001 From: Aaron Salisbury Date: Sat, 19 Sep 2026 11:31:53 -0500 Subject: [PATCH 2/6] Add ProcessResult expected error variant --- .../Core/ProcessResultOfError.cs | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs diff --git a/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs b/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs new file mode 100644 index 0000000..db95571 --- /dev/null +++ b/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace RunnethOverStudio.AppToolkit.Core; + +/// +/// Represents the result of an operation that either produced a value or an expected, explicitly modeled error. +/// +/// The type of the successful value stored in the result. +/// +/// An enumeration describing expected failure states. The default enumeration value is reserved to represent success. +/// +/// +/// +/// Use when failure is an anticipated part of normal application or domain flow +/// and callers are expected to branch on a known error state. Examples include a requested resource not being found, +/// a destination no longer existing, or another explicitly modeled business outcome. +/// +/// +/// This type is intentionally not an exception container. Unexpected or exceptional failures should normally propagate +/// as exceptions. When a boundary deliberately needs to capture an exception as part of its result contract, use +/// instead. +/// +/// +/// The default value of is reserved for success. Define a neutral member such as +/// None = 0 when naming that state improves readability, and use non-default values for actual errors. +/// +/// +/// Heavily inspired by the Result type +/// from .NEXT (dotNext). +/// +/// +[Serializable] +public class ProcessResult where TError : struct, Enum +{ + private readonly T _value; + private readonly TError _error; + + /// + /// Initializes a new successful result. + /// + /// The value to be stored as the successful result. + public ProcessResult(T value) + { + _value = value; + _error = default; + } + + /// + /// Initializes a new unsuccessful result containing an expected error. + /// + /// The expected error describing why the operation was unsuccessful. + /// + /// is the default value of , which is reserved for success. + /// + public ProcessResult(TError error) + { + if (EqualityComparer.Default.Equals(error, default)) + { + throw new ArgumentOutOfRangeException(nameof(error), "The default error value is reserved for successful results."); + } + + _value = default!; + _error = error; + } + + /// + /// Extracts the successful value. + /// + /// + /// This result contains an expected error rather than a successful value. + /// + public T Value + { + get + { + Validate(); + return _value; + } + } + + /// + /// Gets the successful value when present; otherwise returns the default value. + /// + /// The successful value, if present; otherwise, default. + public T? ValueOrDefault => IsSuccessful ? _value : default; + + /// + /// Gets the expected error associated with the result. + /// + /// + /// The default value represents success and is not an error. Check before interpreting + /// this property as a failure state. + /// + public TError Error => _error; + + /// + /// Indicates whether the result contains a successful value rather than an expected error. + /// + /// when successful; otherwise, . + public bool IsSuccessful => EqualityComparer.Default.Equals(_error, default); + + /// + /// Returns a string that represents the current result, indicating success or failure and the associated value or error. + /// + public override string ToString() => IsSuccessful ? $"Success({_value})" : $"Failure({_error})"; + + /// + /// Creates a successful containing the specified value. + /// + /// The value to store in the successful result. + /// A representing a successful operation. + public static ProcessResult Success(T value) => new(value); + + /// + /// Creates a failed containing the specified expected error. + /// + /// The expected error describing why the operation was unsuccessful. + /// A representing an expected failure. + /// + /// is the default value of , which is reserved for success. + /// + public static ProcessResult Failure(TError error) => new(error); + + /// + /// Attempts to extract the successful value. + /// + /// The successful value when present; otherwise, the default value. + /// when successful; otherwise, . + public bool TryGet(out T? value) + { + value = IsSuccessful ? _value : default; + return IsSuccessful; + } + + /// + /// Defines an implicit conversion from to . + /// + /// The result to evaluate. + /// + /// if the result is successful; otherwise, . + /// + public static implicit operator bool(ProcessResult result) => result.IsSuccessful; + + /// + /// Defines an explicit conversion from to the underlying value of type . + /// + /// The result to extract the value from. + /// The value contained in the result if it is successful. + /// + /// Thrown if the result is not successful and an attempt is made to extract the value. + /// + public static explicit operator T(ProcessResult result) => result.Value; + + [StackTraceHidden] + private void Validate() + { + if (!IsSuccessful) + { + throw new InvalidOperationException($"The process result is unsuccessful with error '{_error}'."); + } + } +} From 8cab83f37768468a23f2be319e3b182003ab7dde Mon Sep 17 00:00:00 2001 From: Aaron Salisbury Date: Sat, 19 Sep 2026 11:32:21 -0500 Subject: [PATCH 3/6] Preserve AppToolkit source line endings --- .../Core/ProcessResult.cs | 310 +++++++++--------- 1 file changed, 155 insertions(+), 155 deletions(-) diff --git a/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs b/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs index e9996e3..45b8b27 100644 --- a/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs +++ b/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs @@ -1,155 +1,155 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; - -namespace RunnethOverStudio.AppToolkit.Core; - -/// -/// Represents the result of an operation that either produced a value or intentionally captured an exception. -/// -/// The type of the value stored in the result. -/// -/// -/// Use when an exception is part of the operation's explicit result contract, such as -/// when a boundary deliberately captures an exception so the caller can inspect, forward, or defer it. -/// -/// -/// Expected application or domain outcomes should not be converted into exceptions merely to fit this type. -/// When callers are expected to branch on a known set of non-exceptional failure states, use -/// instead. Unexpected or exceptional failures should normally continue to -/// propagate as exceptions unless the boundary has a concrete reason to capture them. -/// -/// -/// Heavily inspired by the Result type -/// from .NEXT (dotNext). -/// -/// -[Serializable] -public class ProcessResult -{ - private readonly T _value; - private readonly ExceptionDispatchInfo? _exception; - - /// - /// Initializes a new successful result. - /// - /// The value to be stored as result. - public ProcessResult(T value) => this._value = value; - - /// - /// Initializes a new unsuccessful result containing a captured exception. - /// - /// The exception representing the failure. Cannot be . - /// - /// This constructor is intended for failures that are exceptional in nature. Prefer - /// for expected failure states that are part of normal application flow. - /// - public ProcessResult(Exception error) : this(ExceptionDispatchInfo.Capture(error)) { } - - /// - /// Extracts the successful value or rethrows the captured exception. - /// - /// This result contains a captured exception. - public T Value - { - get - { - Validate(); - return _value; - } - } - - /// - /// Gets the value if present; otherwise returns the default value. - /// - /// The value, if present; otherwise, default. - public T? ValueOrDefault => _value; - - /// - /// Gets the exception associated with this result, or when successful. - /// - public Exception? Error => _exception?.SourceException; - - /// - /// Indicates whether the result contains a successful value rather than a captured exception. - /// - /// when successful; otherwise, . - [MemberNotNullWhen(false, nameof(Error))] - public bool IsSuccessful => _exception is null; - - /// - /// Returns a string that represents the current result, indicating success or failure and the associated value or error. - /// - public override string ToString() => IsSuccessful ? $"Success({_value})" : $"Failure({Error})"; - - /// - /// Creates a successful containing the specified value. - /// - /// The value to store in the successful result. - /// A representing a successful operation. - public static ProcessResult Success(T value) => new(value); - - /// - /// Creates a failed containing the specified exception. - /// - /// The exception representing the failure. Cannot be . - /// A representing an exceptional failure. - /// - /// Prefer when the failure is an expected outcome that callers - /// should handle without exception semantics. - /// - public static ProcessResult Failure(Exception error) => new(error); - - /// - /// Logs a failure message and exception using the specified logger and log level, so long as logging is enabled. - /// Then returns a failed containing a new exception with the provided message and - /// the original exception as its inner exception. - /// - /// The message to log and to use as the new exception's message. - /// The original exception to be wrapped and logged. - /// The severity level at which to log the message. - /// The logger to use for logging the failure. - /// - /// A failed containing a new exception with the specified message and the original exception as its inner exception. - /// - public static ProcessResult LogAndForwardException(string message, Exception error, ILogger logger, LogLevel logLevel = LogLevel.Error) - { - if (logger.IsEnabled(logLevel)) - { - logger.Log(logLevel, error, "{Message}", message); - } - - return Failure(new Exception(message, innerException: error)); - } - - /// - /// Defines an implicit conversion from to . - /// - /// The result to evaluate. - /// - /// if the result is successful; otherwise, . - /// - public static implicit operator bool(ProcessResult result) => result.IsSuccessful; - - /// - /// Defines an explicit conversion from to the underlying value of type . - /// - /// The result to extract the value from. - /// The value contained in the result if it is successful. - /// - /// Thrown if the result is not successful and an attempt is made to extract the value. - /// - public static explicit operator T(ProcessResult result) => result.Value; - - [StackTraceHidden] - private void Validate() => _exception?.Throw(); - - private ProcessResult(ExceptionDispatchInfo dispatchInfo) - { - Unsafe.SkipInit(out _value); - _exception = dispatchInfo; - } -} +using Microsoft.Extensions.Logging; +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; + +namespace RunnethOverStudio.AppToolkit.Core; + +/// +/// Represents the result of an operation that either produced a value or intentionally captured an exception. +/// +/// The type of the value stored in the result. +/// +/// +/// Use when an exception is part of the operation's explicit result contract, such as +/// when a boundary deliberately captures an exception so the caller can inspect, forward, or defer it. +/// +/// +/// Expected application or domain outcomes should not be converted into exceptions merely to fit this type. +/// When callers are expected to branch on a known set of non-exceptional failure states, use +/// instead. Unexpected or exceptional failures should normally continue to +/// propagate as exceptions unless the boundary has a concrete reason to capture them. +/// +/// +/// Heavily inspired by the Result type +/// from .NEXT (dotNext). +/// +/// +[Serializable] +public class ProcessResult +{ + private readonly T _value; + private readonly ExceptionDispatchInfo? _exception; + + /// + /// Initializes a new successful result. + /// + /// The value to be stored as result. + public ProcessResult(T value) => this._value = value; + + /// + /// Initializes a new unsuccessful result containing a captured exception. + /// + /// The exception representing the failure. Cannot be . + /// + /// This constructor is intended for failures that are exceptional in nature. Prefer + /// for expected failure states that are part of normal application flow. + /// + public ProcessResult(Exception error) : this(ExceptionDispatchInfo.Capture(error)) { } + + /// + /// Extracts the successful value or rethrows the captured exception. + /// + /// This result contains a captured exception. + public T Value + { + get + { + Validate(); + return _value; + } + } + + /// + /// Gets the value if present; otherwise returns the default value. + /// + /// The value, if present; otherwise, default. + public T? ValueOrDefault => _value; + + /// + /// Gets the exception associated with this result, or when successful. + /// + public Exception? Error => _exception?.SourceException; + + /// + /// Indicates whether the result contains a successful value rather than a captured exception. + /// + /// when successful; otherwise, . + [MemberNotNullWhen(false, nameof(Error))] + public bool IsSuccessful => _exception is null; + + /// + /// Returns a string that represents the current result, indicating success or failure and the associated value or error. + /// + public override string ToString() => IsSuccessful ? $"Success({_value})" : $"Failure({Error})"; + + /// + /// Creates a successful containing the specified value. + /// + /// The value to store in the successful result. + /// A representing a successful operation. + public static ProcessResult Success(T value) => new(value); + + /// + /// Creates a failed containing the specified exception. + /// + /// The exception representing the failure. Cannot be . + /// A representing an exceptional failure. + /// + /// Prefer when the failure is an expected outcome that callers + /// should handle without exception semantics. + /// + public static ProcessResult Failure(Exception error) => new(error); + + /// + /// Logs a failure message and exception using the specified logger and log level, so long as logging is enabled. + /// Then returns a failed containing a new exception with the provided message and + /// the original exception as its inner exception. + /// + /// The message to log and to use as the new exception's message. + /// The original exception to be wrapped and logged. + /// The severity level at which to log the message. + /// The logger to use for logging the failure. + /// + /// A failed containing a new exception with the specified message and the original exception as its inner exception. + /// + public static ProcessResult LogAndForwardException(string message, Exception error, ILogger logger, LogLevel logLevel = LogLevel.Error) + { + if (logger.IsEnabled(logLevel)) + { + logger.Log(logLevel, error, "{Message}", message); + } + + return Failure(new Exception(message, innerException: error)); + } + + /// + /// Defines an implicit conversion from to . + /// + /// The result to evaluate. + /// + /// if the result is successful; otherwise, . + /// + public static implicit operator bool(ProcessResult result) => result.IsSuccessful; + + /// + /// Defines an explicit conversion from to the underlying value of type . + /// + /// The result to extract the value from. + /// The value contained in the result if it is successful. + /// + /// Thrown if the result is not successful and an attempt is made to extract the value. + /// + public static explicit operator T(ProcessResult result) => result.Value; + + [StackTraceHidden] + private void Validate() => _exception?.Throw(); + + private ProcessResult(ExceptionDispatchInfo dispatchInfo) + { + Unsafe.SkipInit(out _value); + _exception = dispatchInfo; + } +} From e98c48591e01dae89f15005560bb0e0c47b6f853 Mon Sep 17 00:00:00 2001 From: Aaron Salisbury Date: Sat, 19 Sep 2026 11:32:25 -0500 Subject: [PATCH 4/6] Preserve AppToolkit source line endings --- .../Core/ProcessResultOfError.cs | 328 +++++++++--------- 1 file changed, 164 insertions(+), 164 deletions(-) diff --git a/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs b/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs index db95571..b4ca598 100644 --- a/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs +++ b/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs @@ -1,164 +1,164 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; - -namespace RunnethOverStudio.AppToolkit.Core; - -/// -/// Represents the result of an operation that either produced a value or an expected, explicitly modeled error. -/// -/// The type of the successful value stored in the result. -/// -/// An enumeration describing expected failure states. The default enumeration value is reserved to represent success. -/// -/// -/// -/// Use when failure is an anticipated part of normal application or domain flow -/// and callers are expected to branch on a known error state. Examples include a requested resource not being found, -/// a destination no longer existing, or another explicitly modeled business outcome. -/// -/// -/// This type is intentionally not an exception container. Unexpected or exceptional failures should normally propagate -/// as exceptions. When a boundary deliberately needs to capture an exception as part of its result contract, use -/// instead. -/// -/// -/// The default value of is reserved for success. Define a neutral member such as -/// None = 0 when naming that state improves readability, and use non-default values for actual errors. -/// -/// -/// Heavily inspired by the Result type -/// from .NEXT (dotNext). -/// -/// -[Serializable] -public class ProcessResult where TError : struct, Enum -{ - private readonly T _value; - private readonly TError _error; - - /// - /// Initializes a new successful result. - /// - /// The value to be stored as the successful result. - public ProcessResult(T value) - { - _value = value; - _error = default; - } - - /// - /// Initializes a new unsuccessful result containing an expected error. - /// - /// The expected error describing why the operation was unsuccessful. - /// - /// is the default value of , which is reserved for success. - /// - public ProcessResult(TError error) - { - if (EqualityComparer.Default.Equals(error, default)) - { - throw new ArgumentOutOfRangeException(nameof(error), "The default error value is reserved for successful results."); - } - - _value = default!; - _error = error; - } - - /// - /// Extracts the successful value. - /// - /// - /// This result contains an expected error rather than a successful value. - /// - public T Value - { - get - { - Validate(); - return _value; - } - } - - /// - /// Gets the successful value when present; otherwise returns the default value. - /// - /// The successful value, if present; otherwise, default. - public T? ValueOrDefault => IsSuccessful ? _value : default; - - /// - /// Gets the expected error associated with the result. - /// - /// - /// The default value represents success and is not an error. Check before interpreting - /// this property as a failure state. - /// - public TError Error => _error; - - /// - /// Indicates whether the result contains a successful value rather than an expected error. - /// - /// when successful; otherwise, . - public bool IsSuccessful => EqualityComparer.Default.Equals(_error, default); - - /// - /// Returns a string that represents the current result, indicating success or failure and the associated value or error. - /// - public override string ToString() => IsSuccessful ? $"Success({_value})" : $"Failure({_error})"; - - /// - /// Creates a successful containing the specified value. - /// - /// The value to store in the successful result. - /// A representing a successful operation. - public static ProcessResult Success(T value) => new(value); - - /// - /// Creates a failed containing the specified expected error. - /// - /// The expected error describing why the operation was unsuccessful. - /// A representing an expected failure. - /// - /// is the default value of , which is reserved for success. - /// - public static ProcessResult Failure(TError error) => new(error); - - /// - /// Attempts to extract the successful value. - /// - /// The successful value when present; otherwise, the default value. - /// when successful; otherwise, . - public bool TryGet(out T? value) - { - value = IsSuccessful ? _value : default; - return IsSuccessful; - } - - /// - /// Defines an implicit conversion from to . - /// - /// The result to evaluate. - /// - /// if the result is successful; otherwise, . - /// - public static implicit operator bool(ProcessResult result) => result.IsSuccessful; - - /// - /// Defines an explicit conversion from to the underlying value of type . - /// - /// The result to extract the value from. - /// The value contained in the result if it is successful. - /// - /// Thrown if the result is not successful and an attempt is made to extract the value. - /// - public static explicit operator T(ProcessResult result) => result.Value; - - [StackTraceHidden] - private void Validate() - { - if (!IsSuccessful) - { - throw new InvalidOperationException($"The process result is unsuccessful with error '{_error}'."); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace RunnethOverStudio.AppToolkit.Core; + +/// +/// Represents the result of an operation that either produced a value or an expected, explicitly modeled error. +/// +/// The type of the successful value stored in the result. +/// +/// An enumeration describing expected failure states. The default enumeration value is reserved to represent success. +/// +/// +/// +/// Use when failure is an anticipated part of normal application or domain flow +/// and callers are expected to branch on a known error state. Examples include a requested resource not being found, +/// a destination no longer existing, or another explicitly modeled business outcome. +/// +/// +/// This type is intentionally not an exception container. Unexpected or exceptional failures should normally propagate +/// as exceptions. When a boundary deliberately needs to capture an exception as part of its result contract, use +/// instead. +/// +/// +/// The default value of is reserved for success. Define a neutral member such as +/// None = 0 when naming that state improves readability, and use non-default values for actual errors. +/// +/// +/// Heavily inspired by the Result type +/// from .NEXT (dotNext). +/// +/// +[Serializable] +public class ProcessResult where TError : struct, Enum +{ + private readonly T _value; + private readonly TError _error; + + /// + /// Initializes a new successful result. + /// + /// The value to be stored as the successful result. + public ProcessResult(T value) + { + _value = value; + _error = default; + } + + /// + /// Initializes a new unsuccessful result containing an expected error. + /// + /// The expected error describing why the operation was unsuccessful. + /// + /// is the default value of , which is reserved for success. + /// + public ProcessResult(TError error) + { + if (EqualityComparer.Default.Equals(error, default)) + { + throw new ArgumentOutOfRangeException(nameof(error), "The default error value is reserved for successful results."); + } + + _value = default!; + _error = error; + } + + /// + /// Extracts the successful value. + /// + /// + /// This result contains an expected error rather than a successful value. + /// + public T Value + { + get + { + Validate(); + return _value; + } + } + + /// + /// Gets the successful value when present; otherwise returns the default value. + /// + /// The successful value, if present; otherwise, default. + public T? ValueOrDefault => IsSuccessful ? _value : default; + + /// + /// Gets the expected error associated with the result. + /// + /// + /// The default value represents success and is not an error. Check before interpreting + /// this property as a failure state. + /// + public TError Error => _error; + + /// + /// Indicates whether the result contains a successful value rather than an expected error. + /// + /// when successful; otherwise, . + public bool IsSuccessful => EqualityComparer.Default.Equals(_error, default); + + /// + /// Returns a string that represents the current result, indicating success or failure and the associated value or error. + /// + public override string ToString() => IsSuccessful ? $"Success({_value})" : $"Failure({_error})"; + + /// + /// Creates a successful containing the specified value. + /// + /// The value to store in the successful result. + /// A representing a successful operation. + public static ProcessResult Success(T value) => new(value); + + /// + /// Creates a failed containing the specified expected error. + /// + /// The expected error describing why the operation was unsuccessful. + /// A representing an expected failure. + /// + /// is the default value of , which is reserved for success. + /// + public static ProcessResult Failure(TError error) => new(error); + + /// + /// Attempts to extract the successful value. + /// + /// The successful value when present; otherwise, the default value. + /// when successful; otherwise, . + public bool TryGet(out T? value) + { + value = IsSuccessful ? _value : default; + return IsSuccessful; + } + + /// + /// Defines an implicit conversion from to . + /// + /// The result to evaluate. + /// + /// if the result is successful; otherwise, . + /// + public static implicit operator bool(ProcessResult result) => result.IsSuccessful; + + /// + /// Defines an explicit conversion from to the underlying value of type . + /// + /// The result to extract the value from. + /// The value contained in the result if it is successful. + /// + /// Thrown if the result is not successful and an attempt is made to extract the value. + /// + public static explicit operator T(ProcessResult result) => result.Value; + + [StackTraceHidden] + private void Validate() + { + if (!IsSuccessful) + { + throw new InvalidOperationException($"The process result is unsuccessful with error '{_error}'."); + } + } +} From c55feee054fac1829ed9fca780d0053f17a0da42 Mon Sep 17 00:00:00 2001 From: Aaron Salisbury Date: Sat, 19 Sep 2026 11:32:51 -0500 Subject: [PATCH 5/6] Refine ProcessResult documentation links --- src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs b/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs index 45b8b27..ac7146f 100644 --- a/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs +++ b/src/RunnethOverStudio.AppToolkit/Core/ProcessResult.cs @@ -98,8 +98,8 @@ public T Value /// The exception representing the failure. Cannot be . /// A representing an exceptional failure. /// - /// Prefer when the failure is an expected outcome that callers - /// should handle without exception semantics. + /// Prefer when the failure is an expected outcome that callers should handle + /// without exception semantics. /// public static ProcessResult Failure(Exception error) => new(error); From 3b1ee86d03f951310d494055f5182de5200dfe46 Mon Sep 17 00:00:00 2001 From: Aaron Salisbury Date: Sat, 19 Sep 2026 11:32:57 -0500 Subject: [PATCH 6/6] Refine expected error result API --- .../Core/ProcessResultOfError.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs b/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs index b4ca598..e90e47f 100644 --- a/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs +++ b/src/RunnethOverStudio.AppToolkit/Core/ProcessResultOfError.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace RunnethOverStudio.AppToolkit.Core; @@ -128,9 +129,9 @@ public T Value /// /// The successful value when present; otherwise, the default value. /// when successful; otherwise, . - public bool TryGet(out T? value) + public bool TryGet([MaybeNullWhen(false)] out T value) { - value = IsSuccessful ? _value : default; + value = _value; return IsSuccessful; }