aboutsummaryrefslogtreecommitdiff
path: root/docs/pages/13-hook.md
blob: 0c5e460c98c878be6947d38632315812628c877a (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
<h1 align="center">Hook System</h1>
<p align="center">
  How to insert custom behavior into a program using ProgramHook
</p>

Hooks let you insert custom logic at various lifecycle points in the pipeline — before dispatch, after chain, before/after render, on program exit …

You can write cross-cutting concerns (logging, auth, metrics collection) in hooks instead of scattering them across business code.

## Basic Usage

`ProgramHook` uses a builder pattern:

```rust
@@@use mingling::hook::ProgramHook;
fn main() {
    let mut program = ThisProgram::new();
    program.with_hook(
        ProgramHook::empty()
            .on_pre_chain(|info| {
                println!("before chain: {}", info.input);
            })
            .on_post_render(|info| {
                println!("after render: {}", info.result);
            }),
    );
    program.exec_and_exit();
}
```
 
> [!TIP]
> `ProgramHook::empty()` creates an empty hook, then chain-calls `.on_*()` methods to register the lifecycle nodes you care about. Unregistered nodes won't execute.

## Lifecycle Nodes

Hooks cover the full pipeline lifecycle:

| Stage        | Hook               | Trigger Point       |
| ------------ | ------------------ | ------------------- |
| **Dispatch** | `on_begin`         | Execution start     |
|              | `on_pre_dispatch`  | Before dispatch     |
|              | `on_post_dispatch` | After dispatch      |
| **Chain**    | `on_pre_chain`     | Before chain exec   |
|              | `on_post_chain`    | After chain exec    |
| **Render**   | `on_pre_render`    | Before render exec  |
|              | `on_post_render`   | After render exec   |
| **Finish**   | `on_finish`        | Before program exit |

Each hook callback receives a corresponding `Hook*Info` struct containing context info (input type, params, render results, etc.).

## Real Example: Logging Operations

```rust
@@@use mingling::prelude::*;
@@@use mingling::hook::ProgramHook;
@@@
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
@@@
@@@#[chain] fn handle_greet(args: EntryGreet) -> Next {
@@@    ResultName::new(args.inner.first().cloned().unwrap_or_default()).to_render()
@@@}
@@@#[renderer] fn render_name(r: ResultName) { r_println!("Hello, {}!", *r); }
fn main() {
    let mut program = ThisProgram::new();
 
    // Log info before and after each chain execution
    program.with_hook(
        ProgramHook::empty()
            .on_pre_chain(|info| {
                eprintln!("[hook] executing chain for: {}", info.input);
            })
            .on_post_chain(|info| {
                eprintln!("[hook] chain output: {}", info.output.member_id);
            }),
    );
 
    program.with_dispatcher(CMDGreet);
    program.exec_and_exit();
}
```
 
Run output:

```text
[hook] executing chain for: EntryGreet
[hook] chain output: ResultName
Hello, World!
```
 
## Controlling Behavior via Hooks

Hooks aren't just for observation — you can use `ProgramControlUnit` to alter program behavior:

| Variant                    | Effect                                    |
| -------------------------- | ----------------------------------------- |
| `Continue`                 | Do nothing, continue execution            |
| `OverrideExitCode(i32)`    | Override the exit code                    |
| `RouteToChain(AnyOutput)`  | Replace current data, re-enter Chain loop |
| `RouteToRender(AnyOutput)` | Skip subsequent Chain, render directly    |

> [!NOTE]
> Multiple hooks can be registered and execute in registration order.

<p align="center" style="font-size: 0.85em; color: gray;">
  Written by @Weicao-CatilGrass
</p>