Declare a Chain

Use the chain macro to declare a chain and handle Entry input

In the previous section, we declared `dispatcher!("greet", EntryGreet)`. Now when a user types `greet`, it gets matched and wrapped into `EntryGreet`. But what happens after we get the Entry? We need a Chain to process it. ## The `#[chain]` Macro `#[chain]` marks a handler function. The format is straightforward: ```rust @@@dispatcher!("greet", EntryGreet); #[derive(Grouped, Wrap)] pub struct ResultName(String); #[chain] fn handle_greet(args: EntryGreet) -> Next { // args contains the remaining params after matching user input let name = args.0.first().cloned().unwrap_or_else(|| "World".to_string()); // Wrap the result into Next, telling the dispatcher where to go next ResultName(name).into() } ``` Notice anything? The Chain function signature declares what it needs — `args: EntryGreet`. Then it returns a newtype via `ResultName(name)`. This returned `Next` expands into `impl Into>`. > [!TIP] > Wondering how `Into>` works? > > Check out the [Any Output Mechanism](pages/concepts/3-any-output) chapter to learn about `ChainProcess`. ## Declaring Types with `#[derive(Grouped, Wrap)]` You've probably guessed it — `#[derive(Grouped, Wrap)] pub struct ResultName(String);` defines a type that flows through the pipeline: ```rust // #[derive(Grouped, Wrap)] generates code roughly like this pub struct ResultName(String); impl From for ResultName { fn from(inner: String) -> Self { ResultName(inner) } } impl std::ops::Deref for ResultName { type Target = String; fn deref(&self) -> &Self::Target { &self.0 } } // Grouped generates member_id() → ThisProgram::ResultName, // giving the type its routing identity and Into conversion. ``` Think of it as a **tagged** `String`. The dispatcher uses this tag for precise routing, ensuring data doesn't get mixed up — e.g., data sent to `RenderGreet` won't be misdelivered to `RenderError`. > [!NOTE] > Unlike a simple type alias (`type`), `#[derive(Grouped, Wrap)]` declares a completely new type with its own `TypeId`. Here's a recommended naming convention: | Role | Naming Pattern | Example | | ------------ | ---------------------- | -------------------- | | Entry | `Entry` + command | `EntryGreet` | | Intermediate | `State` + description | `StateParsedArgs` | | Result | `Result` + description | `ResultGreetSomeone` | | Error | `Error` + description | `ErrorUserNotFound` | See [Naming Convention](pages/other/naming_rule) for details, but for now just remember: **use `#[derive(Grouped)]` (optionally with `Wrap`) to give your data a meaningful name**. ## Extracting Params from Entry `EntryGreet`'s `.0` is a `Vec`, which you can freely process inside a Chain: ```rust @@@dispatcher!("greet", EntryGreet); @@@#[derive(Grouped, Wrap)] @@@pub struct ResultName(String); #[chain] fn handle_greet(args: EntryGreet) -> Next { // Take the first param, or use a default let name = args .0 .first() .cloned() .unwrap_or_else(|| "World".to_string()); ResultName(name).into() } ``` If you enable the `picker` feature, you can also use `Picker` for more flexible param extraction — but that's a topic for later. ## Putting It Together Now let's connect the Dispatcher and Chain: ```rust // 1. Declare the command dispatcher!("greet", EntryGreet); // 2. Declare the pipeline data type #[derive(Grouped, Wrap)] pub struct ResultName(String); // 3. Processing logic #[chain] fn handle_greet(args: EntryGreet) -> Next { let name = args.0 .first() .cloned() .unwrap_or_else(|| "World".to_string()); ResultName(name).into() } fn main() { let mut program = ThisProgram::new(); program.exec_and_exit(); } gen_program!(); ``` But this code isn't complete yet — we only have the Dispatcher and Chain. One last step remains: **rendering the result**. That's what the next chapter, Renderer, covers.

Written by @Weicao-CatilGrass