Hesham.ResultPattern 1.1.2

dotnet add package Hesham.ResultPattern --version 1.1.2
                    
NuGet\Install-Package Hesham.ResultPattern -Version 1.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Hesham.ResultPattern" Version="1.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Hesham.ResultPattern" Version="1.1.2" />
                    
Directory.Packages.props
<PackageReference Include="Hesham.ResultPattern" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Hesham.ResultPattern --version 1.1.2
                    
#r "nuget: Hesham.ResultPattern, 1.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Hesham.ResultPattern@1.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Hesham.ResultPattern&version=1.1.2
                    
Install as a Cake Addin
#tool nuget:?package=Hesham.ResultPattern&version=1.1.2
                    
Install as a Cake Tool

ResultPattern

A zero-exception, discriminated union for representing operation outcomes in .NET.
Built around Result<TValue>, Error, and Success — designed for clean, expressive handler code.

🆕 New in this release: Result.Ensure<T> and Result.Combine — aggregate and validate with full error collection, no short-circuiting.


✨ What Makes This Package Different

Most result pattern libraries tell you what went wrong. This one also tells you where.

Every Error created through this library automatically captures a ResultStackTrace at the call site — no extra setup, no manual tracking. The compiler does it for you via [CallerFilePath], [CallerLineNumber], and [CallerMemberName].

[NotFound Error] Restaurant with id '5' not found.
  at DeleteRestaurantCommandHandler.cs:line 14

You get structured diagnostics on every failure, for free.


đŸ“Ļ Installation

dotnet add package ResultPattern

Core Types

Result<TValue>

A sealed generic type that holds either a value on success, or a list of Error objects on failure. It can never be in both states simultaneously.

Member Type Description
Value TValue The success value. Only safe to access when IsSuccess is true.
Errors IReadOnlyList<Error>? The list of errors. null when the result is successful.
IsSuccess bool true when no errors are present.

Implicit conversions allow natural return syntax inside handlers:

Result<int> fromValue  = 42;
Result<int> fromError  = Error.NotFound("Item not found.");
Result<int> fromErrors = new List<Error> { Error.Validation("Name required."), Error.Validation("Email required.") };

Success

A readonly record struct used as the value type for operations that produce no meaningful return value — equivalent to Unit in functional languages.

return Result.Success;

Error — With Built-in Stack Trace

An immutable record representing a categorized failure. Every error automatically captures its call-site context through compiler attributes — you never pass these manually.

Property Type Description
Code string Machine-readable category (e.g. "NotFound").
Description string Human-readable message.
StatusCode HttpStatusCode Maps directly to an HTTP response code.
StackTrace.FileName string Source file where the error was created.
StackTrace.MemberName string Method or property that created the error.
StackTrace.LineNumber int Exact line number of the error creation.

StackTrace is marked [JsonIgnore] — it is for diagnostics only and never leaks into API responses.

Error.ToString() produces a structured diagnostic string:

[NotFound Error] Restaurant with id '5' not found.
  at Handle in DeleteRestaurantCommandHandler.cs:line 14
Available Error Factories
Method HTTP Status Default Description
Error.Failure(...) 500 Internal Server Error "General failure."
Error.Unexpected(...) 500 Internal Server Error "Unexpected error."
Error.Validation(...) 422 Unprocessable Entity "Validation error"
Error.Conflict(...) 409 Conflict "Conflict error"
Error.NotFound(...) 404 Not Found "Not found error"
Error.Unauthorized(...) 401 Unauthorized "Unauthorized error"
Error.Forbidden(...) 403 Forbidden "Forbidden error"
// Only pass description — caller info is auto-filled by the compiler
return Error.NotFound($"Restaurant with id '{id}' not found.");
return Error.Forbidden("You do not have permission to perform this action.");
return Error.Validation("Email address is not valid.");

Static API — Result

Result.Success

Returns a successful Result<Success> for void-like operations.

return Result.Success;

🆕 Result.Ensure<T> — Multi-Rule Validation

Validates a single value against one or more rules in one call.
Each rule is a (Predicate<T> predicate, Error error) tuple.

✅ Predicate returns true = PASS (valid), false = FAIL (error collected).
All predicates are always evaluated. All failures are collected and returned together.

Signature
Result<T> Result.Ensure<T>(T value, params (Predicate<T> predicate, Error error)[] rules)
Example — Validating a Primitive Value

The simplest use case is validating a single field. Notice how method group syntax keeps the rules concise:

var nameValidation = Result.Ensure(name,
    (n => !string.IsNullOrEmpty(n),     Error.Validation("Name cannot be null or empty.")),
    (n => n?.Length >= 3,               Error.Validation("Name must be 3+ characters."))
);
Example — Validating a Domain Object

Ensure works equally well against complex objects, such as checking business rules on an entity before a destructive operation:

var ensureResult = Result.Ensure(restaurant,
    (
        r => restaurantAuthorization.Authorize(r, ResourceOperation.Delete),
        Error.Forbidden($"You are not the owner of restaurant '{request.Id}'.")
    ),
    (
        r => !r.HasActiveOrders,
        Error.Conflict($"Restaurant '{request.Id}' has active orders and cannot be deleted.")
    )
);

if (!ensureResult.IsSuccess)
    return ensureResult.Errors!.ToList();

🆕 Result.Combine — Aggregate Independent Results

Merges a collection of pre-computed IResult instances into one.
Use this when you have results from multiple independent validations and want to surface all failures at once.

â„šī¸ Does not short-circuit. Every result is evaluated before aggregation.

  • All succeed → returns Result.Success
  • Any fail → returns a failed result with every error from every failure combined
Signature
Result<Success> Result.Combine(IEnumerable<IResult> results)
Example — Combining Field Validations

The real power of Combine emerges in factory methods, where you validate each field independently and then merge all violations together before constructing the domain object:

public static Result<User> Create(string name, string email)
{
    var nameValidation = Result.Ensure(name,
        (n => !string.IsNullOrEmpty(n),     Error.Validation("Name cannot be null or empty.")),
        (n => n?.Length >= 3,               Error.Validation("Name must be 3+ characters."))
    );

    var emailValidation = Result.Ensure(email,
        (e => !string.IsNullOrEmpty(e),     Error.Validation("Email cannot be null or empty.")),
        (e => e?.Length >= 3,               Error.Validation("Email must be 3+ characters.")),
        (e => e?.Contains("@"),             Error.Validation("Invalid email address."))
    );

    var combinedResult = Result.Combine([nameValidation, emailValidation]);

    if (!combinedResult.IsSuccess)
        return combinedResult.Errors!.ToList();

    return new User(name, email);
}

If a caller passes both an invalid name and an invalid email, all violations are returned in a single response — the caller never needs to fix one issue just to discover the next.


Match<TNextValue> — Pattern Matching

Project the result into another type based on its state — no if checks required.

Signature
TNextValue Match<TNextValue>(Func<TValue, TNextValue> onValue, Func<List<Error>, TNextValue> onError)
Example — Mapping to a Minimal API Response
return result.Match(
    onValue: _ => Results.NoContent(),
    onError: errors => Results.UnprocessableEntity(errors)
);

Full Handler Example

Combining everything — entity lookup, business rule validation, and a clean single exit point:

public async ValueTask<Result<Success>> Handle(DeleteRestaurantCommand request, CancellationToken ct)
{
    var restaurant = await dbContext.Restaurants
        .FirstOrDefaultAsync(x => x.Id == request.Id, ct);

    if (restaurant is null)
        return Error.NotFound($"Restaurant with id '{request.Id}' not found.");

    var ensureResult = Result.Ensure(restaurant,
        (
            r => restaurantAuthorization.Authorize(r, ResourceOperation.Delete),
            Error.Forbidden($"You are not the owner of restaurant '{request.Id}'.")
        ),
        (
            r => !r.HasActiveOrders,
            Error.Conflict($"Restaurant '{request.Id}' has active orders and cannot be deleted.")
        )
    );

    if (!ensureResult.IsSuccess)
        return ensureResult.Errors!.ToList();

    dbContext.Restaurants.Remove(restaurant);
    await dbContext.SaveChangesAsync(ct);

    return Result.Success;
}

Ensure vs Combine at a Glance

Ensure Combine
Input A single value + validation rules A collection of pre-computed results
Evaluation All predicates always run All results always evaluated
Short-circuits ❌ No ❌ No
Error aggregation ✅ All failures collected ✅ All failures collected
Typical use Single-field or entity validation Merging multiple independent validations
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Hesham.ResultPattern:

Package Downloads
Hesham.ResultPattern.Extension.MinimalAPI

ResultPattern ProblemResult Helper For MinimalAPI

Hesham.ResultPattern.Extension.MVC

ResultPattern ProblemResult Helper For controllers

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.2 112 3/1/2026

## 🚨 **CRITICAL UPDATE - ResultPattern v1.1.2+**

**âš ī¸ BREAKING CHANGE - All `Result.Ensure<T>` users must update code**

### **`Result.Ensure<T>` Predicate Logic Reverted to Intuitive Behavior**

| Version | Predicate returns `true` | Error Collected When |
|---------|--------------------------|----------------------|
| **v1.1.1** *(Old)* | ❌ Invalid | `predicate(value)` *(wrong)* |
| **v1.1.2+** *(New)* | ✅ Valid | `!predicate(value)` *(correct)* |

### 🔄 **Migration Guide**

#### ❌ **Before (v1.1.1)**
```csharp
Result.Ensure(name,
   (n => string.IsNullOrEmpty(n), Error.Validation("Name required"))
```
#### ✅ **After(v1.1.2)**

```csharp

Result.Ensure(name,

   (n => !string.IsNullOrEmpty(n), Error.Validation("Name required"))

```