# Tutorial
This document describes the primary usage paths of `orion-error`, based on the current source code, tests, and `examples/`.
## Installation
```toml
[dependencies]
orion-error = "0.8.0"
```
Optional features:
```toml
[dependencies]
orion-error = { version = "0.8.0", features = ["serde"] }
orion-error = { version = "0.8.0", features = ["tracing"] }
orion-error = { version = "0.8.0", features = ["serde_json"] }
```
Default features: `derive`, `log`.
## Import Conventions
Prefer one of these two approaches:
**Application code (default):**
```rust
use orion_error::prelude::*;
use orion_error::runtime::OperationContext;
```
**Architecture boundaries** — explicit layered imports:
```rust
use orion_error::prelude::*;
use orion_error::conversion::*; // cross-layer conversion
use orion_error::protocol::*; // boundary output
use orion_error::protocol::*; // boundary output
use orion_error::interop::*; // std::error::Error bridge
```
## 1-Minute Example
```rust
use orion_error::prelude::*;
use orion_error::runtime::OperationContext;
#[derive(Debug, Clone, PartialEq, OrionError)]
enum AppReason {
#[orion_error(identity = "biz.invalid")]
Invalid,
#[orion_error(transparent)]
General(UnifiedReason),
}
fn load_config(path: &str) -> Result<String, StructError<AppReason>> {
let ctx = OperationContext::doing("load_config")
.with_field("path", path)
.with_meta("component.name", "config_loader");
std::fs::read_to_string(path)
.source_err(AppReason::system_error(), "read failed")
.doing("read file")
.with_context(&ctx)
}
```
This covers the four core points:
- Domain reason defined with `OrionError`
- Error entry via `source_err(reason, detail)` (unified entry)
- Semantic context via `doing(...)`
- Diagnostic fields and metadata on `OperationContext`
## 1. Defining Reason
### 1.1 Domain Reason
New code should use `#[derive(OrionError)]`:
```rust
use orion_error::{OrionError, UnifiedReason};
#[derive(Debug, Clone, PartialEq, OrionError)]
enum OrderReason {
#[orion_error(identity = "biz.order_not_found")]
OrderNotFound,
#[orion_error(identity = "biz.insufficient_funds")]
InsufficientFunds,
#[orion_error(transparent)]
General(UnifiedReason),
}
```
`OrionError` generates: `Display`, `DomainReason`, `ErrorCode`, `ErrorIdentityProvider`.
### 1.2 Universal Reason
`UnifiedReason` is the built-in universal reason classification. Common constructors:
- `UnifiedReason::validation_error()`, `UnifiedReason::business_error()`
- `UnifiedReason::system_error()`, `UnifiedReason::network_error()`, `UnifiedReason::timeout_error()`
- `UnifiedReason::core_conf()`, `UnifiedReason::logic_error()`
### 1.3 Delegate Constructors
If your domain reason has a transparent `UnifiedReason` variant, all `UnifiedReason` constructors are generated automatically:
```text
AppReason::system_error() // instead of AppReason::from(UnifiedReason::system_error())
AppReason::validation_error()
```
## 2. Constructing StructError
### 2.1 Direct Construction
```rust
use orion_error::prelude::*;
let err = StructError::from(UnifiedReason::validation_error())
.with_detail("field `email` is required");
```
### 2.2 Builder
```rust
use orion_error::prelude::*;
use orion_error::runtime::OperationContext;
let ctx = OperationContext::doing("validate input");
let err = StructError::builder(UnifiedReason::validation_error())
.detail("field `email` is required")
.context_ref(&ctx)
.finish();
```
### 2.3 Attaching Source
```rust
use orion_error::prelude::*;
let err = StructError::from(UnifiedReason::system_error())
.with_detail("read config failed")
.with_source(std::io::Error::other("disk offline"));
```
Preferred APIs: `with_source(...)`, `builder.source(...)`. These auto-route between `StdError` and `StructError` source types.
## 3. Using Context
`OperationContext` carries runtime context:
```rust
use orion_error::prelude::*;
use orion_error::runtime::OperationContext;
let ctx = OperationContext::doing("place_order")
.with_field("order_id", "A-1001")
.with_field("user_id", "42")
.with_meta("component.name", "order_service");
let result: Result<(), StructError<UnifiedReason>> =
Err(StructError::from(UnifiedReason::system_error()));
let result = result
.doing("check inventory")
.with_context(&ctx);
assert!(result.is_err());
```
Attach context to an error:
```rust
use orion_error::prelude::*;
use orion_error::runtime::OperationContext;
let ctx = OperationContext::doing("place_order")
.with_field("order_id", "A-1001")
.with_field("user_id", "42")
.with_meta("component.name", "order_service");
let result: Result<(), StructError<UnifiedReason>> =
Err(StructError::from(UnifiedReason::system_error()));
let result = result
.doing("check inventory")
.with_context(&ctx);
assert!(result.is_err());
```
Common field types:
- `with_field(...)` — human-readable diagnostic entries (appears in Display output)
- `with_meta(...)` — machine-oriented metadata (serialization only)
## 4. Error Entry and Cross-Layer Conversion
### 4.1 `source_err(reason, detail)` — Unified Entry
Works for both raw `std::error::Error` and already-structured `StructError` sources:
```rust
use orion_error::prelude::*;
let err = std::fs::read_to_string("config.toml")
.source_err(UnifiedReason::system_error(), "read config failed")
.unwrap_err();
```
Supported source types: `std::io::Error`, `anyhow::Error` (with `anyhow` feature), `serde_json::Error` (with `serde_json` feature), `toml::de::Error` / `toml::ser::Error` (with `toml` feature), and custom `RawStdError` types via `raw_source(...)`.
### 4.2 `conv_err()` — Cross-Layer Reason Remap
When the upstream error is already structured and you only need to change the reason type:
```rust
use derive_more::From;
use orion_error::conversion::ConvErr;
use orion_error::conversion::ToStructError;
use orion_error::prelude::*;
#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum RepoReason {
#[orion_error(transparent)]
General(UnifiedReason),
}
#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum ServiceReason {
#[orion_error(transparent)]
Repo(RepoReason),
}
fn lower_layer_call() -> Result<(), StructError<RepoReason>> {
Err(RepoReason::system_error().to_err().with_detail("read config failed"))
}
fn upper_layer_call() -> Result<(), StructError<ServiceReason>> {
lower_layer_call().conv_err()?;
Ok(())
}
```
Requires `ServiceReason: From<RepoReason>`.
## 5. Error Objects Summary
| `StructError<R>` | Runtime carrier | Propagation |
| `DiagnosticReport` | Human diagnostics | `err.report()` |
| `ErrorProtocolSnapshot` | Protocol projection | `err.exposure(&policy)` |
Standard Error interop: `as_std()`, `into_std()`, `into_boxed_std()`, `into_dyn_std()`.
## 6. Stable Identity and Protocol Projection
### 6.1 Stable Identity
Each error variant has a permanent machine-readable name:
```rust
use orion_error::{OrionError, StructError};
use orion_error::reason::ErrorIdentityProvider;
use orion_error::protocol::DefaultExposurePolicy;
use orion_error::UnifiedReason;
#[derive(Debug, PartialEq, OrionError)]
enum ApiReason {
#[orion_error(identity = "biz.invalid_input")]
InvalidInput,
#[orion_error(transparent)]
General(UnifiedReason),
}
assert_eq!(ApiReason::InvalidInput.stable_code(), "biz.invalid_input");
assert_eq!(ApiReason::InvalidInput.error_category().as_str(), "biz");
```
Stable identity never changes — unlike display text, numeric codes, or Rust paths.
The identity prefix (`biz`, `sys`, `conf`, `logic`) also determines the default `ExposurePolicy` behaviour.
### 6.2 Protocol Projection
The same error produces different JSON shapes for different protocol boundaries:
```rust,ignore
use orion_error::{OrionError, StructError};
use orion_error::protocol::DefaultExposurePolicy;
use orion_error::UnifiedReason;
#[derive(Debug, PartialEq, OrionError)]
enum ApiReason {
#[orion_error(identity = "biz.invalid_input")]
InvalidInput,
#[orion_error(transparent)]
General(UnifiedReason),
}
let err = StructError::from(ApiReason::system_error())
.with_detail("disk offline at /dev/sda");
let proto = err.exposure(&DefaultExposurePolicy);
// HTTP response — minimal, safe for external clients
proto.to_http_error_json();
// Log output — full context for debugging
proto.to_log_error_json();
// RPC response — hides internal detail
proto.to_rpc_error_json();
// CLI output — human-readable summary
proto.to_cli_error_json();
```
## 7. Testing
```rust
use orion_error::dev::testing::assert_err_identity;
use orion_error::prelude::SourceErr;
use orion_error::reason::ErrorCategory;
use orion_error::reason::UnifiedReason;
let err = std::fs::read_to_string("config.toml")
.source_err(UnifiedReason::system_error(), "read config failed")
.unwrap_err();
assert_err_identity(&err, "sys.io_error", ErrorCategory::Sys);
```
Test helpers: `assert_err_code()`, `assert_err_category()`, `assert_err_identity()`, `assert_err_operation()`, `assert_err_path()`.
## 8. Best Practices
- Define domain reasons with `#[derive(OrionError)]`
- Use `source_err(reason, detail)` as the unified error entry point
- Use `conv_err()` for cross-layer reason conversion
- Use `identity_snapshot()` for stable identity inspection
- Use `exposure(...)` for protocol boundary output
- Use explicit interop APIs when entering `std::error::Error` ecosystem