orion-error 0.7.1

Struct Error for Large Project
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# 使用教程

本文档以当前源码、测试和 `examples/` 为准,描述 `orion-error` 的主路径用法。

## 安装

```toml
[dependencies]
orion-error = "0.7.0"
```

常见可选 feature:

```toml
[dependencies]
orion-error = { version = "0.7.0", features = ["serde"] }
# 
orion-error = { version = "0.7.0", features = ["tracing"] }
# 
orion-error = { version = "0.7.0", features = ["serde_json"] }
```

默认 feature 包含:

- `derive`
- `log`

## 导入约定

推荐优先使用下面两种方式:

```rust
use orion_error::prelude::*;
use orion_error::reason::UvsReason;
use orion_error::runtime::OperationContext;
```

或:

```rust
use orion_error::{StructError, DefaultExposurePolicy, OrionError};
use orion_error::conversion::{ErrorWith, IntoAs, ErrorWrapAs};
use orion_error::reason::UvsReason;
use orion_error::runtime::OperationContext;
```

其中:

- `prelude::*` 只导出主路径:`OrionError``StructError``IntoAs``ErrorWith``ErrorWrapAs``DefaultExposurePolicy`
- 需要更明确边界时,再按职责补 `runtime` / `conversion` / `snapshot` / `report` / `bridge` / `reason`
-`owe(...)` 路径只从 `compat_prelude::*``compat_traits::*` 导入

## 一分钟上手

```rust
use derive_more::From;
use orion_error::{
    prelude::*,
    reason::UvsReason,
    runtime::OperationContext,
};

#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum AppError {
    #[orion_error(identity = "biz.invalid_request")]
    InvalidRequest,
    #[orion_error(transparent)]
    Uvs(UvsReason),
}

fn load_config() -> Result<String, StructError<AppError>> {
    let mut ctx = OperationContext::doing("load config");
    ctx.record_field("path", "config.toml");
    ctx.record_meta("component.name", "config_loader");

    std::fs::read_to_string("config.toml")
        .into_as(AppError::from(UvsReason::system_error()), "read config failed")
        .doing("read config file")
        .with_context(&ctx)
}
```

这个例子覆盖了当前主路径的四个核心点:

- 领域 reason 用 `OrionError` 定义
- 普通错误第一次进入结构化体系用 `into_as(...)`
- 运行时语义上下文用 `doing(...)`
- 诊断字段和 metadata 写到 `OperationContext`

## 1. 定义 reason

### 1.1 领域 reason

新代码推荐直接 derive `OrionError`:

```rust
use derive_more::From;
use orion_error::{OrionError, UvsReason};

#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum OrderReason {
    #[orion_error(identity = "biz.order_not_found")]
    OrderNotFound,
    #[orion_error(identity = "biz.insufficient_funds")]
    InsufficientFunds,
    #[orion_error(transparent)]
    Uvs(UvsReason),
}
```

`OrionError` 会为该类型生成:

- `Display`
- `DomainReason`
- `ErrorCode`
- `ErrorIdentityProvider`

默认规则:

- `identity = "biz.order_not_found"` 生成 stable code
- `category` 默认由 `identity` 前缀推导
- `message` 未显式指定时,会从 `identity` 最后一段推导出显示文案
- `code` 未显式指定时,兼容数值码默认是 `500`

### 1.2 通用 reason

`UvsReason` 是 crate 内置的通用错误分类,已经实现:

- `DomainReason`
- `ErrorCode`
- `ErrorIdentityProvider`

常用构造:

- `UvsReason::validation_error()`
- `UvsReason::business_error()`
- `UvsReason::system_error()`
- `UvsReason::network_error()`
- `UvsReason::timeout_error()`
- `UvsReason::core_conf()`
- `UvsReason::logic_error()`

## 2. 构造 `StructError`

### 2.1 直接构造

```rust
use orion_error::{StructError, UvsReason};

let err = StructError::from(UvsReason::validation_error())
    .with_detail("field `email` is required");
```

### 2.2 Builder 构造

```rust
use orion_error::{
    runtime::OperationContext,
    StructError,
    UvsReason,
};

let ctx = OperationContext::doing("validate request");

let err = StructError::builder(UvsReason::validation_error())
    .detail("field `email` is required")
    .context_ref(&ctx)
    .finish();
```

### 2.3 挂载 source

已有 `StructError` 时:

```rust
let err = StructError::from(UvsReason::system_error())
    .with_detail("read config failed")
    .with_std_source(std::io::Error::other("disk offline"));
```

Builder 时:

```rust
let err = StructError::builder(UvsReason::system_error())
    .detail("read config failed")
    .source_std(std::io::Error::other("disk offline"))
    .finish();
```

新代码建议优先显式写:

- `with_std_source(...)`
- `with_struct_source(...)`
- `source_std(...)`
- `source_struct(...)`

`with_source(...)` / `source(...)` 仍可用,但更适合兼容自动分流场景。

## 3. 使用上下文

`OperationContext` 是运行时上下文载体。

```rust
use orion_error::OperationContext;

let mut ctx = OperationContext::doing("place_order");
ctx.record_field("order_id", "A-1001");
ctx.record_field("user_id", "42");
ctx.record_meta("component.name", "order_service");
ctx.record_meta("tenant.id", "demo");
```

推荐区分两类写法:

- `record_field(...)`:给人看的诊断字段
- `record_meta(...)`:机器消费的结构化 metadata

### 3.1 错误侧挂载上下文

```rust
use orion_error::conversion::ErrorWith;

let result = check_inventory()
    .doing("check inventory")
    .with_context(&ctx);
```

上下文语义:

- `OperationContext::doing(...)``action`
- `OperationContext::at(...)``locator`
- `StructError::doing(...)` / `at(...)` 是对应的 error-side 语义糖衣
- 兼容投影仍然保留 `target` / `path`

常用读取方法:

- `action_main()`
- `locator_main()`
- `target_main()`
- `target_path()`

## 4. 错误进入和跨层转换

### 4.1 `into_as(...)`

`into_as(...)` 用于“普通错误第一次进入结构化体系”。

```rust
use orion_error::{IntoAs, UvsReason};

let err = std::fs::read_to_string("config.toml")
    .into_as(UvsReason::system_error(), "read config failed")
    .unwrap_err();
```

注意:当前实现没有 blanket `E: std::error::Error` 的通用实现。

当前支持的是一组受控入口:

- `std::io::Error`
- `anyhow::Error`(启用 `anyhow` feature)
- `serde_json::Error`(启用 `serde_json` feature)
- `toml::de::Error` / `toml::ser::Error`(启用 `toml` feature)
- `raw_source(...)` 包装后的下游自定义 `RawStdError`

如果你有第三方错误类型,需要显式 opt-in:

```rust
use std::fmt;
use orion_error::{IntoAs, UvsReason};
use orion_error::bridge::{raw_source, RawStdError};

#[derive(Debug)]
struct ThirdPartyError;

impl fmt::Display for ThirdPartyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "third-party failure")
    }
}

impl std::error::Error for ThirdPartyError {}
impl RawStdError for ThirdPartyError {}

let result: Result<(), ThirdPartyError> = Err(ThirdPartyError);
let err = result
    .map_err(raw_source)
    .into_as(UvsReason::system_error(), "load failed")
    .unwrap_err();
```

### 4.2 `wrap_as(...)`

当上游已经是 `StructError<_>`,而当前层要建立新的语义边界时,使用 `wrap_as(...)`:

```rust
use orion_error::ErrorWrapAs;

let wrapped = repo_call()
    .wrap_as(AppReason::from(UvsReason::system_error()), "service layer failed");
```

### 4.3 `err_conv()`

当只是把下层 reason 收敛到上层 reason,而不想新增一层 detail/source 语义时,使用 `err_conv()`:

```rust
use orion_error::conversion::ErrorConv;

let err = lower_layer_call().err_conv()?;
```

典型前提是:

- `R2: From<R1>`

### 4.4 兼容旧路径

旧代码仍然可以使用:

- `owe(...)`
- `err_wrap(...)`
- `wrap(...)`

但这些都只建议用于兼容维护,不建议作为新代码默认路径。

## 5. source、snapshot、report、bridge 的边界

### 5.1 运行时对象

运行时传播使用:

- `StructError<R>`

### 5.2 稳定导出对象

稳定机器导出使用:

- `ErrorSnapshot`
- `StableErrorSnapshot`

常用入口:

```rust
let snapshot = err.snapshot();
let stable = snapshot.stable_export();
```

### 5.3 人类诊断对象

人类诊断使用:

- `DiagnosticReport`

常用入口:

```rust
let report = err.report();
```

### 5.4 标准错误生态 bridge

`StructError<R>` 本身不再直接实现 `std::error::Error`。

需要进入标准错误生态时,使用显式 bridge:

- `as_std()`
- `into_std()`
- `into_boxed_std()`
- `into_dyn_std()`

## 6. 稳定身份和协议投影

如果 reason 实现了 `ErrorIdentityProvider`,可以直接做稳定身份和协议投影:

```rust
use orion_error::{DefaultExposurePolicy, StructError, UvsReason};
use orion_error::reason::ErrorCategory;

let err = StructError::from(UvsReason::system_error())
    .with_detail("read config failed")
    .doing("load config");

let identity = err.identity_snapshot();
let exposure_view = err.exposure_view();
let snapshot = err.exposure_snapshot(&DefaultExposurePolicy);
let http = err.http_response(&DefaultExposurePolicy);
let cli = err.cli_response(&DefaultExposurePolicy);
let log = err.log_response(&DefaultExposurePolicy);
let rpc = err.rpc_response(&DefaultExposurePolicy);
let user_debug = err.render_user_debug(&DefaultExposurePolicy);

assert_eq!(identity.code, "sys.io_error");
assert_eq!(identity.category, ErrorCategory::Sys);
assert_eq!(http.status, 500);
assert_eq!(rpc.detail, None);
assert!(user_debug.contains("sys.io_error"));
```

这几层的职责分别是:

- `identity_snapshot()`:稳定身份视图
- `exposure_view()`:identity + report 的组合视图
- `exposure_snapshot(...)`:最完整的协议输入
- `http_response(...)` / `cli_response(...)` / `log_response(...)` / `rpc_response(...)`:出口投影

## 7. 测试建议

当前测试 helper:

- `assert_err_code(...)`
- `assert_err_category(...)`
- `assert_err_identity(...)`
- `assert_err_operation(...)`
- `assert_err_path(...)`

这里的 `assert_err_code(...)` 断言的是 stable code 字符串,不是数值 `error_code()`。

示例:

```rust
use orion_error::{IntoAs, UvsReason};
use orion_error::reason::ErrorCategory;
use orion_error::testcase::assert_err_identity;

let err = std::fs::read_to_string("config.toml")
    .into_as(UvsReason::system_error(), "read config failed")
    .unwrap_err();

assert_err_identity(&err, "sys.io_error", ErrorCategory::Sys);
```

如果你要断言数值码,请直接调用:

```rust
use orion_error::reason::ErrorCode;

assert_eq!(err.error_code(), 201);
```

## 8. 推荐实践

- 领域 reason 默认 derive `OrionError`
- 对外稳定协议依赖 stable code,不依赖人类文案
- 第一次进入结构化体系优先 `into_as(...)`
- 已结构化错误跨层包装优先 `wrap_as(...)`
- 只做 reason 收敛优先 `err_conv()`
- 需要稳定导出时使用 `snapshot().stable_export()`
- 需要对外协议时使用 `exposure_snapshot(...)` 或 projection API
- 需要进入标准错误生态时使用显式 bridge API