orion-error
Structured error handling for Rust services with:
- layered universal error categories via
UvsReason - domain-specific error enums with stable
ErrorCode - contextual propagation via
OperationContextandErrorWith - conversion helpers via
ErrorOwe,ErrorOweSource, andErrorConv - cross-layer wrapping via
WrapStructErrorandErrorWrap - optional source-chain preservation for real underlying errors
Installation
[]
= "0.6.1"
Optional features:
[]
= { = "0.6.1", = ["serde"] }
# or
= { = "0.6.1", = ["tracing"] }
Default features include log.
Quick Start
use From;
use ;
use Error;
Notes:
DomainReasonis usually implemented automatically when your enum satisfiesFrom<UvsReason> + Display + PartialEq.- Use
record(...)onOperationContext;with(...)on the context itself is deprecated. - Default to
owe_*_source()for real error types; use legacyowe_*()only when the upstream error is merelyDisplay. - For
Result<T, StructError<_>>, prefererr_conv()orerr_wrap(...)instead of routing the error back throughowe_*().
Core Concepts
1. UvsReason
UvsReason is the built-in cross-project error taxonomy:
- Business layer:
ValidationError100,BusinessError101,NotFoundError102,PermissionError103,LogicError104,RunRuleError105 - Infrastructure layer:
DataError200,SystemError201,NetworkError202,ResourceError203,TimeoutError204 - Config/external layer:
ConfigError300,ExternalError301
Useful helpers:
error_code()is_retryable()is_high_severity()category_name()
2. StructError<R>
StructError<R> is the main structured wrapper around a domain reason R.
It carries:
reasondetailposition- context stack
- optional underlying
source
Construction styles:
let err = from
.with_detail;
let err = builder
.detail
.position
.finish;
With preserved source:
let err = builder
.detail
.source
.finish;
3. Context Propagation
use ;
let mut ctx = want;
ctx.record;
ctx.record;
let result = do_work
.want
.with;
Rules of thumb:
OperationContext::want("process_order")defines the outermost goal for this call.- Chained
.want("validate order")on an error appends an inner path segment instead of replacing the outer goal. - Display and
serdenow expose bothWantandPath, for example:Want=process_order,Path=process_order / validate order. - Use
target_main()to read the outermost goal andtarget_path()to read the full path.
3.1 Typed Metadata
OperationContext can also carry machine-readable metadata for diagnostics and classification:
use ;
let ctx = want
.with_meta
.with_meta
.with_meta;
let err = from.with;
assert_eq!;
Recommended usage:
- Put stable classification hints such as
config.kind,config.scope,component.name,parse.lineinto metadata. - Keep metadata short and machine-readable.
- Keep long human-facing explanations in
detail. - Metadata is not rendered by default in
Display.
4. Conversion Helpers
Default recommendation for plain Result<T, E: Error>:
read_file.owe_sys_source?;
http_call.owe_net_source?;
Use legacy owe_*() only for sources that are not real error types and only implement Display:
parse_input.owe_validation?;
message_only_result.owe_biz?;
For converting one StructError<R1> into another StructError<R2>, prefer err_conv():
repo_call.err_conv?;
err_conv() preserves context, detail, position, and source.
If the upper layer wants to redefine the reason instead of converting it, use err_wrap(...) to keep the lower StructError as source:
repo_call.err_wrap?;
In other words:
owe_*_source()is forResult<T, E>whereEis a real non-structured error typeerr_conv()is forResult<T, StructError<R1>>toResult<T, StructError<R2>>err_wrap(...)is forResult<T, StructError<R1>>when the upper layer wants a new reason boundary
If you want to attach a lower StructError directly and preserve its structured source frames, use with_struct_source(...):
use ;
let source = from.with;
let err = from
.with
.with_struct_source;
assert_eq!;
assert_eq!;
The same rule applies to the builder API: use .source_struct(lower_err) for StructError<_> sources, and keep .source(err) for ordinary non-structured errors.
Reports and Redaction
Default Display should stay concise. For diagnostics, logs, or structured export, use ErrorReport and the explicit render APIs:
use ;
;
let err = from
.with_detail
.with;
let report = err.report;
assert_eq!;
let verbose = err.render;
let redacted = err.render_redacted;
assert!;
assert!;
Recommended usage:
report()for structured inspection or custom serialization pipelines.render(RenderMode::Compact)for short summaries.render(RenderMode::Verbose)for local diagnostics and debug output.render_redacted(...)before writing potentially sensitive diagnostics to logs or external systems.
Logging
OperationContext supports optional logging integration.
use ;
let mut ctx = op_context!.with_auto_log;
ctx.record;
ctx.info;
do_sync?;
ctx.mark_suc;
Use scoped_success() if you want RAII-style success marking.
Source Chain
If you use with_source(...) or owe_*_source(), the original error remains available:
let err: = read_to_string
.owe_sys_source
.unwrap_err;
assert!;
assert!;
You can also inspect the entire chain:
let chain = err.source_chain;
let frames = err.source_frames;
let pretty = err.display_chain;
With the serde feature, serialized output also includes:
wantpathsource_framessource_messagesource_chain
source_frames is the structured form of the chain. Each frame contains:
indexmessage- optional
display - optional
type_name - optional
error_code - optional
reason - optional
want - optional
path - optional
detail - optional
metadata is_root_cause
For StructError sources, message is the stable reason text and display carries the full formatted error. debug remains available on SourceFrame at runtime, but it is not serialized by default because Debug output may contain sensitive internal fields. source_chain is kept as a compatibility summary; new observability pipelines should prefer source_frames. type_name is best-effort and should not be treated as a complete or stable classification key.
The underlying trait object itself is still not serialized.
If you use legacy owe_*() helpers, only the display string is copied into detail, so they are not the preferred path for normal Rust errors.
thiserror Integration
Recommended pattern:
- use
thiserrorfor domain enum definition - include
Uvs(UvsReason)as the bridge variant - implement
ErrorCode - use
orion-errorfor conversion, context, and classification
See docs/thiserror-comparison.md.
Migration Notes
Prefer these current names:
CwdGuard-style example does not apply here; ignore older cross-project docsOperationContext::record(...)instead of deprecatedwith(...)with_auto_log()instead of deprecatedwith_exit_log()- prefer
owe_*_source()by default; keepowe_*()forDisplay-only cases
Validation
From crate root:
Chinese Notes
当前版本文档以源码为准,推荐优先参考:
如果 README 与源码冲突,请以 src/ 和测试为准。