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.
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
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 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/ 和测试为准。