Vyrn.Ratatoskr
0.4.0
dotnet add package Vyrn.Ratatoskr --version 0.4.0
NuGet\Install-Package Vyrn.Ratatoskr -Version 0.4.0
<PackageReference Include="Vyrn.Ratatoskr" Version="0.4.0" />
<PackageVersion Include="Vyrn.Ratatoskr" Version="0.4.0" />
<PackageReference Include="Vyrn.Ratatoskr" />
paket add Vyrn.Ratatoskr --version 0.4.0
#r "nuget: Vyrn.Ratatoskr, 0.4.0"
#:package Vyrn.Ratatoskr@0.4.0
#addin nuget:?package=Vyrn.Ratatoskr&version=0.4.0
#tool nuget:?package=Vyrn.Ratatoskr&version=0.4.0
Vyrn.Ratatoskr
A small result library for .NET 10.
Use Result when an operation can succeed or fail. Expected failures become values that your
code can return and handle. Exceptions stay available for unexpected failures.
Packages
| Package | Use it for |
|---|---|
Vyrn.Ratatoskr |
Results, errors, validation, and result pipelines |
Vyrn.Ratatoskr.AspNetCore |
MVC and Minimal API ProblemDetails responses |
Install
Install the core package:
dotnet add package Vyrn.Ratatoskr
For ASP.NET Core, also install:
dotnet add package Vyrn.Ratatoskr.AspNetCore
Quick Start
Return a value when the operation succeeds. Return a Rejection when it fails.
using Vyrn.Ratatoskr;
public Result<Order> FindOrder(string id)
{
var order = orders.Find(order => order.Id == id);
if (order is null)
{
return Rejection.NotFound(
"Orders.NotFound",
$"Order '{id}' was not found.");
}
return order;
}
Use Match to handle both outcomes:
string message = FindOrder("42").Match(
order => $"Found {order.Name}",
error => error.Description);
Create Results
Result.Success();
Result.Success(order);
Result.Failure(Error.Unexpected("Orders.Failed", "The order could not be saved."));
Result.Failure<Order>(Error.NotFound("Orders.NotFound", "The order was not found."));
Rejection is a shorter way to return a failure. It works with Result and Result<T>.
return Rejection.Validation("Orders.Name.Required", "Name is required.");
return Rejection.NotFound("Orders.NotFound", "The order was not found.");
return Rejection.Conflict("Orders.Exists", "The order already exists.");
return Rejection.Unexpected("Orders.Failed", "The order could not be saved.");
Work With Results
| Method | What it does |
|---|---|
Map |
Changes a successful value |
Bind |
Runs the next operation after success |
Match |
Handles success and failure |
Ensure |
Checks a successful value |
Tap |
Runs an action after success |
TapFailure |
Runs an action after failure |
Sequence |
Combines a collection of results |
Traverse |
Transforms a collection with a result-producing function |
UnwrapOr |
Returns the value or a fallback |
UnwrapOrElse |
Creates a fallback from the error |
Failures keep their error in Map, Bind, and Ensure.
public Result<OrderDto> GetOrder(string id)
=> FindOrder(id)
.Ensure(
order => order.IsActive,
Rejection.Validation("Orders.Inactive", "The order is not active."))
.Map(order => new OrderDto(order.Id, order.Name));
Map, Bind, Match, Ensure, Tap, and TapFailure also work on Task<Result> and
Task<Result<T>>:
Result<OrderDto> result = await FindOrderAsync(id)
.Map(order => new OrderDto(order.Id, order.Name));
Validate Input
Use Result.Ensure for one check. Use Result.Validate to run several checks.
Result validation = Result.Validate(
Result.Ensure(
!string.IsNullOrWhiteSpace(name),
Rejection.Validation("Orders.Name.Required", "Name is required.")),
Result.Ensure(
quantity > 0,
Rejection.Validation("Orders.Quantity.Invalid", "Quantity must be greater than zero.")));
One failed check returns its error. Several failed checks return a ValidationError with all
errors in Details.
Combine Results
Use Sequence when you already have a collection of results. It returns all successful values in
source order, or the first failure:
Result<IReadOnlyList<Order>> orders = orderResults.Sequence();
Use Traverse to transform a collection with a result-producing function and combine the results
in one operation:
Result<IReadOnlyList<Order>> orders = orderIds.Traverse(FindOrder);
Both operations stop at the first failure. Exceptions from enumeration or transformation continue normally so the application's exception boundary can handle them.
Define Reusable Errors
Use ErrorCatalog when one area of your application has many errors.
public static class OrderErrors
{
private static readonly ErrorCatalog Catalog = new("Orders");
public static Rejection NotFound(string id)
=> Catalog.NotFound("NotFound", $"Order '{id}' was not found.");
public static Rejection SaveFailed()
=> Catalog.Unexpected("SaveFailed", "The order could not be saved.");
}
Call sites stay short:
return OrderErrors.NotFound(id);
ASP.NET Core
Add the namespace and convert the result in an MVC controller:
using Vyrn.Ratatoskr.AspNetCore;
[HttpGet("{id}")]
public async Task<ActionResult> Get(string id)
{
Result<OrderDto> result = await service.GetOrder(id);
return result.ToActionResult();
}
Default responses:
| Result | HTTP response |
|---|---|
Result.Success() |
204 |
Result.Success(value) |
200 |
| Validation failure | 400 |
| Not found failure | 404 |
| Conflict failure | 409 |
| Dependency failure | 502 |
| Timeout failure | 504 |
| Payload too large | 413 |
| Unsupported media | 415 |
| Unexpected failure | 500 |
Failures use ProblemDetails. The error code and category are in the code and category
extensions. A ValidationError also adds an errors extension. Attached exceptions are never
included in the HTTP response.
Use a success factory when you need another success response:
return result.ToActionResult(
order => CreatedAtAction(nameof(Get), new { id = order.Id }, order));
Minimal APIs keep success and failure mapping explicit:
static async Task<IResult> GetOrder(
string id,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(id))
{
return Rejection.Validation("Orders.Id.Required", "An order ID is required.")
.ToProblemHttpResult();
}
var result = await LoadOrder(id, cancellationToken);
if (result.IsFailure)
{
return result.Error.ToProblemHttpResult();
}
return TypedResults.Ok(result.Value);
}
Use ASP.NET Core's built-in TypedResults for successful Minimal API responses. Check failures
explicitly and convert the error with ToProblemHttpResult():
if (result.IsFailure)
{
return result.Error.ToProblemHttpResult();
}
return TypedResults.Created($"orders/{result.Value.Id}", result.Value);
ProblemHttpResult has a dynamic status. Declare the successful response with .Produces(...)
and add .ProducesProblem(status) for each failure status so OpenAPI describes the complete
endpoint contract.
Showcase
The Pokemon showcase is a small ASP.NET Core application that demonstrates validation, reusable errors, and exception conversion against the public PokeAPI. Its browser UI is one search form that loads a Razor card with HTMX.
Run it locally:
dotnet run --project Samples/Vyrn.Ratatoskr.Showcase
Then open http://localhost:5000 and search for a Pokemon.
Main Types
| Type | Purpose |
|---|---|
Result |
Success or failure without a value |
Result<T> |
Success with a value, or failure |
Error |
Error code, description, type, and optional exception |
ErrorType |
The stable category of an error |
Rejection |
A failure that converts to any result type |
ValidationError |
Several validation errors |
ErrorCatalog |
Reusable errors with a shared code prefix |
More Information
Run the project locally with:
dotnet test vyrn.ratatoskr.slnx
| 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
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Vyrn.Ratatoskr:
| Package | Downloads |
|---|---|
|
Vyrn.Ratatoskr.AspNetCore
ASP.NET Core MVC and Minimal API ProblemDetails mapping for Vyrn.Ratatoskr. |
GitHub repositories
This package is not used by any popular GitHub repositories.