API Reference ResultExtensions - ulfbou/Zentient.Results GitHub Wiki

API Reference: ResultExtensions

1. ResultExtensions

1.1. Overview

  • Namespace: Zentient.Results
  • Type: Static Class
  • Summary: Provides a rich set of extension methods for IResult and IResult<T>, significantly enhancing the library's fluent API for composing operations, handling outcomes, and simplifying common result-related tasks.

1.2. Design Philosophy / Rationale

ResultExtensions centralizes utility functions that operate on IResult and IResult<T> instances. This approach keeps the core Result types lean while providing powerful, readable, and composable operations. It encourages a declarative style of programming by allowing developers to chain actions and transformations on results, reducing the need for verbose if/else checks and promoting a more functional approach to error handling.


2. Extension Methods for IResult

2.1. IsSuccess(this IResult result)

  • Signature: public static bool IsSuccess(this IResult result)
  • Summary: Checks if the operation represented by the IResult was successful.
  • Parameters:
    • result (this IResult): The result instance.
  • Return Value: (bool): true if the result is successful; otherwise, false.
  • Behavior: Returns true if the result's status code is in the 2xx range and its Errors collection is empty.
  • Example Usage:
    IResult opResult = SomeService.PerformAction();
    if (opResult.IsSuccess())
    {
        Console.WriteLine("Operation succeeded!");
    }

2.2. IsFailure(this IResult result)

  • Signature: public static bool IsFailure(this IResult result)
  • Summary: Checks if the operation represented by the IResult failed.
  • Parameters:
    • result (this IResult): The result instance.
  • Return Value: (bool): true if the result is a failure; otherwise, false.
  • Behavior: Returns true if the result's status code is not in the 2xx range OR its Errors collection contains one or more entries. It's the logical opposite of IsSuccess().
  • Example Usage:
    IResult opResult = SomeService.PerformAction();
    if (opResult.IsFailure())
    {
        Console.WriteLine($"Operation failed: {opResult.Error}");
    }

2.3. HasErrorCategory(this IResult result, ErrorCategory category)

  • Signature: public static bool HasErrorCategory(this IResult result, ErrorCategory category)
  • Summary: Determines if a failed result contains at least one error belonging to a specific ErrorCategory.
  • Parameters:
    • result (this IResult): The result instance.
    • category (ErrorCategory): The ErrorCategory to check for.
  • Return Value: (bool): true if the result is a failure and has an error with the specified category; otherwise, false.
  • Example Usage:
    IResult userOp = UserService.UpdateUser(userDto);
    if (userOp.HasErrorCategory(ErrorCategory.Validation))
    {
        Console.WriteLine("User update failed due to validation errors.");
    }

2.4. HasErrorCode(this IResult result, string errorCode)

  • Signature: public static bool HasErrorCode(this IResult result, string errorCode)
  • Summary: Determines if a failed result contains at least one error with a specific error code.
  • Parameters:
    • result (this IResult): The result instance.
    • errorCode (string): The error code to check for.
  • Return Value: (bool): true if the result is a failure and has an error with the specified code; otherwise, false.
  • Example Usage:
    IResult authResult = AuthService.Login("user", "pass");
    if (authResult.HasErrorCode("InvalidCredentials"))
    {
        Console.WriteLine("Invalid username or password.");
    }

2.5. OnSuccess(this IResult result, Action onSuccess)

  • Signature: public static IResult OnSuccess(this IResult result, Action onSuccess)
  • Summary: Executes a side-effecting action if the IResult is successful, then returns the original result.
  • Parameters:
    • result (this IResult): The result instance.
    • onSuccess (Action): The action to execute.
  • Return Value: (IResult): The original IResult instance.
  • Behavior: Useful for logging, auditing, or other actions that should only happen upon success without modifying the result itself.
  • Example Usage:
    IResult saveResult = DataService.SaveData(data);
    saveResult.OnSuccess(() => Console.WriteLine("Data saved successfully."))
              .OnFailure(errors => Console.WriteLine($"Failed to save: {errors[0].Message}"));

2.6. OnFailure(this IResult result, Action<IReadOnlyList<ErrorInfo>> onFailure)

  • Signature: public static IResult OnFailure(this IResult result, Action<IReadOnlyList<ErrorInfo>> onFailure)
  • Summary: Executes a side-effecting action if the IResult is a failure, then returns the original result.
  • Parameters:
    • result (this IResult): The result instance.
    • onFailure (Action<IReadOnlyList<ErrorInfo>>): The action to execute, receiving the list of errors.
  • Return Value: (IResult): The original IResult instance.
  • Behavior: Useful for logging errors, displaying messages, or other actions specific to failure.
  • Example Usage:
    IResult processResult = Processor.Process();
    processResult.OnFailure(errors => {
        foreach (var err in errors) Console.WriteLine($"Error: {err.Message}");
    });

2.7. ToBoolResult(this IResult result)

  • Signature: public static IResult<bool> ToBoolResult(this IResult result)
  • Summary: Converts an IResult (non-generic) into an IResult<bool>.
  • Parameters:
    • result (this IResult): The non-generic result.
  • Return Value: (IResult<bool>): A successful IResult<bool> with true if the original result was successful, or a failed IResult<bool> with false and propagating the original errors and status if it was a failure.
  • Example Usage:
    IResult voidOp = Database.DeleteUser(123);
    IResult<bool> boolResult = voidOp.ToBoolResult();
    if (boolResult.IsSuccess && boolResult.Value) Console.WriteLine("User deleted successfully!");

2.8. ToErrorString(this IResult result, string separator = "; ")

  • Signature: public static string ToErrorString(this IResult result, string separator = "; ")
  • Summary: Concatenates the messages of all ErrorInfo objects in a failed result into a single string.
  • Parameters:
    • result (this IResult): The result instance.
    • separator (string): The string to use as a separator between error messages. Defaults to "; ".
  • Return Value: (string): A combined string of error messages if the result is a failure; otherwise, an empty string.
  • Example Usage:
    IResult loginResult = AuthService.Login("bad", "credentials");
    if (loginResult.IsFailure())
    {
        Console.WriteLine($"Login errors: {loginResult.ToErrorString()}");
    }

2.9. FirstErrorMessage(this IResult result)

  • Signature: public static string? FirstErrorMessage(this IResult result)
  • Summary: Retrieves the message of the first ErrorInfo in a failed result, or null.
  • Parameters:
    • result (this IResult): The result instance.
  • Return Value: (string?): The message of the first error if the result is a failure and has errors; otherwise, null.
  • Behavior: This provides a quick way to get a single error message for simple display, without direct access to the Errors collection.
  • Example Usage:
    IResult purchaseResult = Store.CompletePurchase(item);
    if (purchaseResult.IsFailure())
    {
        Console.WriteLine($"Purchase failed: {purchaseResult.FirstErrorMessage()}");
    }

2.10. Then(this IResult result, Func<IResult> func)

  • Signature: public static IResult Then(this IResult result, Func<IResult> func)
  • Summary: Chains another operation that returns a non-generic IResult, executing it only if the current result is successful.
  • Parameters:
    • result (this IResult): The preceding result.
    • func (Func<IResult>): The function representing the next operation.
  • Return Value: (IResult): The result of func if the current result is successful, or the original failed result if it was a failure.
  • Behavior: This method allows for sequential execution of IResult operations. If the initial result is a failure, func is not executed, and the failure is propagated.
  • Example Usage:
    IResult step1 = Service.DoStep1();
    IResult finalResult = step1.Then(() => Service.DoStep2()); // DoStep2 only runs if DoStep1 succeeded

2.11. Then<TOut>(this IResult result, Func<IResult<TOut>> func)

  • Signature: public static IResult<TOut> Then<TOut>(this IResult result, Func<IResult<TOut>> func)
  • Summary: Chains another operation that returns a generic IResult<TOut>, executing it only if the current non-generic result is successful.
  • Parameters:
    • result (this IResult): The preceding non-generic result.
    • func (Func<IResult<TOut>>): The function representing the next operation that produces a value.
  • Return Value: (IResult<TOut>): The result of func if the current IResult is successful, or a failed IResult<TOut> propagating the original errors and status if it was a failure.
  • Behavior: Similar to Then(Func<IResult>), but for chaining to an operation that returns a value.
  • Example Usage:
    IResult voidOp = UserService.PerformSetup();
    IResult<User> getUserResult = voidOp.Then(() => UserService.GetUser("new-user")); // GetUser only runs if setup succeeded

2.12. ThrowIfFailure(this IResult result)

  • Signature: public static void ThrowIfFailure(this IResult result)
  • Summary: Throws a ResultException if the IResult is a failure.
  • Parameters:
    • result (this IResult): The result instance.
  • Behavior: This method provides an "escape hatch" from the Result pattern. It's typically used at the application's "edge" (e.g., in an API controller or the main application entry point) when a failure is deemed unrecoverable at that level and should escalate to an exception.
  • Exceptions Thrown: ResultException (if result.IsFailure is true).
  • Remarks: Use sparingly and intentionally. The strength of the Result pattern lies in explicit non-exception-based error handling.
  • Example Usage:
    public IActionResult CreateUser([FromBody] UserDto userDto)
    {
        IResult<User> result = _userService.CreateUser(userDto);
        try
        {
            result.ThrowIfFailure(); // If result is a failure, ResultException is thrown
            return Ok(result.Value);
        }
        catch (ResultException ex)
        {
            // Map ResultException.Errors to ProblemDetails for API response
            return BadRequest(new { errors = ex.Errors.Select(e => e.Message) });
        }
    }

3. Extension Methods for IResult<TValue>

3.1. IsSuccess<TValue>(this IResult<TValue> result)

  • Signature: public static bool IsSuccess<TValue>(this IResult<TValue> result)
  • Summary: Checks if the generic IResult<TValue> was successful.
  • Parameters:
    • result (this IResult<TValue>): The generic result instance.
  • Return Value: (bool): true if successful; otherwise, false.
  • Remarks: This is an overload for type inference. Functionally identical to the non-generic IsSuccess but provides explicit type safety for generic results.

3.2. IsFailure<TValue>(this IResult<TValue> result)

  • Signature: public static bool IsFailure<TValue>(this IResult<TValue> result)
  • Summary: Checks if the generic IResult<TValue> failed.
  • Parameters:
    • result (this IResult<TValue>): The generic result instance.
  • Return Value: (bool): true if a failure; otherwise, false.
  • Remarks: Overload for type inference. Functionally identical to the non-generic IsFailure.

3.3. OnSuccess<TValue>(this IResult<TValue> result, Action<TValue> onSuccess)

  • Signature: public static IResult<TValue> OnSuccess<TValue>(this IResult<TValue> result, Action<TValue> onSuccess)
  • Summary: Executes a side-effecting action if the generic IResult<TValue> is successful, passing the Value, then returns the original result.
  • Parameters:
    • result (this IResult<TValue>): The generic result instance.
    • onSuccess (Action<TValue>): The action to execute, receiving the successful value.
  • Return Value: (IResult<TValue>): The original IResult<TValue> instance.
  • Remarks: Functionally identical to Tap on IResult<TValue>.
  • Example Usage:
    IResult<string> loadedData = DataRepository.LoadString();
    loadedData.OnSuccess(data => Console.WriteLine($"Loaded: {data}"))
              .OnFailure(errors => Console.WriteLine($"Failed to load: {errors.First().Message}"));

3.4. OnFailure<TValue>(this IResult<TValue> result, Action<IReadOnlyList<ErrorInfo>> onFailure)

  • Signature: public static IResult<TValue> OnFailure<TValue>(this IResult<TValue> result, Action<IReadOnlyList<ErrorInfo>> onFailure)
  • Summary: Executes a side-effecting action if the generic IResult<TValue> is a failure, then returns the original result.
  • Parameters:
    • result (this IResult<TValue>): The generic result instance.
    • onFailure (Action<IReadOnlyList<ErrorInfo>>): The action to execute, receiving the list of errors.
  • Return Value: (IResult<TValue>): The original IResult<TValue> instance.
  • Example Usage:
    IResult<User> fetchedUser = UserService.FetchUser(userId);
    fetchedUser.OnFailure(errors => Log.Error($"User fetch failed: {errors.ToErrorString()}"));

3.5. Map<TIn, TOut>(this IResult<TIn> result, Func<TIn, TOut> selector)

  • Signature: public static IResult<TOut> Map<TIn, TOut>(this IResult<TIn> result, Func<TIn, TOut> selector)
  • Summary: Transforms the successful value of an IResult<TIn> into a new type TOut if the result is successful.
  • Parameters:
    • result (this IResult<TIn>): The source generic result.
    • selector (Func<TIn, TOut>): A function to apply to the successful Value.
  • Return Value: (IResult<TOut>): A new IResult<TOut> with the transformed value on success, or a propagated failure.
  • Behavior: If the input result is a success, selector is applied to result.Value and a new successful Result<TOut> is returned. If result is a failure, selector is skipped, and a new failed Result<TOut> is returned with the original errors and status.
  • Example Usage:
    IResult<Product> productResult = ProductService.GetProduct(productId);
    IResult<ProductDto> productDtoResult = productResult.Map(product => new ProductDto(product));

3.6. Bind<TIn, TOut>(this IResult<TIn> result, Func<TIn, Task<IResult<TOut>>> next)

  • Signature: public static async Task<IResult<TOut>> Bind<TIn, TOut>(this IResult<TIn> result, Func<TIn, Task<IResult<TOut>>> next)
  • Summary: Asynchronously chains an operation that returns a Task<IResult<TOut>>, executing it only if the current IResult<TIn> is successful.
  • Parameters:
    • result (this IResult<TIn>): The preceding generic result.
    • next (Func<TIn, Task<IResult<TOut>>>): An asynchronous function representing the next operation, taking the successful value and returning a Task of a generic result.
  • Return Value: (Task<IResult<TOut>>): A Task that resolves to the result of next if the current result is successful, or a Task that resolves to a failed IResult<TOut> propagating the original errors and status if it was a failure.
  • Behavior: This enables seamless chaining of asynchronous operations where each step might fail.
  • Example Usage:
    // Assume GetUserAsync and SaveUserAsync return Task<IResult<T>>
    IResult<User> initialUserResult = await UserRepository.GetUserAsync(userId);
    IResult<User> updatedUserResult = await initialUserResult
        .Bind(async user => {
            user.Status = UserStatus.Active;
            return await UserRepository.SaveUserAsync(user);
        });

3.7. Bind(this IResult result, Func<Task<IResult>> next)

  • Signature: public static async Task<IResult> Bind(this IResult result, Func<Task<IResult>> next)
  • Summary: Asynchronously chains an operation that returns a Task<IResult>, executing it only if the current non-generic IResult is successful.
  • Parameters:
    • result (this IResult): The preceding non-generic result.
    • next (Func<Task<IResult>>): An asynchronous function representing the next operation.
  • Return Value: (Task<IResult>): A Task that resolves to the result of next if the current result is successful, or a Task that resolves to the original failed IResult if it was a failure.
  • Behavior: Enables asynchronous sequencing of non-value-producing operations that might fail.
  • Example Usage:
    IResult validationResult = await Validator.ValidateInputAsync(input);
    IResult processingResult = await validationResult.Bind(async () => await Processor.ProcessDataAsync(input));

3.8. Unwrap<TValue>(this IResult<TValue> result)

  • Signature: public static TValue Unwrap<TValue>(this IResult<TValue> result)
  • Summary: Retrieves the successful Value from an IResult<TValue>. If the result is a failure, it throws an InvalidOperationException.
  • Parameters:
    • result (this IResult<TValue>): The generic result instance.
  • Return Value: (TValue): The successful value.
  • Exceptions Thrown: InvalidOperationException (if result.IsFailure is true, with a message detailing the errors).
  • Behavior: A less flexible alternative to GetValueOrThrow(), provided for convenience when immediate unwrapping is desired.
  • Example Usage:
    IResult<string> data = FetchData();
    try
    {
        string value = data.Unwrap(); // Throws if FetchData() returned a failure
        Console.WriteLine(value);
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine($"Error unwrapping: {ex.Message}");
    }

3.9. GetValueOrDefault<TValue>(this IResult<TValue> result, TValue defaultValue)

  • Signature: public static TValue GetValueOrDefault<TValue>(this IResult<TValue> result, TValue defaultValue)
  • Summary: Retrieves the successful Value or returns a specified default value if the IResult<TValue> is a failure or its Value is null.
  • Parameters:
    • result (this IResult<TValue>): The generic result instance.
    • defaultValue (TValue): The value to return if the result is a failure or its Value is null.
  • Return Value: (TValue): The successful value, or the defaultValue.
  • Behavior: Useful for scenarios where you always need a value, even if the primary operation failed, avoiding exceptions.
  • Example Usage:
    IResult<int> countResult = Database.GetItemCount();
    int count = countResult.GetValueOrDefault(0); // If GetItemCount fails, count will be 0

3.10. Then<TIn>(this IResult<TIn> result, Func<TIn, IResult> func)

  • Signature: public static IResult Then<TIn>(this IResult<TIn> result, Func<TIn, IResult> func)
  • Summary: Chains another operation that returns a non-generic IResult, executing it only if the current IResult<TIn> is successful.
  • Parameters:
    • result (this IResult<TIn>): The preceding generic result.
    • func (Func<TIn, IResult>): The function representing the next non-generic operation, taking the successful value.
  • Return Value: (IResult): The result of func if the current IResult<TIn> is successful, or the original failed IResult if it was a failure.
  • Behavior: Similar to Bind, but specifically for chaining from a generic result to a non-generic result.
  • Example Usage:
    IResult<UserData> userData = AuthProvider.Login(credentials);
    IResult processResult = userData.Then(user => ReportingService.LogLogin(user.Id));

3.11. Then<TIn, TOut>(this IResult<TIn> result, Func<TIn, IResult<TOut>> func)

  • Signature: public static IResult<TOut> Then<TIn, TOut>(this IResult<TIn> result, Func<TIn, IResult<TOut>> func)
  • Summary: Chains another operation that returns a generic IResult<TOut>, executing it only if the current IResult<TIn> is successful.
  • Parameters:
    • result (this IResult<TIn>): The preceding generic result.
    • func (Func<TIn, IResult<TOut>>): The function representing the next generic operation, taking the successful value.
  • Return Value: (IResult<TOut>): The result of func if the current IResult<TIn> is successful, or a failed IResult<TOut> propagating the original errors and status if it was a failure.
  • Behavior: An alternative to Map or Bind for chaining generic results, often used for clarity.
  • Example Usage:
    IResult<Order> orderResult = OrderProcessor.CreateOrder(cart);
    IResult<PaymentConfirmation> paymentResult = orderResult.Then(order => PaymentGateway.ProcessPayment(order.Amount));

7. Remarks / Additional Notes

  • Fluent API: The methods in ResultExtensions are designed to be chainable, enabling a highly readable and concise style for expressing complex workflows that involve multiple operations with potential failure points.
  • Separation of Concerns: These extensions keep the core Result and Result<T> structs focused on their fundamental immutability and state, while providing a rich functional API for interaction.
  • Common Patterns: This class encapsulates many common patterns for working with the Result type, reducing boilerplate and promoting consistency across your codebase.

Last Updated: 2025-06-08 Version: 0.3.0

⚠️ **GitHub.com Fallback** ⚠️