aboutsummaryrefslogtreecommitdiff
path: root/docs/pages/9-error-handling.md
blob: ec1f4220bc638dda7dfff718b9016c58eaa94f1f (plain) (blame)
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>