pub mod batching;
mod context;
mod core;
mod db_events;
pub mod debugger;
pub mod dispatch;
pub mod dlq;
pub mod doc_generator;
pub mod error;
pub mod history;
pub mod injectable_signal;
mod lifecycle_events;
mod middleware;
mod model_signals;
pub mod persistence;
pub mod profiler;
pub mod receiver_context;
pub mod receiver_registry;
mod registry;
pub mod replay;
mod request_events;
mod signal;
pub mod throttling;
pub mod visualization;
pub mod distributed;
pub mod graphql_integration;
pub mod orm_integration;
pub mod transaction;
pub mod websocket_integration;
pub use context::{SignalContext, SignalMetrics};
pub use core::{AsyncSignalDispatcher, ReceiverFn, SignalDispatcher, SignalName};
pub use error::SignalError;
pub use injectable_signal::InjectableSignal;
pub use middleware::{MiddlewareFn, SignalCall, SignalMiddleware, SignalSpy};
pub use receiver_context::ReceiverContext;
pub use receiver_registry::{ReceiverRegistryEntry, auto_connect_receivers};
pub use registry::{get_signal, get_signal_with_string};
pub use signal::Signal;
pub use model_signals::{post_delete, post_save, pre_delete, pre_save};
pub use db_events::{
DbEvent, after_delete, after_insert, after_update, before_delete, before_insert, before_update,
};
pub use lifecycle_events::{
ClassPreparedEvent, M2MAction, M2MChangeEvent, MigrationEvent, PostInitEvent, PreInitEvent,
class_prepared, m2m_changed, post_init, post_migrate, pre_init, pre_migrate,
};
pub use request_events::{
GotRequestExceptionEvent, RequestFinishedEvent, RequestStartedEvent, SettingChangedEvent,
got_request_exception, request_finished, request_started, setting_changed,
};
pub use dispatch::{SyncReceiverFn, SyncSignal};
#[macro_export]
macro_rules! connect_receiver {
($signal:expr, $receiver:expr) => {
$signal.connect($receiver)
};
($signal:expr, $receiver:expr, priority = $priority:expr) => {
$signal.connect_with_priority($receiver, $priority)
};
($signal:expr, $receiver:expr, dispatch_uid = $uid:expr) => {
$signal.connect_with_options($receiver, None, Some($uid.to_string()), 0)
};
($signal:expr, $receiver:expr, sender = $sender:ty) => {
$signal.connect_with_options($receiver, Some(std::any::TypeId::of::<$sender>()), None, 0)
};
($signal:expr, $receiver:expr, priority = $priority:expr, dispatch_uid = $uid:expr) => {
$signal.connect_with_options($receiver, None, Some($uid.to_string()), $priority)
};
($signal:expr, $receiver:expr, sender = $sender:ty, dispatch_uid = $uid:expr) => {
$signal.connect_with_options(
$receiver,
Some(std::any::TypeId::of::<$sender>()),
Some($uid.to_string()),
0,
)
};
($signal:expr, $receiver:expr, sender = $sender:ty, priority = $priority:expr) => {
$signal.connect_with_options(
$receiver,
Some(std::any::TypeId::of::<$sender>()),
None,
$priority,
)
};
($signal:expr, $receiver:expr, sender = $sender:ty, dispatch_uid = $uid:expr, priority = $priority:expr) => {
$signal.connect_with_options(
$receiver,
Some(std::any::TypeId::of::<$sender>()),
Some($uid.to_string()),
$priority,
)
};
}
#[cfg(test)]
mod tests {
use super::*;
use parking_lot::Mutex;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct TestModel {
id: i32,
name: String,
}
#[tokio::test]
async fn test_signal_connect_and_send() {
let signal = Signal::new(SignalName::custom("test"));
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = Arc::clone(&counter);
signal.connect(move |_instance: Arc<TestModel>| {
let counter = Arc::clone(&counter_clone);
async move {
counter.fetch_add(1, Ordering::SeqCst);
Ok(())
}
});
let model = TestModel {
id: 1,
name: "Test".to_string(),
};
signal.send(model).await.unwrap();
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_signals_multiple_receivers() {
let signal = Signal::new(SignalName::custom("test"));
let counter = Arc::new(AtomicUsize::new(0));
for _ in 0..3 {
let counter_clone = Arc::clone(&counter);
signal.connect(move |_instance: Arc<TestModel>| {
let counter = Arc::clone(&counter_clone);
async move {
counter.fetch_add(1, Ordering::SeqCst);
Ok(())
}
});
}
let model = TestModel {
id: 1,
name: "Test".to_string(),
};
signal.send(model).await.unwrap();
assert_eq!(counter.load(Ordering::SeqCst), 3);
}
#[tokio::test]
async fn test_signals_pre_post_save() {
let pre_counter = Arc::new(AtomicUsize::new(0));
let post_counter = Arc::new(AtomicUsize::new(0));
let pre_clone = Arc::clone(&pre_counter);
pre_save::<TestModel>().connect(move |_instance| {
let counter = Arc::clone(&pre_clone);
async move {
counter.fetch_add(1, Ordering::SeqCst);
Ok(())
}
});
let post_clone = Arc::clone(&post_counter);
post_save::<TestModel>().connect(move |_instance| {
let counter = Arc::clone(&post_clone);
async move {
counter.fetch_add(1, Ordering::SeqCst);
Ok(())
}
});
let model = TestModel {
id: 1,
name: "Test".to_string(),
};
pre_save::<TestModel>().send(model.clone()).await.unwrap();
post_save::<TestModel>().send(model).await.unwrap();
assert_eq!(pre_counter.load(Ordering::SeqCst), 1);
assert_eq!(post_counter.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_signals_global_registry() {
let signal1 = get_signal::<TestModel>(SignalName::custom("custom_signal"));
let signal2 = get_signal::<TestModel>(SignalName::custom("custom_signal"));
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = Arc::clone(&counter);
signal1.connect(move |_instance| {
let counter = Arc::clone(&counter_clone);
async move {
counter.fetch_add(1, Ordering::SeqCst);
Ok(())
}
});
let model = TestModel {
id: 1,
name: "Test".to_string(),
};
signal2.send(model).await.unwrap();
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_signal_spy() {
let signal = Signal::new(SignalName::custom("test"));
let spy = SignalSpy::new();
signal.add_middleware(spy.clone());
signal.connect(|_instance: Arc<TestModel>| async move { Ok(()) });
assert!(!spy.was_called());
assert_eq!(spy.call_count(), 0);
let model = TestModel {
id: 1,
name: "Test".to_string(),
};
signal.send(model).await.unwrap();
assert!(spy.was_called());
assert_eq!(spy.call_count(), 1);
assert_eq!(spy.total_receivers_called(), 1);
assert!(!spy.has_errors());
}
#[tokio::test]
async fn test_signal_chain() {
let signal_a = Signal::new(SignalName::custom("signal_a"));
let signal_b = Signal::new(SignalName::custom("signal_b"));
let calls = Arc::new(Mutex::new(Vec::new()));
let calls_a = calls.clone();
signal_a.connect(move |instance: Arc<TestModel>| {
let calls = calls_a.clone();
async move {
calls.lock().push(format!("signal_a: {}", instance.id));
Ok(())
}
});
let calls_b = calls.clone();
signal_b.connect(move |instance: Arc<TestModel>| {
let calls = calls_b.clone();
async move {
calls.lock().push(format!("signal_b: {}", instance.id));
Ok(())
}
});
signal_a.chain(&signal_b);
let model = TestModel {
id: 1,
name: "Test".to_string(),
};
signal_a.send(model).await.unwrap();
let results = calls.lock();
assert_eq!(results.len(), 2);
assert_eq!(results[0], "signal_a: 1");
assert_eq!(results[1], "signal_b: 1");
}
#[tokio::test]
async fn test_signal_filter() {
let signal = Signal::new(SignalName::custom("user_signal"));
let admin_only = signal.filter(|model: &TestModel| model.id > 100);
let admin_calls = Arc::new(Mutex::new(Vec::new()));
let all_calls = Arc::new(Mutex::new(Vec::new()));
let admin = admin_calls.clone();
admin_only.connect(move |instance: Arc<TestModel>| {
let admin = admin.clone();
async move {
admin.lock().push(instance.id);
Ok(())
}
});
let all = all_calls.clone();
signal.connect(move |instance: Arc<TestModel>| {
let all = all.clone();
async move {
all.lock().push(instance.id);
Ok(())
}
});
signal
.send(TestModel {
id: 50,
name: "Regular".to_string(),
})
.await
.unwrap();
signal
.send(TestModel {
id: 101,
name: "Admin".to_string(),
})
.await
.unwrap();
let admin_results = admin_calls.lock();
let all_results = all_calls.lock();
assert_eq!(admin_results.len(), 1); assert_eq!(admin_results[0], 101);
assert_eq!(all_results.len(), 2); }
#[test]
fn test_signal_name_validation() {
assert!(SignalName::custom_validated("my_custom_signal").is_ok());
assert!(SignalName::custom_validated("user_created").is_ok());
assert!(SignalName::custom_validated("").is_err());
assert!(SignalName::custom_validated("MySignal").is_err());
assert!(SignalName::custom_validated("pre_save").is_err());
}
#[tokio::test]
async fn test_all_signal_types_accessible() {
let _ = pre_save::<TestModel>();
let _ = post_save::<TestModel>();
let _ = pre_delete::<TestModel>();
let _ = post_delete::<TestModel>();
let _ = pre_init::<TestModel>();
let _ = post_init::<TestModel>();
let _ = m2m_changed::<TestModel, TestModel>();
let _ = pre_migrate();
let _ = post_migrate();
let _ = class_prepared();
let _ = request_started();
let _ = request_finished();
let _ = got_request_exception();
let _ = setting_changed();
let _ = before_insert();
let _ = after_insert();
let _ = before_update();
let _ = after_update();
let _ = before_delete();
let _ = after_delete();
}
}