ErrorOrX.Generators
2.2.1
See the version list below for details.
dotnet add package ErrorOrX.Generators --version 2.2.1
NuGet\Install-Package ErrorOrX.Generators -Version 2.2.1
<PackageReference Include="ErrorOrX.Generators" Version="2.2.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="ErrorOrX.Generators" Version="2.2.1" />
<PackageReference Include="ErrorOrX.Generators"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add ErrorOrX.Generators --version 2.2.1
#r "nuget: ErrorOrX.Generators, 2.2.1"
#:package ErrorOrX.Generators@2.2.1
#addin nuget:?package=ErrorOrX.Generators&version=2.2.1
#tool nuget:?package=ErrorOrX.Generators&version=2.2.1
ErrorOrX
A discriminated union type for .NET with source-generated ASP.NET Core Minimal API integration. Zero boilerplate, full AOT support.
Installation
dotnet add package ErrorOrX.Generators
This brings in both the source generator and the ErrorOrX runtime library.
| Package | Purpose |
|---|---|
ErrorOrX.Generators |
Source generator + analyzer (references ErrorOrX) |
ErrorOrX |
Runtime types: ErrorOr<T>, Error, ErrorType |
Quick Start
Program.cs
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
app.MapErrorOrEndpoints(); // Auto-registers all endpoints
app.Run();
Define Endpoints
using ErrorOr;
public static class TodoApi
{
[Get("/todos")]
public static ErrorOr<List<Todo>> GetAll(ITodoService svc)
=> svc.GetAll();
[Get("/todos/{id}")]
public static ErrorOr<Todo> GetById(int id, ITodoService svc)
=> svc.GetById(id) is { } todo
? todo
: Error.NotFound("Todo.NotFound", $"Todo {id} not found");
[Post("/todos")]
public static ErrorOr<Todo> Create(CreateTodoRequest req, ITodoService svc)
{
if (string.IsNullOrWhiteSpace(req.Title))
return Error.Validation("Todo.InvalidTitle", "Title is required");
return svc.Create(req); // Returns 201 Created with Location header
}
[Delete("/todos/{id}")]
public static ErrorOr<Deleted> Delete(int id, ITodoService svc)
=> svc.Delete(id) ? Result.Deleted : Error.NotFound("Todo.NotFound", $"Todo {id} not found");
}
ErrorOr Fundamentals
Creating Values and Errors
// Success - implicit conversion
ErrorOr<int> result = 42;
// Errors
ErrorOr<User> notFound = Error.NotFound("User.NotFound", "User not found");
ErrorOr<User> validation = Error.Validation("User.InvalidEmail", "Invalid email format");
// Multiple errors
ErrorOr<User> errors = new List<Error>
{
Error.Validation("User.InvalidName", "Name is required"),
Error.Validation("User.InvalidEmail", "Email is invalid")
};
Checking Results
if (result.IsError)
{
foreach (var error in result.Errors)
Console.WriteLine($"{error.Code}: {error.Description}");
}
else
{
Console.WriteLine(result.Value);
}
Built-in Result Types
ErrorOr<Deleted> DeleteUser(int id) => Result.Deleted; // 204 No Content
ErrorOr<Updated> UpdateUser(int id) => Result.Updated; // 204 No Content
ErrorOr<Created> CreateUser() => Result.Created; // 201 Created
ErrorOr<Success> DoSomething() => Result.Success; // 200 OK
Error Types and HTTP Mapping
| Error Factory | HTTP Status | TypedResult |
|---|---|---|
Error.Validation() |
400 | ValidationProblem |
Error.Unauthorized() |
401 | UnauthorizedHttpResult |
Error.Forbidden() |
403 | ForbidHttpResult |
Error.NotFound() |
404 | NotFound<ProblemDetails> |
Error.Conflict() |
409 | Conflict<ProblemDetails> |
Error.Failure() |
500 | InternalServerError<ProblemDetails> |
Error.Unexpected() |
500 | InternalServerError<ProblemDetails> |
Middleware Attribute Support
The generator detects BCL middleware attributes and emits corresponding fluent calls:
[Post("/admin/users")]
[Authorize("Admin")]
[EnableRateLimiting("fixed")]
public static ErrorOr<User> CreateAdmin(CreateUserRequest req)
{
// Generated code includes:
// .RequireAuthorization("Admin")
// .RequireRateLimiting("fixed")
}
| Attribute | Generated Call |
|---|---|
[Authorize] |
.RequireAuthorization() |
[Authorize("Policy")] |
.RequireAuthorization("Policy") |
[AllowAnonymous] |
.AllowAnonymous() |
[EnableRateLimiting("policy")] |
.RequireRateLimiting("policy") |
[DisableRateLimiting] |
.DisableRateLimiting() |
[OutputCache] |
.CacheOutput() |
[OutputCache(PolicyName = "x")] |
.CacheOutput("x") |
[EnableCors("policy")] |
.RequireCors("policy") |
[DisableCors] |
.DisableCors() |
Fluent API
Chain operations with railway-oriented programming:
// Then - chain dependent operations
ErrorOr<Order> result = ValidateOrder(request)
.Then(order => CheckInventory(order))
.Then(order => ProcessPayment(order))
.Then(order => CreateShipment(order));
// Async chains
var result = await GetUserAsync(id)
.ThenAsync(user => ValidateAsync(user))
.ThenAsync(user => EnrichAsync(user));
// Else - provide fallbacks
User user = GetUser(id).Else(User.Guest);
User user = GetUser(id).Else(errors => HandleErrors(errors));
// Match - handle both cases
string message = GetUser(id).Match(
onValue: user => $"Found: {user.Name}",
onError: errors => $"Error: {errors.First().Description}"
);
// Switch - side effects
GetUser(id).Switch(
onValue: user => SendEmail(user),
onError: errors => LogErrors(errors)
);
Endpoint Attributes
[Get("/path")] // HTTP GET
[Post("/path")] // HTTP POST
[Put("/path")] // HTTP PUT
[Delete("/path")] // HTTP DELETE
[Patch("/path")] // HTTP PATCH
// Route parameters
[Get("/users/{id}")]
public static ErrorOr<User> Get(int id) { }
// Query parameters (automatically bound)
[Get("/users")]
public static ErrorOr<List<User>> Search(int page = 1, string? search = null) { }
// Request body (automatically bound for POST/PUT/PATCH)
[Post("/users")]
public static ErrorOr<User> Create(CreateUserRequest request) { }
// Async endpoints
[Get("/users/{id}")]
public static Task<ErrorOr<User>> GetAsync(int id, CancellationToken ct) { }
Native AOT Support
ErrorOr is fully compatible with Native AOT. The source generator produces reflection-free code that works with
PublishAot=true.
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
For AOT JSON serialization, register your types:
[JsonSerializable(typeof(Todo))]
[JsonSerializable(typeof(List<Todo>))]
public partial class AppJsonContext : JsonSerializerContext { }
// In Program.cs
builder.Services.ConfigureHttpJsonOptions(options =>
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));
Best Practices
Domain-Specific Errors
public static class UserErrors
{
public static Error NotFound(int id) =>
Error.NotFound("User.NotFound", $"User {id} not found");
public static Error DuplicateEmail(string email) =>
Error.Conflict("User.DuplicateEmail", $"Email '{email}' already exists");
}
// Usage
return UserErrors.NotFound(id);
Aggregate Validation Errors
public static ErrorOr<ValidatedRequest> Validate(CreateUserRequest request)
{
var errors = new List<Error>();
if (string.IsNullOrWhiteSpace(request.Name))
errors.Add(Error.Validation("User.Name.Required", "Name is required"));
if (string.IsNullOrWhiteSpace(request.Email))
errors.Add(Error.Validation("User.Email.Required", "Email is required"));
return errors.Count > 0 ? errors : new ValidatedRequest(request);
}
Keep Endpoints Thin
// Delegate to services
[Post("/orders")]
public static ErrorOr<Order> Create(CreateOrderRequest request, IOrderService service)
=> service.CreateOrder(request);
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- ErrorOrX (>= 2.2.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.