Gracefully present errors to the user
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.Written by @Weicao-CatilGrass