Hesham.ResultPattern
1.1.2
dotnet add package Hesham.ResultPattern --version 1.1.2
NuGet\Install-Package Hesham.ResultPattern -Version 1.1.2
<PackageReference Include="Hesham.ResultPattern" Version="1.1.2" />
<PackageVersion Include="Hesham.ResultPattern" Version="1.1.2" />
<PackageReference Include="Hesham.ResultPattern" />
paket add Hesham.ResultPattern --version 1.1.2
#r "nuget: Hesham.ResultPattern, 1.1.2"
#:package Hesham.ResultPattern@1.1.2
#addin nuget:?package=Hesham.ResultPattern&version=1.1.2
#tool nuget:?package=Hesham.ResultPattern&version=1.1.2
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>andResult.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. |
StackTraceis 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 | Versions 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. |
-
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"))
```