| name | orion-error-engineering |
| description | Use when working with the Rust crate orion-error: adopting it in services, defining OrionError enums, choosing UnifiedReason categories, adding StructError context, using ToStructError/SourceErr/ConvErr correctly, leveraging transparent UnifiedReason delegate constructors, integrating report/protocol/interop APIs, or fixing stale 0.6/0.7-era orion-error examples. |
Orion Error Engineering
Use this skill for source-accurate work with the orion-error Rust crate.
First Checks
- Locate the crate root. In this workspace it is usually
orion-error/.
- Treat
src/, tests/, and compiling examples as source of truth.
- For
0.8.x, prefer orion_error::prelude::* plus small layered imports.
- When behavior changes, verify with
cargo test --all-features -- --test-threads=1 from the crate root.
Current API Facts
- Package line is
0.8.x, Rust 2021.
- Main runtime carrier:
StructError<R>.
- New domain reason enums should usually derive
OrionError instead of manually assembling Display, ErrorCode, ErrorIdentityProvider, and DomainReason.
- Stable external identity is
ErrorIdentity.code, not the legacy numeric ErrorCode.
- Public protocol naming is now
Exposure*, not ErrorPolicy*.
StructError<R> no longer directly implements std::error::Error.
- Standard-error ecosystem boundaries go through explicit interop APIs such as
as_std(), into_std(), into_boxed_std(), and into_dyn_std().
- Prefer layered imports when the module benefits from explicit boundaries:
runtime::*
conversion::*
report::*
protocol::*
interop::*
reason::*
dev::testing::*
SourceRawErr + source_raw_err(Reason, detail) accepts any StdError, no UnstructuredSource impl needed. This is the escape hatch for third-party error types without a dedicated bridge (e.g. git2::Error, reqwest::Error).
source_err(Reason, detail) remains the preferred path when E: UnstructuredSource (e.g. std::io::Error, serde_json::Error with serde_json, toml errors with toml, and anyhow::Error with anyhow).
with_source(err) is the single public source-attachment path on StructError; with_std_source / with_struct_source are pub(crate).
- Legacy
owe(...), owe_*(), err_wrap(...), want(...), and with(...) paths are removed from the current primary API.
Recommended 0.8 Workflow
- Define a domain enum with
#[derive(OrionError)].
- Give business variants stable
#[orion_error(identity = "...")] identities.
- Keep a transparent
General(UnifiedReason) variant for shared infrastructure categories.
#[derive(OrionError)] exposes the full set of UnifiedReason delegate constructors on the outer enum, so prefer calls like AppReason::system_error() or AppReason::validation_error() over AppReason::from(UnifiedReason::system_error()).
- Use
OperationContext::doing(...) plus chained with_field(...) / with_meta(...) for structured context.
- For a single domain reason turning into
StructError<R>, prefer to_err().
- For a normal
Result<T, E> entering the structured system the first time, prefer source_err(reason, detail) when E: UnstructuredSource (e.g. std::io::Error).
- For third-party
StdError types that don't implement UnstructuredSource (e.g. git2::Error, reqwest::Error), use source_raw_err(reason, detail).
- For an upstream
Result<T, StructError<R1>> that only changes reason type, use conv_err().
- For an upstream error where the upper layer wants a new semantic boundary, use
source_err(reason, detail).
- For real underlying errors, prefer unified source APIs:
with_source(...) — single public source-attachment path.
StructErrorBuilder::source(...)
These auto-route raw StdError and lower StructError<_> sources.
- For stable machine identity, prefer
identity_snapshot() / ErrorIdentity.code.
- For protocol projection, prefer
exposure(...) / into_exposure(...) and to_*_error_json() when serde_json is enabled.
- For human diagnostics and redaction, prefer
report(), render(...), render_redacted(...), and render_user_debug(...) on protocol snapshots.
Decision Tree
- Upstream is
Result<T, E> and E: UnstructuredSource (e.g. std::io::Error, feature-enabled serde_json, toml, or anyhow errors): use source_err(reason, detail).
- Upstream is
Result<T, E> and E: StdError but NOT UnstructuredSource (e.g. git2::Error, reqwest::Error): use source_raw_err(reason, detail).
- Upstream is
Result<T, StructError<R1>> and the upper layer only remaps reason type: use conv_err().
- Upstream is
Result<T, StructError<R1>> and the upper layer wants a new semantic boundary with preserved lower structured source: use source_err(reason, detail).
- A single reason enum value needs to become a
StructError<R>: use to_err().
- You are attaching any source directly while building a
StructError: use with_source(...) or builder.source(...).
err_conv() is a deprecated compatibility alias for conv_err(). Old call sites will warn; new code should use conv_err().
- A boundary requires
std::error::Error: use as_std(), into_std(), into_boxed_std(), or into_dyn_std() instead of assuming StructError<R> itself is a standard error.
- If you see legacy
owe(...), owe_*(), err_wrap(...), want(...), or with(...) in examples, treat that material as stale and migrate to the current workflow.
Existing-System Migration Branch
Use this branch when upgrading an existing crate or service with old error APIs, anyhow::Result, broad map_err, or payload-carrying reason enums.
- Upgrade the
orion-error dependency and imports first.
- Replace obvious legacy APIs such as
.owe(), owe_*(), into_as(...), upcast(), wrap_as(...), err_wrap(...), want(...), and with(...).
- Convert boundary errors next:
- File IO: prefer
source_err(reason, detail).
- Command execution: prefer
source_err(...) for supported std errors, source_raw_err(...) for third-party process wrappers.
- HTTP/download/upload: usually
source_raw_err(...) unless the error type has an UnstructuredSource bridge.
- serde/json/yaml/toml/template/render: use
source_err(...) when the crate feature provides a bridge, otherwise source_raw_err(...).
anyhow::Result: do not expand anyhow through core paths; with the anyhow feature use source_err(...), otherwise use source_raw_err(...) if the type is a plain StdError boundary.
- Replace pure wrapping
map_err calls with source_err(...), source_raw_err(...), or conv_err().
- Keep payload-carrying reason enums temporarily during the API migration so compile errors stay focused on API changes.
- After tests stabilize, migrate domain reasons such as
RunReason, ExecReason, and GxlReason toward unit enum variants.
- Move the old payload information into
StructError detail, context, fields, metadata, or source chains.
Unit enum reason modeling is the semantic-convergence phase, not the first API-upgrade step. Doing it too early mixes constructor breakage with diagnostic relocation and makes migrations harder to review.
MapErr Migration Rules
- Replace pure wrappers like
foo().map_err(|e| Reason::Io.to_err().with_detail(e.to_string()))? with foo().source_err(Reason::Io, "read config")? when E: UnstructuredSource.
- For third-party
StdError boundaries without a bridge, use foo().source_raw_err(Reason::system_error(), "call http api")?.
- For lower
StructError<R1> values that only need reason conversion, use conv_err().
- Keep
map_err when conversion contains business branching, rewrites a user-facing message, or adds structured fields/context that cannot be expressed cleanly with the chained helpers.
Domain Enum Guidance
- Prefer
#[derive(OrionError)] on enums or structs used as reasons.
- Use explicit stable identities for business-facing variants.
- Model domain reasons as unit enum variants whenever possible, e.g.
Reason::InvalidInput, not Reason::InvalidInput(payload).
- Do not put diagnostic payloads, file paths, tenant IDs, raw backend messages, or dynamic details inside the reason value. Put them on
StructError with to_err().with_detail(...), with_context(OperationContext::...with_field(...) / with_meta(...)), or the detail argument of source_err(...).
- Keep the
General(UnifiedReason) transparent variant to reuse shared category / retry / severity semantics.
- When the variant is transparent over
UnifiedReason, prefer the generated delegate constructors (core_conf(), validation_error(), system_error(), etc.) on the outer enum.
- Root-visible
ErrorCode remains valid, but treat it as a compatibility numeric code rather than the primary external identity.
Import Guidance
- Default for new application code:
use orion_error::prelude::*;
- then add small layered imports such as
runtime::OperationContext, reason::UnifiedReason, protocol::DefaultExposurePolicy, or conversion::{ToStructError, SourceErr, SourceRawErr, ConvErr}
- Prefer explicit layered imports when reviewing architecture boundaries.
- Avoid broad root imports except for the small primary-path names.
dev::prelude::* is for protocol/schema tests and migration verification, not normal business modules.
Exposure, Report, Protocol
- Protocol/redaction APIs are centered on:
DefaultExposurePolicy
ExposurePolicy
ExposureDecision
ErrorProtocolSnapshot
- Stable identity path:
identity_snapshot()
ErrorIdentity.code
- Human/report path:
report()
into_report()
render(...)
render_redacted(...)
- Protocol projection path:
exposure(...)
into_exposure(...)
to_http_error_json()
to_rpc_error_json()
to_cli_error_json()
to_log_error_json()
render_user_debug(...)
render_user_debug_redacted(...)
Logging And Features
- Default features include
log and derive.
- Optional features include
tracing, serde, serde_json, anyhow, and toml.
OperationContext still carries logging helpers.
- Prefer current context APIs such as
doing(...), with_field(...), with_meta(...), and with_context(...) in examples and migration advice. Use record_field(...) / record_meta(...) only when code already holds a mutable context reference.
Docs And Site Guidance
- Current docs are two mdBooks:
- English:
docs/en/book.toml, source under docs/en/src/
- Chinese:
docs/zh/book.toml, source under docs/zh/src/
- Root
docs/index.html is the language selector; generated output lives under site/en/ and site/zh/.
- When writing governance/AI docs, do not claim "error-handling code is 40-60% of development work." The defensible claim is that finding/fixing bugs and avoidable rework are often cited in the 40-60% effort range, while exception/error-handling code itself is a much smaller source-code fraction in field studies.
- Keep docs concise and distinguish current 0.8 API from ideal architecture or future design notes.
Stale-Doc Guardrails
- Do not teach
thiserror-only reason enums as the preferred 0.8 style; prefer #[derive(OrionError)].
- Do not present
ErrorPolicy* or policy_* as current public APIs.
- Do not assume
StructError<R> implements std::error::Error directly.
- Do not recommend
owe_*() as the default path for new code.
- Do not recommend
StructError::from(reason) as the default path for new code; prefer to_err().
- Do not introduce data-carrying reason variants like
Reason::X(payload) for diagnostics; migrate those to Reason::X.to_err().with_detail(payload) or preserve the dynamic information in the source_err(reason, detail) detail/context path.
- Do not recommend
into_as(...); use source_err(...).
- Do not recommend
upcast(); use conv_err().
- Do not recommend
wrap_as(...) or ErrorWrapAs; that API has been removed and semantic-boundary wrapping now goes through source_err(...).
- Do not recommend
err_conv() as the primary conversion path; prefer conv_err().
with_std_source(...) / with_struct_source(...) are pub(crate); use with_source(...) as the single public path.
- Do not recommend
AppReason::from(UnifiedReason::...) as the default way to build a transparent UnifiedReason variant; prefer the derived delegate constructor on the outer enum.
- Do not route a
StructError<_> through legacy into_as(...); use source_err(...) when intentionally preserving it as a source under a new reason.
- Do not treat
ErrorCode as the primary external identity; use stable identity / ErrorIdentity.code.
- Do not recommend public field-name constants for report/protocol JSON shapes; these are not part of the stable public API.
- If docs disagree with
src/, tests, or examples in orion-error, follow the source and tests.
References
Read references/patterns.md when you need copyable Rust templates for 0.8.x.