ricecoder_hooks/dispatcher/
event.rs1use 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#[derive(Clone)]
15pub struct DefaultEventDispatcher {
16 registry: Arc<dyn HookRegistry>,
17 executor: Arc<dyn HookExecutor>,
18}
19
20impl DefaultEventDispatcher {
21 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 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 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 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 }
87 }
88 }
89
90 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 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 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 assert!(order.contains(&"hook1".to_string()));
351 assert!(order.contains(&"hook2".to_string()));
352 assert!(order.contains(&"hook3".to_string()));
353 }
354}