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
|
<h1 align="center">Error Handling</h1>
<p align="center">
Gracefully present errors to the user
</p>
A pipeline isn't just the happy path. When input is invalid, a resource isn't found, or an operation fails, you need a place to handle these "surprises" instead of letting the program panic.
## Two Paths: Success vs. Error
Recall the pipeline model: Chain's return value is `Next`, which has two destinations:
| Route | Meaning |
| -------------- | ------------------------------------------- |
| `.to_render()` | Got a result, hand it to a Renderer to show |
| `.to_chain()` | Not done yet, hand it to the next Chain |
Error values can also take either path—you can render the error msg directly, or pass it to the next Chain for potential recovery.
## Distinguish Errors with Dedicated Types
```rust
@@@dispatcher!("greet", CMDGreet => EntryGreet);
pack!(ResultGreeting = String);
pack!(ErrorNameEmpty = String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let name = args.inner.first().cloned().unwrap_or_default();
if name.is_empty() {
ErrorNameEmpty::new("name is required".to_string()).to_render()
} else {
ResultGreeting::new(name).to_render()
}
}
```
Then write separate Renderers:
```rust
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultGreeting = String);
@@@pack!(ErrorNameEmpty = String);
@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting::new(args.inner.first().cloned().unwrap_or_default()).to_render() }
#[renderer]
fn render_greeting(result: ResultGreeting) {
r_println!("Hello, {}!", *result);
}
#[renderer]
fn render_error_name_empty(err: ErrorNameEmpty) {
r_println!("Error: {}", *err);
}
```
Each Renderer does its own job; what the user sees depends on what the Chain returned.
## Complete Example
```rust
dispatcher!("greet", CMDGreet => EntryGreet);
pack!(ResultGreeting = String);
pack!(ErrorNameEmpty = String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let name = args.inner.first().cloned().unwrap_or_default();
if name.is_empty() {
ErrorNameEmpty::new("name is required".to_string()).to_render()
} else {
ResultGreeting::new(name).to_render()
}
}
#[renderer]
fn render_greeting(result: ResultGreeting) {
r_println!("Hello, {}!", *result);
}
#[renderer]
fn render_error_name_empty(err: ErrorNameEmpty) {
r_println!("Error: {}", *err);
}
fn main() {
let mut program = ThisProgram::new();
program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
gen_program!();
```
Output:
```text
~# my-cli greet Alice
Hello, Alice!
~# my-cli greet
Error: name is required
```
## About `pack_err!`
If you've enabled `extra_macros`, you can use `pack_err!` to quickly declare an error type with an auto-generated `name` field:
```rust
// Features: ["extra_macros"]
pack_err!(ErrorNotFound);
// Generates: struct ErrorNotFound { pub name: String }
```
See [Feature List](pages/other/features) for details.
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
</p>
|