diff options
Diffstat (limited to 'docs/pages/9-error-handling.md')
| -rw-r--r-- | docs/pages/9-error-handling.md | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/docs/pages/9-error-handling.md b/docs/pages/9-error-handling.md new file mode 100644 index 0000000..ec1f422 --- /dev/null +++ b/docs/pages/9-error-handling.md @@ -0,0 +1,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> |
