Trellis.Results
3.0.0-alpha.127
dotnet add package Trellis.Results --version 3.0.0-alpha.127
NuGet\Install-Package Trellis.Results -Version 3.0.0-alpha.127
<PackageReference Include="Trellis.Results" Version="3.0.0-alpha.127" />
<PackageVersion Include="Trellis.Results" Version="3.0.0-alpha.127" />
<PackageReference Include="Trellis.Results" />
paket add Trellis.Results --version 3.0.0-alpha.127
#r "nuget: Trellis.Results, 3.0.0-alpha.127"
#:package Trellis.Results@3.0.0-alpha.127
#addin nuget:?package=Trellis.Results&version=3.0.0-alpha.127&prerelease
#tool nuget:?package=Trellis.Results&version=3.0.0-alpha.127&prerelease
Railway Oriented Programming
Composable error handling for .NET using Result<T>, Maybe<T>, and 10 discriminated error types. No exceptions, no null — just clean pipelines that read like English.
Installation
dotnet add package Trellis.Results
Quick Start
using Trellis;
// Chain operations — errors short-circuit automatically
Result<string> result = GetUser(userId)
.Ensure(user => user.IsActive, Error.Validation("User is not active"))
.Tap(user => _logger.LogInformation("Accessed {Id}", user.Id))
.Map(user => user.Email);
// Pattern match the result
result.Match(
onSuccess: email => Console.WriteLine($"Email: {email}"),
onFailure: error => Console.WriteLine($"Error: {error.Detail}")
);
Core Types
Result<T>
Represents success (with a value) or failure (with an error).
Result<int> success = 42; // Implicit conversion
Result<int> failure = Error.NotFound("Item not found"); // Implicit conversion
if (success.IsSuccess)
Console.WriteLine(success.Value); // 42
Maybe<T>
Domain-level optionality — replaces nullable references.
Maybe<string> some = Maybe.From("hello");
Maybe<string> none = Maybe.None<string>();
string greeting = some.Match(
s => $"Hi, {s}!",
() => "No name");
Error Types
| Error Type | Factory | HTTP Status |
|---|---|---|
ValidationError |
Error.Validation() |
400 |
BadRequestError |
Error.BadRequest() |
400 |
UnauthorizedError |
Error.Unauthorized() |
401 |
ForbiddenError |
Error.Forbidden() |
403 |
NotFoundError |
Error.NotFound() |
404 |
ConflictError |
Error.Conflict() |
409 |
DomainError |
Error.Domain() |
422 |
RateLimitError |
Error.RateLimit() |
429 |
UnexpectedError |
Error.Unexpected() |
500 |
ServiceUnavailableError |
Error.ServiceUnavailable() |
503 |
Pipeline Operations
| Method | Purpose | Example |
|---|---|---|
| Bind | Chain operations that return Result |
.Bind(user => GetOrder(user.Id)) |
| Map | Transform value, keep Result wrapper |
.Map(user => user.Email) |
| Tap | Side effects on success | .Tap(user => Log(user.Id)) |
| TapOnFailure | Side effects on failure | .TapOnFailure(err => Log(err)) |
| Ensure | Validate conditions | .Ensure(u => u.IsActive, error) |
| EnsureAll | Validate all, accumulate errors | .EnsureAll((u => u.IsActive, err1), ...) |
| Match | Pattern match success/failure | .Match(onSuccess, onFailure) |
| MatchError | Pattern match on specific error types | .MatchError(onSuccess, onNotFound, ...) |
| Combine | Merge multiple Results | Result.Combine(r1, r2, r3) |
| Recover | Simple fallback on failure | .Recover(defaultValue) |
| RecoverOnFailure | Fallback on failure | .RecoverOnFailure(err => default) |
| ToMaybe | Convert Result to Maybe | .ToMaybe() — success→Some, failure→None |
| MapOnFailure | Transform errors | .MapOnFailure(err => Error.Domain(...)) |
| When | Conditional operations | .When(u => u.IsPremium, ApplyDiscount) |
| Traverse | Map collection, short-circuit on first failure | items.Traverse(i => Validate(i)) |
All operations have async variants (BindAsync, MapAsync, etc.) with CancellationToken support.
LINQ query syntax is also supported (from, select, where) for both Result<T> and Maybe<T>.
Real-World Example
public Result<Order> PlaceOrder(OrderRequest request)
{
return ProductId.TryCreate(request.ProductId)
.Combine(Quantity.TryCreate(request.Quantity))
.Bind((productId, qty) => _inventory.Reserve(productId, qty))
.Ensure(reservation => reservation.IsConfirmed,
Error.Conflict("Item is out of stock"))
.Map(reservation => Order.FromReservation(reservation))
.Tap(order => _events.Publish(new OrderPlaced(order.Id)));
}
Combine — Collect All Errors
// Validates ALL fields and returns ALL errors at once
var result = FirstName.TryCreate(input.FirstName)
.Combine(LastName.TryCreate(input.LastName))
.Combine(EmailAddress.TryCreate(input.Email))
.Bind((first, last, email) => User.TryCreate(first, last, email));
Performance
11–16 nanoseconds per operation — 0.002% of a typical database query. Zero extra allocations on Combine.
Related Packages
- Trellis.Primitives — Type-safe value objects (EmailAddress, Money, etc.)
- Trellis.DomainDrivenDesign — Aggregate, Entity, ValueObject
- Trellis.Asp — ASP.NET Core integration (Result → HTTP responses)
- Trellis.Http — HttpClient → Result<T> extensions
- Trellis.Analyzers — 19 Roslyn analyzers enforcing ROP best practices
- Trellis.Testing — FluentAssertions extensions for Result<T>
License
MIT — see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- OpenTelemetry.Api (>= 1.15.0)
NuGet packages (10)
Showing the top 5 NuGet packages that depend on Trellis.Results:
| Package | Downloads |
|---|---|
|
Trellis.DomainDrivenDesign
Building blocks for implementing Domain-Driven Design tactical patterns in C# with functional programming principles. Create Aggregate, Entity, and ValueObject classes with Result-based validation. For simple value objects with a single value, ScalarValueObject can be used. For strongly-typed primitives, see Trellis.Primitives. |
|
|
Trellis.Primitives
Infrastructure and ready-to-use implementations for primitive value objects in Domain-Driven Design. Includes base classes (RequiredString, RequiredGuid) with source generation, plus EmailAddress with RFC 5322 validation. Eliminates primitive obsession with strongly-typed domain primitives. |
|
|
Trellis.Asp
These extension methods are used to convert the ROP Result object to ActionResult. If the Result is in a failed state, it returns the corresponding HTTP error code. |
|
|
Trellis.Testing
Testing utilities and assertions for Trellis - FluentAssertions extensions, test builders, and fake implementations for Railway-Oriented Programming |
|
|
Trellis.Stateless
Wraps the Stateless library's Fire() method to return Result instead of throwing on invalid transitions, converting known invalid-transition exceptions into failure results for Railway Oriented Programming. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0-alpha.127 | 31 | 3/23/2026 |
| 3.0.0-alpha.123 | 49 | 3/19/2026 |
| 3.0.0-alpha.118 | 59 | 3/14/2026 |
| 3.0.0-alpha.111 | 57 | 3/12/2026 |
| 3.0.0-alpha.104 | 78 | 3/9/2026 |
| 3.0.0-alpha.100 | 57 | 3/4/2026 |
| 3.0.0-alpha.99 | 47 | 3/4/2026 |
| 3.0.0-alpha.98 | 45 | 3/3/2026 |
| 3.0.0-alpha.95 | 50 | 3/2/2026 |
| 3.0.0-alpha.94 | 56 | 3/2/2026 |
| 3.0.0-alpha.93 | 59 | 3/1/2026 |
| 3.0.0-alpha.92 | 67 | 2/28/2026 |
| 3.0.0-alpha.83 | 60 | 2/27/2026 |