use std::collections::HashMap;
use std::sync::Arc;
use super::models::{MarkovBlanketMessage, TracingEvent};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HookEvent {
SessionStart,
SessionEnd,
TimestepStart,
TimestepEnd,
EventRecorded,
MessageRecorded,
BeforeSave,
AfterSave,
}
impl std::fmt::Display for HookEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HookEvent::SessionStart => write!(f, "session_start"),
HookEvent::SessionEnd => write!(f, "session_end"),
HookEvent::TimestepStart => write!(f, "timestep_start"),
HookEvent::TimestepEnd => write!(f, "timestep_end"),
HookEvent::EventRecorded => write!(f, "event_recorded"),
HookEvent::MessageRecorded => write!(f, "message_recorded"),
HookEvent::BeforeSave => write!(f, "before_save"),
HookEvent::AfterSave => write!(f, "after_save"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct HookContext {
pub session_id: Option<String>,
pub step_id: Option<String>,
pub event: Option<TracingEvent>,
pub message: Option<MarkovBlanketMessage>,
}
impl HookContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
pub fn with_step(mut self, step_id: impl Into<String>) -> Self {
self.step_id = Some(step_id.into());
self
}
pub fn with_event(mut self, event: TracingEvent) -> Self {
self.event = Some(event);
self
}
pub fn with_message(mut self, message: MarkovBlanketMessage) -> Self {
self.message = Some(message);
self
}
}
pub type HookCallback = Arc<dyn Fn(&HookContext) + Send + Sync>;
struct HookRegistration {
callback: HookCallback,
priority: i32,
}
pub struct HookManager {
hooks: HashMap<HookEvent, Vec<HookRegistration>>,
}
impl Default for HookManager {
fn default() -> Self {
Self::new()
}
}
impl HookManager {
pub fn new() -> Self {
Self {
hooks: HashMap::new(),
}
}
pub fn register(&mut self, event: HookEvent, callback: HookCallback, priority: i32) {
let registrations = self.hooks.entry(event).or_insert_with(Vec::new);
registrations.push(HookRegistration { callback, priority });
registrations.sort_by(|a, b| b.priority.cmp(&a.priority));
}
pub fn on(&mut self, event: HookEvent, callback: HookCallback) {
self.register(event, callback, 0);
}
pub fn trigger(&self, event: HookEvent, context: &HookContext) {
if let Some(registrations) = self.hooks.get(&event) {
for reg in registrations {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
(reg.callback)(context);
}))
.ok();
}
}
}
pub fn has_hooks(&self, event: HookEvent) -> bool {
self.hooks
.get(&event)
.map(|v| !v.is_empty())
.unwrap_or(false)
}
pub fn hook_count(&self, event: HookEvent) -> usize {
self.hooks.get(&event).map(|v| v.len()).unwrap_or(0)
}
pub fn clear(&mut self, event: HookEvent) {
self.hooks.remove(&event);
}
pub fn clear_all(&mut self) {
self.hooks.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicI32, Ordering};
#[test]
fn test_hook_registration() {
let mut manager = HookManager::new();
assert!(!manager.has_hooks(HookEvent::SessionStart));
manager.on(HookEvent::SessionStart, Arc::new(|_| {}));
assert!(manager.has_hooks(HookEvent::SessionStart));
assert_eq!(manager.hook_count(HookEvent::SessionStart), 1);
}
#[test]
fn test_hook_trigger() {
let mut manager = HookManager::new();
let counter = Arc::new(AtomicI32::new(0));
let counter_clone = counter.clone();
manager.on(
HookEvent::SessionStart,
Arc::new(move |_| {
counter_clone.fetch_add(1, Ordering::SeqCst);
}),
);
let context = HookContext::new().with_session("test-session");
manager.trigger(HookEvent::SessionStart, &context);
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn test_hook_priority() {
let mut manager = HookManager::new();
let order = Arc::new(std::sync::Mutex::new(Vec::new()));
let order1 = order.clone();
manager.register(
HookEvent::SessionStart,
Arc::new(move |_| {
order1.lock().unwrap().push(1);
}),
10,
);
let order2 = order.clone();
manager.register(
HookEvent::SessionStart,
Arc::new(move |_| {
order2.lock().unwrap().push(2);
}),
20, );
let order3 = order.clone();
manager.register(
HookEvent::SessionStart,
Arc::new(move |_| {
order3.lock().unwrap().push(3);
}),
5,
);
manager.trigger(HookEvent::SessionStart, &HookContext::new());
let result = order.lock().unwrap();
assert_eq!(*result, vec![2, 1, 3]); }
#[test]
fn test_hook_context() {
let context = HookContext::new()
.with_session("session-1")
.with_step("step-1");
assert_eq!(context.session_id, Some("session-1".to_string()));
assert_eq!(context.step_id, Some("step-1".to_string()));
}
#[test]
fn test_hook_clear() {
let mut manager = HookManager::new();
manager.on(HookEvent::SessionStart, Arc::new(|_| {}));
manager.on(HookEvent::SessionEnd, Arc::new(|_| {}));
assert!(manager.has_hooks(HookEvent::SessionStart));
manager.clear(HookEvent::SessionStart);
assert!(!manager.has_hooks(HookEvent::SessionStart));
assert!(manager.has_hooks(HookEvent::SessionEnd));
manager.clear_all();
assert!(!manager.has_hooks(HookEvent::SessionEnd));
}
}