π Languages:
π¬π§ English (this file) | π«π· FranΓ§ais
Turn your exceptions into structured, living knowledge about your system.
FirstClassErrors is a .NET library that treats errors as first-class, documented, and diagnosable concepts β not just strings thrown at runtime.
It helps you:
- express errors in a consistent and structured way
- attach meaningful diagnostics to each error
- keep error documentation close to the code
- generate human-readable error documentation automatically
In most systems, errors are:
- scattered across the codebase
- described by ad-hoc messages
- poorly documented
- hard to troubleshoot
- disconnected from support and operations
Over time, this leads to:
- duplicated investigations
- tribal knowledge
- support teams guessing
- developers reinventing error explanations
What if:
Every error in your system was explicitly described, structured, and documented β directly in code β and that documentation could be generated automatically?
FirstClassErrors introduces:
- a rich error model
- a structured diagnostic system
- a DSL to document errors
- a documentation extraction pipeline
Errors become:
not just failures, but documented knowledge units.
The Error carries:
- a stable error code
- a timestamp
- three distinct messages β a mandatory public short summary, an optional public detail, and a mandatory internal diagnostic message
- contextual data
- structured diagnostics
The exception itself only exposes its .Error, and is designed to be:
- logged consistently
- understood by humans
- used by tooling
An Error carries three messages but deliberately splits them across just two audiences β a public one (end users / API clients) and an internal one (logs, support, developers). The separation is enforced by construction, so what reaches a caller can never leak what is meant for developers and support:
| Message | Mandatory | Audience | Exposure |
|---|---|---|---|
ShortMessage |
Yes | End users / API clients | A short public summary, safe to surface as-is (e.g. the title of an RFC 9457 problem detail). |
DetailedMessage |
No | End users / API clients | A controlled public detail (e.g. the detail of an RFC 9457 problem detail), exposed only when the application explicitly chooses to. Must not carry sensitive or internal information. |
DiagnosticMessage |
Yes | Logs, support, developers | The internal diagnostic message. May contain technical/operational detail (identifiers, offending values, internal state); it is never exposed to external clients by default. |
The core model is HTTP-agnostic: DiagnosticMessage is never used as a default HTTP response body. When you turn an error into an exception with .ToException(), the resulting Exception.Message is the DiagnosticMessage β the developer- and log-facing text.
Each error can declare possible causes and analysis leads:
- What might have caused this error?
- Is it likely input-related, system-related, or both?
- Where should investigation start?
Diagnostics guide troubleshooting without hardcoding operational processes.
Errors are documented directly in code using a fluent API:
return DescribeError.WithTitle("Temperature below absolute zero")
.WithDescription("This error occurs when trying to instantiate a temperature with a value that is below absolute zero.")
.WithRule("Temperature cannot go below absolute zero because absolute zero is the point where particles have minimum possible energy.")
.WithDiagnostics(ValueObjectDiagnostic.Diagnostic)
.WithExamples(
() => BelowAbsoluteZero(-1, TemperatureUnit.Kelvin),
() => BelowAbsoluteZero(-280, TemperatureUnit.Celsius));This is not just comments β it is structured, executable documentation.
The library includes a mechanism to scan assemblies and extract all declared error documentation:
- linked to error factory classes
- linked to factory methods
- enriched with examples
- ready to be rendered
This enables:
- Markdown or JSON error catalogs (or any custom format via a renderer)
- support-oriented documentation
- living documentation generated from code
- multi-language catalogs (opt-in) β see Internationalization
The library supports both:
- throwing errors (traditional exception flow)
- transporting errors without throwing via
OutcomeandOutcome<T>
This allows you to use exceptions as:
runtime signals or structured error data
depending on the context (domain logic, validation, pipelines, etc.).
From the FirstClassErrors.Usage project:
[ProvidesErrorsFor(nameof(Temperature))]
public static class InvalidTemperatureError {
[DocumentedBy(nameof(BelowAbsoluteZeroDocumentation))]
internal static DomainError BelowAbsoluteZero(decimal invalidValue, TemperatureUnit invalidValueUnit) {
return DomainError.Create(
Code.TemperatureBelowAbsoluteZero,
diagnosticMessage: $"Failed to instantiate temperature: the value {invalidValue} {invalidValueUnit} is below absolute zero.")
.WithPublicMessage(
shortMessage: "Temperature is invalid.",
detailedMessage: $"The temperature {invalidValue} {invalidValueUnit} is below absolute zero.");
}
private static ErrorDocumentation BelowAbsoluteZeroDocumentation() {
return DescribeError.WithTitle("Temperature below absolute zero")
.WithDescription("This error occurs when trying to instantiate a temperature with a value that is below absolute zero.")
.WithRule("Temperature cannot go below absolute zero because absolute zero is the point where particles have minimum possible energy.")
.WithDiagnostics(ValueObjectDiagnostic.Diagnostic)
.WithExamples(
() => BelowAbsoluteZero(-1, TemperatureUnit.Kelvin),
() => BelowAbsoluteZero(-280, TemperatureUnit.Celsius));
}
private static class Code {
public static readonly ErrorCode TemperatureBelowAbsoluteZero = ErrorCode.Create("TEMPERATURE_BELOW_ABSOLUTE_ZERO");
}
}The factory returns a structured Error. When you need to throw it, you turn it into an exception with .ToException():
throw InvalidTemperatureError.BelowAbsoluteZero(-1, TemperatureUnit.Kelvin).ToException();Here, the error, its meaning, its rule, its diagnostics, and example messages are all defined together β in code.
FirstClassErrors is especially useful if:
- you build complex business systems
- you care about supportability
- you want consistent error handling
- you want documentation that doesnβt drift from code
- you design with domain-driven thinking
FirstClassErrors ships with a set of Roslyn analyzers (rule ids FCExxx) bundled in the NuGet package β reference the package and they run at build time, no extra install. They catch, before you run anything, the mistakes the runtime or the documentation pipeline would otherwise surface late or silently: duplicate error codes, [DocumentedBy] references that don't resolve, documented errors that never reach the catalog, and more.
See the analyzer rules reference.
See the full documentation:
- Getting Started
- Design Principles
- When Not to Use FirstClassErrors
- Core Concepts
- Error Context Guide
- Writing Errors Guide
- Usage Patterns
- Best Practices
- CI/CD and Operational Integration
- Architecture of the Documentation Pipeline
- Writing a custom renderer
- Internationalization
- Comparison with error-handling libraries
- Analyzer rules (FCExxx)
- FAQ
