1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3use std::sync::Arc;
4use std::sync::atomic::{AtomicUsize, Ordering};
5
6use parking_lot::RwLock;
7use tokio_util::sync::CancellationToken;
8
9use crate::types::{Event, EventType, HookError, HookResult};
10
11type ErasedEvent = dyn Any + Send + Sync;
12type ErasedHandler = Arc<dyn Fn(CancellationToken, &ErasedEvent) -> HookResult + Send + Sync>;
13type HandlerKey = (TypeId, EventType);
14type HandlerMap = Arc<RwLock<HashMap<HandlerKey, Vec<(usize, ErasedHandler)>>>>;
15
16pub struct HookRegistry {
24 handlers: HandlerMap,
25 event_types: Arc<RwLock<HashMap<TypeId, EventType>>>,
26 next_id: AtomicUsize,
27}
28
29impl std::fmt::Debug for HookRegistry {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 let event_types = self.event_types.read();
32 let handlers = self.handlers.read();
33 let counts = handlers
34 .iter()
35 .map(|((type_id, registered_type), handlers)| {
36 let event_type = event_types
37 .get(type_id)
38 .unwrap_or(registered_type)
39 .to_string();
40 (event_type, handlers.len())
41 })
42 .collect::<HashMap<_, _>>();
43 f.debug_struct("HookRegistry")
44 .field("handler_counts", &counts)
45 .finish()
46 }
47}
48
49impl Default for HookRegistry {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl HookRegistry {
56 #[must_use]
58 pub fn new() -> Self {
59 Self {
60 handlers: Arc::new(RwLock::new(HashMap::new())),
61 event_types: Arc::new(RwLock::new(HashMap::new())),
62 next_id: AtomicUsize::new(0),
63 }
64 }
65
66 pub fn on<E>(
70 &self,
71 event_type: EventType,
72 handler: impl Fn(CancellationToken, &E) -> HookResult + Send + Sync + 'static,
73 ) -> Box<dyn FnOnce() + Send>
74 where
75 E: Event,
76 {
77 let type_id = TypeId::of::<E>();
78 let key = (type_id, event_type.clone());
79 self.event_types.write().insert(type_id, event_type);
80 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
81 self.handlers.write().entry(key.clone()).or_default().push((
82 id,
83 Arc::new(move |cancel, event| {
84 let Some(event) = event.downcast_ref::<E>() else {
85 return Err(HookError::fatal("hook event type mismatch"));
86 };
87 handler(cancel, event)
88 }),
89 ));
90 let handlers = Arc::clone(&self.handlers);
91 Box::new(move || {
92 if let Some(items) = handlers.write().get_mut(&key) {
93 items.retain(|(handler_id, _)| *handler_id != id);
94 }
95 })
96 }
97
98 pub fn emit<E>(&self, event: &E, cancel: CancellationToken) -> HookResult
103 where
104 E: Event,
105 {
106 let key = (TypeId::of::<E>(), event.event_type());
107 let handler_list = {
108 let handlers = self.handlers.read();
109 let Some(handler_list) = handlers.get(&key) else {
110 return Ok(());
111 };
112 handler_list
113 .iter()
114 .map(|(_, handler)| Arc::clone(handler))
115 .collect::<Vec<_>>()
116 };
117 let mut first_error: Option<HookError> = None;
118 for handler in handler_list {
119 if cancel.is_cancelled() {
120 let err = HookError::fatal("hook dispatch cancelled");
121 return Err(err);
122 }
123 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
124 handler(cancel.clone(), event)
125 }))
126 .unwrap_or_else(|payload| {
127 Err(HookError::new(format!(
128 "hook handler panicked: {}",
129 panic_message(payload)
130 )))
131 });
132 if let Err(err) = result {
133 if err.is_fatal() {
134 return Err(err);
135 }
136 if first_error.is_none() {
137 first_error = Some(err);
138 }
139 }
140 }
141 first_error.map_or(Ok(()), Err)
142 }
143
144 #[must_use]
146 pub fn has_handlers<E>(&self, event_type: &EventType) -> bool
147 where
148 E: Event,
149 {
150 self.handlers
151 .read()
152 .get(&(TypeId::of::<E>(), event_type.clone()))
153 .is_some_and(|handlers| !handlers.is_empty())
154 }
155
156 pub fn clear<E>(&self, event_type: &EventType)
158 where
159 E: Event,
160 {
161 self.handlers
162 .write()
163 .remove(&(TypeId::of::<E>(), event_type.clone()));
164 }
165
166 pub fn clear_all(&self) {
168 let mut handlers = self.handlers.write();
169 handlers.clear();
170 }
171}
172
173pub type LifecycleHookRegistry = HookRegistry;
179
180fn panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
181 if let Some(message) = payload.downcast_ref::<&str>() {
182 (*message).to_string()
183 } else if let Some(message) = payload.downcast_ref::<String>() {
184 message.clone()
185 } else {
186 "unknown panic payload".to_string()
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use std::sync::Arc;
193 use std::sync::atomic::{AtomicU32, Ordering};
194
195 use tokio_util::sync::CancellationToken;
196
197 use super::HookRegistry;
198 use crate::{Event, EventType, HookError};
199
200 struct Ping;
201 impl Event for Ping {
202 fn event_type(&self) -> EventType {
203 EventType::new("ping")
204 }
205 }
206 struct Pong;
207 impl Event for Pong {
208 fn event_type(&self) -> EventType {
209 EventType::new("pong")
210 }
211 }
212 fn ping_type() -> EventType {
213 EventType::new("ping")
214 }
215 fn pong_type() -> EventType {
216 EventType::new("pong")
217 }
218
219 #[test]
220 fn register_and_emit() {
221 let registry = HookRegistry::new();
222 let counter = Arc::new(AtomicU32::new(0));
223 let cloned = Arc::clone(&counter);
224 let _unsubscribe = registry.on::<Ping>(ping_type(), move |_, _| {
225 cloned.fetch_add(1, Ordering::SeqCst);
226 Ok(())
227 });
228 assert!(registry.emit(&Ping, CancellationToken::new()).is_ok());
229 assert_eq!(counter.load(Ordering::SeqCst), 1);
230 }
231
232 #[test]
233 fn emit_no_handlers_returns_ok() {
234 assert!(
235 HookRegistry::new()
236 .emit(&Pong, CancellationToken::new())
237 .is_ok()
238 );
239 }
240
241 #[test]
242 fn non_fatal_error_does_not_short_circuit() {
243 let registry = HookRegistry::new();
244 let counter = Arc::new(AtomicU32::new(0));
245 let _unsubscribe1 = registry.on::<Ping>(ping_type(), |_, _| Err(HookError::new("warn")));
246 let cloned = Arc::clone(&counter);
247 let _unsubscribe2 = registry.on::<Ping>(ping_type(), move |_, _| {
248 cloned.fetch_add(1, Ordering::SeqCst);
249 Ok(())
250 });
251 let result = registry.emit(&Ping, CancellationToken::new());
252 assert_eq!(result.expect_err("error").message(), "warn");
253 assert_eq!(counter.load(Ordering::SeqCst), 1);
254 }
255
256 #[test]
257 fn fatal_error_short_circuits() {
258 let registry = HookRegistry::new();
259 let counter = Arc::new(AtomicU32::new(0));
260 let _unsubscribe1 =
261 registry.on::<Ping>(ping_type(), |_, _| Err(HookError::fatal("blocked")));
262 let cloned = Arc::clone(&counter);
263 let _unsubscribe2 = registry.on::<Ping>(ping_type(), move |_, _| {
264 cloned.fetch_add(1, Ordering::SeqCst);
265 Ok(())
266 });
267 assert!(
268 registry
269 .emit(&Ping, CancellationToken::new())
270 .expect_err("fatal")
271 .is_fatal()
272 );
273 assert_eq!(counter.load(Ordering::SeqCst), 0);
274 }
275
276 #[test]
277 fn cancelled_emit_errors_before_dispatch() {
278 let registry = HookRegistry::new();
279 let counter = Arc::new(AtomicU32::new(0));
280 let cloned = Arc::clone(&counter);
281 let _unsubscribe = registry.on::<Ping>(ping_type(), move |_, _| {
282 cloned.fetch_add(1, Ordering::SeqCst);
283 Ok(())
284 });
285 let token = CancellationToken::new();
286 token.cancel();
287 assert!(
288 registry
289 .emit(&Ping, token)
290 .expect_err("cancelled")
291 .is_fatal()
292 );
293 assert_eq!(counter.load(Ordering::SeqCst), 0);
294 }
295
296 #[test]
297 fn panicking_handler_becomes_error_and_continues() {
298 let registry = HookRegistry::new();
299 let counter = Arc::new(AtomicU32::new(0));
300 let _unsubscribe1 = registry.on::<Ping>(ping_type(), |_, _| panic!("boom"));
301 let cloned = Arc::clone(&counter);
302 let _unsubscribe2 = registry.on::<Ping>(ping_type(), move |_, _| {
303 cloned.fetch_add(1, Ordering::SeqCst);
304 Ok(())
305 });
306 assert!(registry.emit(&Ping, CancellationToken::new()).is_err());
307 assert_eq!(counter.load(Ordering::SeqCst), 1);
308 }
309
310 #[test]
311 fn unsubscribe_removes_handler() {
312 let registry = HookRegistry::new();
313 let counter = Arc::new(AtomicU32::new(0));
314 let cloned = Arc::clone(&counter);
315 struct ErrorEvent;
316 impl Event for ErrorEvent {
317 fn event_type(&self) -> EventType {
318 EventType::new("error")
319 }
320 }
321 let unsubscribe = registry.on::<ErrorEvent>(EventType::new("error"), move |_, _| {
322 cloned.fetch_add(1, Ordering::SeqCst);
323 Ok(())
324 });
325 unsubscribe();
326 let _ = registry.emit(&ErrorEvent, CancellationToken::new());
327 assert_eq!(counter.load(Ordering::SeqCst), 0);
328 }
329
330 #[test]
331 fn clear_removes_selected_event_types() {
332 let registry = HookRegistry::new();
333 struct A;
334 impl Event for A {
335 fn event_type(&self) -> EventType {
336 EventType::new("a")
337 }
338 }
339 struct B;
340 impl Event for B {
341 fn event_type(&self) -> EventType {
342 EventType::new("b")
343 }
344 }
345 let a = EventType::new("a");
346 let b = EventType::new("b");
347 let _u1 = registry.on::<A>(a.clone(), |_, _| Ok(()));
348 let _u2 = registry.on::<B>(b.clone(), |_, _| Ok(()));
349 registry.clear::<A>(&a);
350 assert!(!registry.has_handlers::<A>(&a));
351 assert!(registry.has_handlers::<B>(&b));
352 registry.clear_all();
353 assert!(!registry.has_handlers::<B>(&b));
354 }
355
356 #[test]
357 fn multiple_handlers_same_event_all_run() {
358 let registry = HookRegistry::new();
359 let counter = Arc::new(AtomicU32::new(0));
360 for _ in 0..3 {
361 let cloned = Arc::clone(&counter);
362 let _unsubscribe = registry.on::<Pong>(pong_type(), move |_, _| {
363 cloned.fetch_add(1, Ordering::SeqCst);
364 Ok(())
365 });
366 }
367 let _ = registry.emit(&Pong, CancellationToken::new());
368 assert_eq!(counter.load(Ordering::SeqCst), 3);
369 }
370
371 #[test]
372 fn handler_can_mutate_registry_during_emit() {
373 let registry = Arc::new(HookRegistry::new());
374 let counter = Arc::new(AtomicU32::new(0));
375
376 let registry_for_handler = Arc::clone(®istry);
377 let _unsubscribe1 = registry.on::<Ping>(ping_type(), move |_, _| {
378 registry_for_handler.clear::<Ping>(&ping_type());
379 Ok(())
380 });
381
382 let cloned = Arc::clone(&counter);
383 let _unsubscribe2 = registry.on::<Ping>(ping_type(), move |_, _| {
384 cloned.fetch_add(1, Ordering::SeqCst);
385 Ok(())
386 });
387
388 registry
389 .emit(&Ping, CancellationToken::new())
390 .expect("emit");
391
392 assert_eq!(counter.load(Ordering::SeqCst), 1);
393 assert!(!registry.has_handlers::<Ping>(&ping_type()));
394 }
395}