Zentient Results Api Reference Result Types - ulfbou/Zentient.Results GitHub Wiki
Zentient.Results provides core types to encapsulate the outcome of an operation, clearly distinguishing between success and various forms of failure. These types are designed to be immutable and to support functional composition patterns.
The IResult interface is the non-generic base contract for all operation outcomes within Zentient.Results. It defines common properties to indicate success or failure and provides access to associated errors, messages, and status information. It is primarily used for operations that do not return a specific value upon success, but only convey the outcome of the action itself (e.g., a void method equivalent).
namespace Zentient.Results
{
public interface IResult
{
/// <summary>
/// Gets a value indicating whether the result represents a successful outcome.
/// </summary>
bool IsSuccess { get; }
/// <summary>
/// Gets a value indicating whether the result represents a failed outcome.
/// This is the inverse of <see cref="IsSuccess"/>.
/// </summary>
bool IsFailure { get; }
/// <summary>
/// Gets a read-only list of structured error information.
/// This list will be empty if <see cref="IsSuccess"/> is <c>true</c>.
/// </summary>
IReadOnlyList<ErrorInfo> Errors { get; }
/// <summary>
/// Gets a read-only list of general messages associated with the result,
/// which can be present for both successful and failed outcomes.
/// </summary>
IReadOnlyList<string> Messages { get; }
/// <summary>
/// Gets the message of the first <see cref="ErrorInfo"/> in the <see cref="Errors"/> list, if any.
/// Returns <c>null</c> if no errors are present or if the result is successful.
/// </summary>
string? Error { get; }
/// <summary>
/// Gets the overall status of the operation, providing a structured
/// representation of the outcome (e.g., HTTP status code mapping).
/// </summary>
IResultStatus Status { get; }
}
}-
IsSuccess:bool-
Description: Returns
trueif the operation completed successfully; otherwise,false. A result is considered successful if itsStatusindicates success (typically a 2xx status code) and itsErrorscollection is empty. - Usage: Useful for quick checks in conditional logic.
-
Example:
if (result.IsSuccess) { /* handle success */ }
-
Description: Returns
-
IsFailure:bool-
Description: Returns
trueif the operation failed; otherwise,false. This is always the logical negation ofIsSuccess. - Usage: Provides an alternative way to check for failure.
-
Example:
if (result.IsFailure) { /* handle failure */ }
-
Description: Returns
-
Errors:IReadOnlyList<ErrorInfo>-
Description: A collection of
ErrorInfoobjects, each providing structured details about a specific error that occurred during the operation. This list will be empty for successful results. - Usage: Iterate through this collection to display or log detailed error information.
- See Also: ErrorInfo Struct
-
Example:
foreach (var error in result.Errors) { Console.WriteLine($"{error.Code}: {error.Message}"); }
-
Description: A collection of
-
Messages:IReadOnlyList<string>-
Description: A collection of general informational or warning messages associated with the result. These messages can be present for both successful and failed outcomes and are typically less critical than
Errors. - Usage: Display user-friendly messages regardless of success or failure.
-
Example:
result.Messages.ForEach(m => Console.WriteLine(m));
-
Description: A collection of general informational or warning messages associated with the result. These messages can be present for both successful and failed outcomes and are typically less critical than
-
Error:string?-
Description: A convenience property that returns the
Messageof the firstErrorInfoin theErrorslist. If theErrorslist is empty (e.g., for a successful result), it returnsnull. - Usage: Quick access to a primary error message for display.
-
Example:
Console.WriteLine($"First error: {result.Error}");
-
Description: A convenience property that returns the
-
Status:IResultStatus-
Description: Represents the overall status of the operation. This can include a numeric code (like an HTTP status code) and a descriptive message. The status is crucial for categorizing the outcome (e.g.,
Ok,NotFound,ValidationFailure). - Usage: Determine the semantic outcome of the operation beyond just success/failure.
- See Also: IResultStatus Interface, ResultStatuses Static Class
-
Example:
if (result.Status == ResultStatuses.NotFound) { /* specific handling */ }
-
Description: Represents the overall status of the operation. This can include a numeric code (like an HTTP status code) and a descriptive message. The status is crucial for categorizing the outcome (e.g.,
The IResult<T> interface extends IResult by adding the capability to encapsulate a successful value of type T. It is the primary interface for operations that are expected to return data upon success, while still providing robust error handling for failure scenarios.
namespace Zentient.Results
{
public interface IResult<T> : IResult
{
/// <summary>
/// Gets the successful value encapsulated by the result.
/// This property will be <c>null</c> or <c>default(T)</c> if <see cref="IsFailure"/> is <c>true</c>.
/// </summary>
T? Value { get; }
/// <summary>
/// Retrieves the value if the result is successful, otherwise throws an <see cref="InvalidOperationException"/>.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the result is a failure.</exception>
T GetValueOrThrow();
/// <summary>
/// Retrieves the value if the result is successful, otherwise throws an <see cref="InvalidOperationException"/>
/// with a custom message.
/// </summary>
/// <param name="message">The custom message for the exception.</param>
/// <exception cref="InvalidOperationException">Thrown if the result is a failure.</exception>
T GetValueOrThrow(string message);
/// <summary>
/// Retrieves the value if the result is successful, otherwise throws an exception created by the provided factory.
/// </summary>
/// <param name="exceptionFactory">A function that returns an exception to be thrown on failure.</param>
/// <exception cref="Exception">The exception created by the provided factory.</exception>
T GetValueOrThrow(Func<Exception> exceptionFactory);
/// <summary>
/// Retrieves the value if the result is successful, otherwise returns a specified fallback value.
/// </summary>
/// <param name="fallback">The value to return if the result is a failure.</param>
/// <returns>The successful value or the fallback value.</returns>
T GetValueOrDefault(T fallback);
/// <summary>
/// Transforms the successful value of the result to a new result type <typeparamref name="U"/>
/// using the provided selector function. If the current result is a failure, it propagates the failure.
/// This is useful for chaining operations that return <see cref="IResult{U}"/>.
/// </summary>
/// <typeparam name="U">The type of the new result's value.</typeparam>
/// <param name="selector">A function to transform the successful value.</param>
/// <returns>A new <see cref="IResult{U}"/> instance.</returns>
IResult<U> Map<U>(Func<T, U> selector);
/// <summary>
/// Flat-maps the successful value of the result to a new <see cref="IResult{U}"/>
/// using the provided binder function. If the current result is a failure, it propagates the failure.
/// This is useful for chaining operations that themselves return <see cref="IResult{U}"/>.
/// </summary>
/// <typeparam name="U">The type of the new result's value.</typeparam>
/// <param name="binder">A function to transform the successful value into a new result.</param>
/// <returns>A new <see cref="IResult{U}"/> instance.</returns>
IResult<U> Bind<U>(Func<T, IResult<U>> binder);
/// <summary>
/// Executes a side-effect action if the result is successful, then returns the original result.
/// </summary>
/// <param name="onSuccess">The action to execute if the result is successful.</param>
/// <returns>The original <see cref="IResult{T}"/> instance.</returns>
IResult<T> Tap(Action<T> onSuccess);
/// <summary>
/// Executes an action if the result is successful, then returns the original result.
/// This is an alias for <see cref="Tap(Action{T})"/>.
/// </summary>
/// <param name="action">The action to execute on success.</param>
/// <returns>The original <see cref="IResult{T}"/> instance.</returns>
IResult<T> OnSuccess(Action<T> action);
/// <summary>
/// Executes an action if the result is a failure, then returns the original result.
/// </summary>
/// <param name="action">The action to execute on failure, receiving the list of errors.</param>
/// <returns>The original <see cref="IResult{T}"/> instance.</returns>
IResult<T> OnFailure(Action<IReadOnlyList<ErrorInfo>> action);
/// <summary>
/// Transforms the result into a new type <typeparamref name="U"/> by applying one of two functions,
/// depending on whether the result is successful or a failure.
/// </summary>
/// <typeparam name="U">The type to which the result will be transformed.</typeparam>
/// <param name="onSuccess">The function to apply if the result is successful, receiving the value.</param>
/// <param name="onFailure">The function to apply if the result is a failure, receiving the list of errors.</param>
/// <returns>The transformed value of type <typeparamref name="U"/>.</returns>
U Match<U>(Func<T, U> onSuccess, Func<IReadOnlyList<ErrorInfo>, U> onFailure);
}
}-
Value:T?-
Description: The successful value encapsulated by the result. This property will be
default(T)(e.g.,nullfor reference types,0forint, etc.) ifIsFailureistrue. AccessingValuewhenIsFailureistrueis a common source of bugs if not handled carefully. Always checkIsSuccessor use helper methods likeGetValueOrThrow()orGetValueOrDefault(). - Usage: Retrieve the actual data from a successful result.
-
Example:
if (result.IsSuccess) { var data = result.Value; }
-
Description: The successful value encapsulated by the result. This property will be
(Detailed explanations and practical examples for Map, Bind, Then, Tap, OnSuccess, OnFailure, and Match are provided in the Functional Utilities page. Below are brief descriptions focusing on their core purpose.)
-
GetValueOrThrow()(and overloads)-
Description: Retrieves the
ValueifIsSuccessistrue. IfIsFailureistrue, it throws anInvalidOperationException(or a custom exception with overloads). - Usage: Use when you are certain the result will be successful, or when a failure should explicitly halt execution (e.g., during startup configuration).
- See Also: Inspection & Querying
-
Description: Retrieves the
-
GetValueOrDefault(T fallback)-
Description: Retrieves the
ValueifIsSuccessistrue. IfIsFailureistrue, it returns the providedfallbackvalue instead. - Usage: Provides a safe default value for failure scenarios, preventing exceptions.
- See Also: Inspection & Querying
-
Description: Retrieves the
-
Map<U>(Func<T, U> selector)-
Description: Transforms a successful
IResult<T>into anIResult<U>by applying aselectorfunction to theValue. If the original result was a failure, the failure is propagated without executing theselector. - Usage: Chain operations where each step produces a different successful value, but the overall result can still fail.
- See Also: Map Extension Methods
-
Description: Transforms a successful
-
Bind<U>(Func<T, IResult<U>> binder)-
Description: Flat-maps a successful
IResult<T>into anIResult<U>by applying abinderfunction that itself returns anIResult<U>. If the original result was a failure, the failure is propagated. -
Usage: Chain operations where each step also produces a
Resulttype, allowing for sequential validation or processing. - See Also: Bind Extension Methods
-
Description: Flat-maps a successful
-
Tap(Action<T> onSuccess)/OnSuccess(Action<T> action)-
Description: Executes a side-effect
Actionif the result is successful, then returns the original result instance.OnSuccessis an alias forTap. - Usage: For logging, auditing, or other side-effects that don't change the result's value or success/failure state.
- See Also: Tap Extension Method, OnSuccess Extension Methods
-
Description: Executes a side-effect
-
OnFailure(Action<IReadOnlyList<ErrorInfo>> action)-
Description: Executes an
Actionif the result is a failure, receiving the list of errors, then returns the original result instance. - Usage: For logging errors, displaying error messages to the user, or other side-effects specific to failure scenarios.
- See Also: OnFailure Extension Methods
-
Description: Executes an
-
Match<U>(Func<T, U> onSuccess, Func<IReadOnlyList<ErrorInfo>, U> onFailure)-
Description: Transforms the result into a new type
Uby applying one of two functions:onSuccessifIsSuccessistrue, oronFailureifIsFailureistrue. This forces exhaustive handling of both success and failure paths. -
Usage: Convert a
Resultinto a UI model, a different data structure, or handle side-effects in a single expression. - See Also: Match Extension Method
-
Description: Transforms the result into a new type
The Result struct is the immutable, non-generic concrete implementation of the IResult interface. It's used for operations that only need to convey success or failure, without an associated return value. It provides a rich set of static factory methods for creating results.
namespace Zentient.Results
{
public readonly struct Result : IResult, IEquatable<Result>
{
// Properties inherited from IResult (IsSuccess, IsFailure, Errors, Messages, Error, Status)
// Internal constructor, not for public direct use.
// Static factory methods for creation (e.g., Result.Success(), Result.Failure())
// Implicit operator for ErrorInfo conversion
// Overrides for Equals, GetHashCode
// ...
}
}-
Immutable: Once a
Resultinstance is created, its internal state (success/failure, errors, messages, status) cannot be changed. This promotes predictability and thread-safety. -
Value Type: Being a
readonly struct,Resultinstances are allocated on the stack (or inline in objects), reducing garbage collection pressure compared to reference types. -
Factory-driven Creation:
Resultinstances are almost exclusively created using the static factory methods provided on theResultclass (e.g.,Result.Success(),Result.Failure()). Direct instantiation vianew Result(...)is discouraged and often not possible due to internal constructors. -
Equality:
ResultimplementsIEquatable<Result>, allowing for value-based equality comparisons.
The Result struct provides a comprehensive set of static factory methods to create various types of successful or failed results. These are detailed in the Result Builders & Factories page. Examples include:
-
Result.Success(): Creates a successful result. -
Result.Success(string message): Creates a successful result with an associated message. -
Result.Failure(ErrorInfo error): Creates a failed result with a singleErrorInfo. -
Result.Validation(IEnumerable<ErrorInfo> errors): Creates a failed result specifically for validation errors. -
Result.NotFound(string resourceName): Creates a failed result indicating a resource was not found.
For convenience, Result supports implicit conversions:
-
ErrorInfotoResult: Allows you to directly return anErrorInfoobject where aResultis expected, which will implicitly convert to a failedResult.public Result DoSomething() { return new ErrorInfo(ErrorCategory.BusinessLogic, "InvalidState", "System is in invalid state."); }
The Result<T> struct is the immutable, generic concrete implementation of the IResult<T> interface. It is designed for operations that return a specific value of type T upon success, alongside comprehensive error handling for failure scenarios. It combines the benefits of the Result struct with the ability to carry a payload.
namespace Zentient.Results
{
public readonly struct Result<T> : IResult<T>, IEquatable<Result<T>>
{
// Properties inherited from IResult and IResult<T> (Value, IsSuccess, IsFailure, Errors, Messages, Error, Status)
// Internal constructor, not for public direct use.
// Static factory methods for creation (e.g., Result.Success<T>(value), Result.Failure<T>(errors))
// Implicit operator for T value conversion
// Overrides for Equals, GetHashCode
// Implementations of IResult<T> methods (Map, Bind, GetValueOrThrow, etc.)
// ...
}
}-
Immutable: Like the non-generic
Result,Result<T>instances are immutable. -
Value Type: As a
readonly struct, it offers performance benefits by reducing heap allocations and GC overhead. -
Factory-driven Creation: Created via static factory methods, often on the non-generic
Resultclass (e.g.,Result.Success<T>(value)) or implicit conversions. -
Payload Carrying: Safely encapsulates a value of type
Twhen the operation is successful. -
Equality:
Result<T>implementsIEquatable<Result<T>>, enabling value-based equality comparisons, considering both the success/failure state, errors, and theValue(if successful).
The Result<T> type also leverages the static factory methods from the Result class (or its own implicit conversions) to create instances. These are also covered in detail on the Result Builders & Factories page. Examples include:
-
Result.Success<T>(T value): Creates a successful result with a value. -
Result.Failure<T>(ErrorInfo error): Creates a failed result forResult<T>. -
Result.NotFound<T>(string resourceName): Creates a typed failure.
For highly fluent syntax, Result<T> supports implicit conversions:
-
TtoResult<T>: Allows you to directly return a value of typeTwhere aResult<T>is expected, which will implicitly convert to a successfulResult<T>.public IResult<int> ParseNumber(string text) { if (int.TryParse(text, out int number)) { return number; // Implicitly converts to Result<int>.Success(number) } return Result.Validation<int>(new ErrorInfo[] { new ErrorInfo(ErrorCategory.Validation, "InvalidNumber", "Input is not a valid number.") }); }
-
ErrorInfotoResult<T>: Similar to the non-genericResult, anErrorInfocan implicitly convert to a failedResult<T>.public IResult<User> GetUserById(int id) { if (id <= 0) { return new ErrorInfo(ErrorCategory.Validation, "InvalidId", "User ID must be positive."); // Implicitly converts to Result<User>.Failure } // ... logic return Result.NotFound<User>("User", id.ToString()); // Explicit factory method example }
Result<T> implements the core functional methods defined in IResult<T>, such as Map, Bind, Match, Tap, OnSuccess, and OnFailure. These methods are critical for chaining operations and handling outcomes in a functional style.
- See Also: For detailed explanations and examples of how to use these powerful methods, refer to the Functional Utilities page.