ricecoder_hooks/dispatcher/
event.rs

1//! Event routing and dispatching implementation
2
3use crate::error::{HooksError, Result};
4use crate::executor::HookExecutor;
5use crate::registry::HookRegistry;
6use crate::types::Event;
7use std::sync::Arc;
8use tracing::{debug, error, info};
9
10/// Default implementation of EventDispatcher
11///
12/// Routes events to matching hooks in the registry and executes them using the executor.
13/// Implements hook isolation: if one hook fails, other hooks continue executing.
14#[derive(Clone)]
15pub struct DefaultEventDispatcher {
16    registry: Arc<dyn HookRegistry>,
17    executor: Arc<dyn HookExecutor>,
18}
19
20impl DefaultEventDispatcher {
21    /// Create a new event dispatcher
22    ///
23    /// # Arguments
24    ///
25    /// * `registry` - Hook registry for querying hooks
26    /// * `executor` - Hook executor for executing hooks
27    pub fn new(registry: Arc<dyn HookRegistry>, executor: Arc<dyn HookExecutor>) -> Self {
28        Self { registry, executor }
29    }
30}
31
32impl super::EventDispatcher for DefaultEventDispatcher {
33    fn dispatch_event(&self, event: Event) -> Result<()> {
34        debug!(
35            event_type = %event.event_type,
36            timestamp = %event.timestamp,
37            "Dispatching event"
38        );
39
40        // Query registry for hooks matching this event type
41        let hooks = self.registry.list_hooks_for_event(&event.event_type)?;
42        let hook_count = hooks.len();
43
44        if hooks.is_empty() {
45            debug!(
46                event_type = %event.event_type,
47                "No hooks registered for event"
48            );
49            return Ok(());
50        }
51
52        info!(
53            event_type = %event.event_type,
54            hook_count = hook_count,
55            "Found hooks for event"
56        );
57
58        // Execute each hook in order
59        let mut execution_errors = Vec::new();
60
61        for hook in hooks {
62            debug!(
63                hook_id = %hook.id,
64                hook_name = %hook.name,
65                "Executing hook"
66            );
67
68            // Execute the hook with the event context
69            match self.executor.execute_hook(&hook, &event.context) {
70                Ok(result) => {
71                    info!(
72                        hook_id = %hook.id,
73                        status = ?result.status,
74                        duration_ms = result.duration_ms,
75                        "Hook executed successfully"
76                    );
77                }
78                Err(e) => {
79                    error!(
80                        hook_id = %hook.id,
81                        error = %e,
82                        "Hook execution failed"
83                    );
84                    execution_errors.push((hook.id.clone(), e));
85                    // Continue with next hook (hook isolation)
86                }
87            }
88        }
89
90        // If all hooks failed, return error
91        if !execution_errors.is_empty() && execution_errors.len() == hook_count {
92            let error_msg = execution_errors
93                .iter()
94                .map(|(id, e)| format!("{}: {}", id, e))
95                .collect::<Vec<_>>()
96                .join("; ");
97            return Err(HooksError::ExecutionFailed(format!(
98                "All hooks failed: {}",
99                error_msg
100            )));
101        }
102
103        Ok(())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use crate::dispatcher::EventDispatcher;
111    use crate::executor::HookExecutor;
112    use crate::registry::InMemoryHookRegistry;
113    use crate::types::{Action, CommandAction, EventContext, Hook, HookResult, HookStatus};
114    use std::sync::Mutex;
115
116    struct MockExecutor {
117        call_count: Arc<Mutex<usize>>,
118        should_fail: bool,
119    }
120
121    impl MockExecutor {
122        fn new(should_fail: bool) -> Self {
123            Self {
124                call_count: Arc::new(Mutex::new(0)),
125                should_fail,
126            }
127        }
128
129        fn get_call_count(&self) -> usize {
130            *self.call_count.lock().unwrap()
131        }
132    }
133
134    impl HookExecutor for MockExecutor {
135        fn execute_hook(&self, hook: &Hook, _context: &EventContext) -> Result<HookResult> {
136            let mut count = self.call_count.lock().unwrap();
137            *count += 1;
138
139            if self.should_fail {
140                Err(HooksError::ExecutionFailed("Mock failure".to_string()))
141            } else {
142                Ok(HookResult {
143                    hook_id: hook.id.clone(),
144                    status: HookStatus::Success,
145                    output: Some("Mock output".to_string()),
146                    error: None,
147                    duration_ms: 100,
148                })
149            }
150        }
151
152        fn execute_action(&self, _hook: &Hook, _context: &EventContext) -> Result<String> {
153            Ok("Mock action result".to_string())
154        }
155    }
156
157    fn create_test_hook(id: &str, event: &str) -> Hook {
158        Hook {
159            id: id.to_string(),
160            name: format!("Test Hook {}", id),
161            description: None,
162            event: event.to_string(),
163            action: Action::Command(CommandAction {
164                command: "echo".to_string(),
165                args: vec!["test".to_string()],
166                timeout_ms: None,
167                capture_output: false,
168            }),
169            enabled: true,
170            tags: vec![],
171            metadata: serde_json::json!({}),
172            condition: None,
173        }
174    }
175
176    fn create_test_event(event_type: &str) -> Event {
177        Event {
178            event_type: event_type.to_string(),
179            context: EventContext {
180                data: serde_json::json!({}),
181                metadata: serde_json::json!({}),
182            },
183            timestamp: "2024-01-01T12:00:00Z".to_string(),
184        }
185    }
186
187    #[test]
188    fn test_dispatch_event_with_matching_hooks() {
189        let mut registry = InMemoryHookRegistry::new();
190        let hook1 = create_test_hook("hook1", "file_saved");
191        let hook2 = create_test_hook("hook2", "file_saved");
192
193        registry.register_hook(hook1).unwrap();
194        registry.register_hook(hook2).unwrap();
195
196        let executor = Arc::new(MockExecutor::new(false));
197        let dispatcher = DefaultEventDispatcher::new(
198            Arc::new(registry),
199            executor.clone() as Arc<dyn HookExecutor>,
200        );
201
202        let event = create_test_event("file_saved");
203        dispatcher.dispatch_event(event).unwrap();
204
205        assert_eq!(executor.get_call_count(), 2);
206    }
207
208    #[test]
209    fn test_dispatch_event_no_matching_hooks() {
210        let registry = InMemoryHookRegistry::new();
211        let executor = Arc::new(MockExecutor::new(false));
212        let dispatcher = DefaultEventDispatcher::new(
213            Arc::new(registry),
214            executor.clone() as Arc<dyn HookExecutor>,
215        );
216
217        let event = create_test_event("file_saved");
218        dispatcher.dispatch_event(event).unwrap();
219
220        assert_eq!(executor.get_call_count(), 0);
221    }
222
223    #[test]
224    fn test_dispatch_event_hook_isolation() {
225        let mut registry = InMemoryHookRegistry::new();
226        let hook1 = create_test_hook("hook1", "file_saved");
227        let hook2 = create_test_hook("hook2", "file_saved");
228
229        registry.register_hook(hook1).unwrap();
230        registry.register_hook(hook2).unwrap();
231
232        // Create executor that fails on first call, succeeds on second
233        let call_count = Arc::new(Mutex::new(0));
234        let call_count_clone = call_count.clone();
235
236        struct SelectiveFailExecutor {
237            call_count: Arc<Mutex<usize>>,
238        }
239
240        impl HookExecutor for SelectiveFailExecutor {
241            fn execute_hook(&self, hook: &Hook, _context: &EventContext) -> Result<HookResult> {
242                let mut count = self.call_count.lock().unwrap();
243                *count += 1;
244
245                if *count == 1 {
246                    Err(HooksError::ExecutionFailed("First hook fails".to_string()))
247                } else {
248                    Ok(HookResult {
249                        hook_id: hook.id.clone(),
250                        status: HookStatus::Success,
251                        output: None,
252                        error: None,
253                        duration_ms: 100,
254                    })
255                }
256            }
257
258            fn execute_action(&self, _hook: &Hook, _context: &EventContext) -> Result<String> {
259                Ok("Mock action result".to_string())
260            }
261        }
262
263        let executor = Arc::new(SelectiveFailExecutor {
264            call_count: call_count_clone,
265        });
266        let dispatcher = DefaultEventDispatcher::new(
267            Arc::new(registry),
268            executor.clone() as Arc<dyn HookExecutor>,
269        );
270
271        let event = create_test_event("file_saved");
272        // Should succeed because second hook succeeds (hook isolation)
273        dispatcher.dispatch_event(event).unwrap();
274
275        assert_eq!(*call_count.lock().unwrap(), 2);
276    }
277
278    #[test]
279    fn test_dispatch_event_all_hooks_fail() {
280        let mut registry = InMemoryHookRegistry::new();
281        let hook1 = create_test_hook("hook1", "file_saved");
282        let hook2 = create_test_hook("hook2", "file_saved");
283
284        registry.register_hook(hook1).unwrap();
285        registry.register_hook(hook2).unwrap();
286
287        let executor = Arc::new(MockExecutor::new(true));
288        let dispatcher = DefaultEventDispatcher::new(
289            Arc::new(registry),
290            executor.clone() as Arc<dyn HookExecutor>,
291        );
292
293        let event = create_test_event("file_saved");
294        let result = dispatcher.dispatch_event(event);
295
296        assert!(result.is_err());
297        assert_eq!(executor.get_call_count(), 2);
298    }
299
300    #[test]
301    fn test_dispatch_event_respects_hook_order() {
302        let mut registry = InMemoryHookRegistry::new();
303        let hook1 = create_test_hook("hook1", "file_saved");
304        let hook2 = create_test_hook("hook2", "file_saved");
305        let hook3 = create_test_hook("hook3", "file_saved");
306
307        registry.register_hook(hook1).unwrap();
308        registry.register_hook(hook2).unwrap();
309        registry.register_hook(hook3).unwrap();
310
311        let execution_order = Arc::new(Mutex::new(Vec::new()));
312        let execution_order_clone = execution_order.clone();
313
314        struct OrderTrackingExecutor {
315            execution_order: Arc<Mutex<Vec<String>>>,
316        }
317
318        impl HookExecutor for OrderTrackingExecutor {
319            fn execute_hook(&self, hook: &Hook, _context: &EventContext) -> Result<HookResult> {
320                self.execution_order.lock().unwrap().push(hook.id.clone());
321
322                Ok(HookResult {
323                    hook_id: hook.id.clone(),
324                    status: HookStatus::Success,
325                    output: None,
326                    error: None,
327                    duration_ms: 100,
328                })
329            }
330
331            fn execute_action(&self, _hook: &Hook, _context: &EventContext) -> Result<String> {
332                Ok("Mock action result".to_string())
333            }
334        }
335
336        let executor = Arc::new(OrderTrackingExecutor {
337            execution_order: execution_order_clone,
338        });
339        let dispatcher = DefaultEventDispatcher::new(
340            Arc::new(registry),
341            executor.clone() as Arc<dyn HookExecutor>,
342        );
343
344        let event = create_test_event("file_saved");
345        dispatcher.dispatch_event(event).unwrap();
346
347        let order = execution_order.lock().unwrap();
348        assert_eq!(order.len(), 3);
349        // Verify hooks were executed (order may vary due to HashMap iteration)
350        assert!(order.contains(&"hook1".to_string()));
351        assert!(order.contains(&"hook2".to_string()));
352        assert!(order.contains(&"hook3".to_string()));
353    }
354}