diagweave
Type-safe error algebra and runtime diagnostic reports for Rust
diagweave unifies three layers that are often split across different crates:
- Type layer:
set!/union!for composable, strongly-typed error modeling - Propagation layer:
Reportfor context, attachments, events, stack trace, and source chain - Presentation layer:
Compact/Pretty/Json, plus tracing/telemetry export
Table of Contents
- diagweave
Why diagweave
In many Rust projects, error modeling, propagation context, and rendering are handled by separate tools. diagweave keeps them on one consistent data model:
- what failed
- what runtime context came with the failure
- how to render/export it for humans and systems
Benefits:
- less manual nested enum boilerplate
- structured diagnostics instead of string-only errors
- chain-friendly context enrichment near the failure site
- one output pipeline for text, JSON, and observability sinks
Installation
[]
= "0.1"
If you do not need default features:
[]
= { = "0.1", = false }
With default-features = false, diagweave supports no_std + alloc.
Quick Start
use ;
set!
Core Concepts
set!
Define structured error sets for module/domain-local modeling.
union!
Compose multiple sets and external error types into one boundary error.
Report
Wrap an error value and enrich it with runtime diagnostics.
set!
Basic example:
use set;
set!
Generated constructors:
AuthError::invalid_token(user_id)AuthError::permission_denied(role)AuthError::timeout()- report helpers:
*_report(...)
Custom constructor prefix:
use set;
set!
let e = new_invalid_token;
let r = new_invalid_token_report;
Custom report path:
use set;
#
set!
#[display(transparent)] and #[from] on tuple variants are supported and require exactly one field.
Additional notes:
- enum visibility follows the
set!declaration (pub,pub(crate), or private) - top-level attributes on the
set!enum are preserved - auto helpers:
to_report(),source(), anddiag()on the enum
union!
use ;
set!
union!
Highlights:
- auto-
From<T>for listed external types - display delegation for wrapped external errors
as Aliasfor variant naming override- auto
Errorimplementation and autoDebugbackfill - generated constructors and
*_reporthelpers (same asset!) - supports
#[diagweave(constructor_prefix = "...", report_path = "...")] - auto helpers:
to_report(),source(), anddiag()on the enum
Standalone #[derive(Error)]
use Error;
Supports #[display(...)], #[display(transparent)], #[from], and #[source], plus to_report() integration.
Report and chain APIs
From Result<T, E>:
to_report()to_report_note(message)
Common enrichers on Result<T, Report<E>>:
and_then_report(|r| r.with_ctx(key, value).with_severity(...))— apply any chain ofReportmethods on the error path
Hot-path string fields like category, trace_state, and trace event names are stored with StaticRefStr after capture.
Attachment keys, payload names, payload media types, global context keys, and other stored string metadata also use StaticRefStr.
The matching setters accept impl Into<StaticRefStr>, so callers can pass owned shared strings without an extra copy.
map_err() is the recommended entry point for error type transformation; whether it accumulates the origin source chain is controlled by ReportOptions (debug: enabled, release: disabled by default).
Read APIs on Report<E>:
attachments(),metadata(),stack_trace()context() -> &ContextMap,system() -> &ContextMaperror_code(),severity(),category(),retryable()visit_causes(visit)/visit_causes_ext(options, visit)visit_origin_sources(visit)/visit_origin_src_ext(options, visit)visit_diag_sources(visit)/visit_diag_srcs_ext(options, visit)iter_origin_sources()/iter_origin_src_ext(options)iter_diag_sources()/iter_diag_srcs_ext(options)options()— read currentReportOptionsset_options(options: ReportOptions)— replace report optionsset_accumulate_src_chain(accumulate: bool)— quick toggle formap_err()source chain accumulation
Attachment note access:
Attachment::as_note() -> Option<String>(materialized text view)Attachment::as_note_display() -> Option<&(dyn Display + Send + Sync + 'static)>(zero-allocation display view)
Read APIs on Result<T, Report<E>> via InspectReportExt:
report_ref(),report_metadata(),report_attachments()report_error_code(),report_severity(),report_category(),report_retryable()
ErrorCode design:
- dual representation:
Integer(i64)orString(StaticRefStr) - write path:
set_error_code(x)orwith_error_code(x)acceptsimpl Into<ErrorCode> set_error_code(x)replaces existing value;with_error_code(x)only sets if not already set- integer inputs that fit in
i64are stored asInteger; overflow falls back to decimalString - read path:
TryFrom<ErrorCode>/TryFrom<&ErrorCode>to integer types (i8..i128,u8..u128,isize,usize) - string path:
Into<String>andto_string()are both supported
AttachmentValue::String also uses StaticRefStr internally, so repeated report wrapping can reuse string payloads without copying.
- integer parse failures return
ErrorCodeIntError::{InvalidIntegerString, OutOfRange}
Cause semantics:
with_display_cause/with_display_causesacceptimpl Display + Send + Sync + 'staticand append display-cause strings (for rendering/IR).with_diag_src_errappends explicit error objects into the diagnostic source chain, requiringimpl Error + Send + Sync + 'static.- Origin source propagation is maintained by
map_err()andError::source(); whethermap_err()continues to chain the old inner error is controlled byReportOptions.accumulate_src_chain.
Global context injector (std):
Trace context uses validated IDs:
TraceId::from_str("32-hex")/SpanId::from_str("16-hex")/ParentSpanId::from_str("16-hex")unsafe { TraceId::new_unchecked(...) }to skip validation
Rendering and export
Built-in renderers:
use ;
# use set;
# use Report;
# set!
# let report = new;
let _ = report.render.to_string;
let _ = report.render.to_string;
Rendering presets:
use ReportRenderOptions;
let dev = developer; // full details, unfiltered stack traces
let prod = production; // trace event details, app-only frames
let minimal = minimal; // core info only, focused stack traces
Stack trace filtering (StackTraceFilter):
All— show every frame (default)AppOnly— filter outstd::/core::/alloc::/backtrace::framesAppFocused— additionally filter outdiagweave::and diagnostic-internal frames
IR and adapters:
# use set;
# use ReportRenderOptions;
# use ;
# set!
# let report = new
# .with_severity;
let ir = report.to_diagnostic_ir;
let tracing_fields = ir.to_tracing_fields;
assert!;
let otel = ir.to_otel_envelope;
DiagnosticIr and the tracing/OTEL adapter outputs are borrow-first views: string fields use RefStr<'a> where possible and only materialize owned strings when a projected value cannot safely borrow from the source report. OTEL export is intentionally gated to DiagnosticIr<'_, HasSeverity>, so reports must set an explicit severity before producing an OTEL envelope.
to_otel_envelope(config) accepts an OtelEnvelopeConfig so callers can keep compatibility output or opt into a shared namespace such as diagweave.otel. Diagweave-owned keys are namespaced by the config, while OTEL semantic-convention keys like exception.type remain unchanged.
DiagnosticIr keeps render-stable header/metadata plus aggregate counters:
use ReportRenderOptions;
# use ;
#
# ;
#
#
# let report = new
# .with_ctx
# .attach_printable
# .attach_payload
# .with_display_cause
# .with_diag_src_err;
let ir = report.to_diagnostic_ir;
let context_count = ir.context.len;
let attachment_count = ir.attachments.len;
println!;
Use Report::visit_attachments(...) if you need streaming access to per-item context/note/payload entries.
JSON renderer (json feature):
JSON output includes schema_version: "v0.1.0".
- Schema:
diagweave/schemas/report-v0.1.0.schema.json - Doc:
docs/report-json-schema-v0.1.0.md
OTEL schema
OpenTelemetry envelope output is documented separately and requires the otel feature.
- Schema:
diagweave/schemas/report-otel-v0.1.0.schema.json - Doc:
docs/report-otel-schema-v0.1.0.md
The OTEL adapter keeps the report tree structured where possible:
- the main
exceptionrecord carries a structuredbodyinstead of a plain string exception.stacktraceis exported as aKvListdiagnostic_bag.origin_source_errors / diagnostic_bag.diagnostic_source_errorspreservemessage;typeis emitted only when present- empty
trace/context/attachmentssections are omitted
When you pass a namespace in OtelEnvelopeConfig, diagweave-owned keys such as context, system, attachment, and diagnostic_bag are emitted under that namespace.
Tracing export:
Advanced patterns from showcase
See examples/showcase/src/main.rs for a runnable showcase including:
set!composition andunion!API boundary- custom constructor prefixes
- custom
ReportRenderer - custom
TracingExporterTrait - unified display causes list
- manual and captured stack trace
- global injector for context/trace propagation
Run it with:
Comparison with other crates
| Capability | thiserror |
anyhow |
miette |
diagweave |
|---|---|---|---|---|
| Typed error definitions | Strong | Weak | Medium | Strong |
| Composable error modeling | Weak | Weak | Weak | Strong |
| Propagation-time context | Weak | Strong | Medium | Strong |
| Structured payloads | Weak | Medium | Medium | Strong |
| Human-readable rendering | Weak | Medium | Strong | Strong |
| Machine-consumable JSON | Weak | Weak | Medium | Strong |
| Tracing/observability export | Weak | Weak | Medium | Strong |
Feature flags
std(default): std integrationsjson:Jsonrenderer (serde/serde_json)trace: trace data model (ReportTrace, etc.), prepared emission typestate (PreparedTracingEmission), and pluggable exporter trait (TracingExporterTrait)otel: OTLP envelope model (OtelEnvelope,OtelEvent,OtelValue),OtelEnvelopeConfig, andto_otel_envelope(config)/to_otel_envelope_default()onDiagnosticIr<'_, HasSeverity>tracing: defaulttracingcrate integration (TracingExporter,prepare_tracing,emit_tracing)
Workspace layout
diagweave/: runtime APIs + macro re-exportdiagweave-macros/: proc-macro implementationexamples/showcase/: runnable best-practice sample (publish = false)
Testing
powershell -File scripts/test-feature-matrix.ps1
When to use
diagweave is a good fit when you need both typed boundaries and rich runtime diagnostics for services, libraries, or frameworks.
If you only need minimal display derivation or quick app-level propagation, a lighter stack may be enough.
License
Dual-licensed under MIT OR Apache-2.0.