use super::error::SignalError;
use std::any::TypeId;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Debug, Clone)]
enum SignalNameInner {
Static(&'static str),
Owned(Arc<str>),
}
#[derive(Debug, Clone)]
pub struct SignalName(SignalNameInner);
impl SignalName {
pub const PRE_SAVE: Self = Self(SignalNameInner::Static("pre_save"));
pub const POST_SAVE: Self = Self(SignalNameInner::Static("post_save"));
pub const PRE_DELETE: Self = Self(SignalNameInner::Static("pre_delete"));
pub const POST_DELETE: Self = Self(SignalNameInner::Static("post_delete"));
pub const PRE_INIT: Self = Self(SignalNameInner::Static("pre_init"));
pub const POST_INIT: Self = Self(SignalNameInner::Static("post_init"));
pub const M2M_CHANGED: Self = Self(SignalNameInner::Static("m2m_changed"));
pub const CLASS_PREPARED: Self = Self(SignalNameInner::Static("class_prepared"));
pub const PRE_MIGRATE: Self = Self(SignalNameInner::Static("pre_migrate"));
pub const POST_MIGRATE: Self = Self(SignalNameInner::Static("post_migrate"));
pub const REQUEST_STARTED: Self = Self(SignalNameInner::Static("request_started"));
pub const REQUEST_FINISHED: Self = Self(SignalNameInner::Static("request_finished"));
pub const GOT_REQUEST_EXCEPTION: Self = Self(SignalNameInner::Static("got_request_exception"));
pub const SETTING_CHANGED: Self = Self(SignalNameInner::Static("setting_changed"));
pub const DB_BEFORE_INSERT: Self = Self(SignalNameInner::Static("db_before_insert"));
pub const DB_AFTER_INSERT: Self = Self(SignalNameInner::Static("db_after_insert"));
pub const DB_BEFORE_UPDATE: Self = Self(SignalNameInner::Static("db_before_update"));
pub const DB_AFTER_UPDATE: Self = Self(SignalNameInner::Static("db_after_update"));
pub const DB_BEFORE_DELETE: Self = Self(SignalNameInner::Static("db_before_delete"));
pub const DB_AFTER_DELETE: Self = Self(SignalNameInner::Static("db_after_delete"));
pub const fn custom(name: &'static str) -> Self {
Self(SignalNameInner::Static(name))
}
pub fn from_string(name: impl Into<Arc<str>>) -> Self {
Self(SignalNameInner::Owned(name.into()))
}
pub fn custom_validated(name: &'static str) -> Result<Self, SignalError> {
validate_signal_name(name)?;
Ok(Self(SignalNameInner::Static(name)))
}
pub fn reserved_names() -> &'static [&'static str] {
&[
"pre_save",
"post_save",
"pre_delete",
"post_delete",
"pre_init",
"post_init",
"m2m_changed",
"class_prepared",
"pre_migrate",
"post_migrate",
"request_started",
"request_finished",
"got_request_exception",
"setting_changed",
"db_before_insert",
"db_after_insert",
"db_before_update",
"db_after_update",
"db_before_delete",
"db_after_delete",
]
}
pub fn as_str(&self) -> &str {
match &self.0 {
SignalNameInner::Static(s) => s,
SignalNameInner::Owned(s) => s,
}
}
}
impl PartialEq for SignalName {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for SignalName {}
impl std::hash::Hash for SignalName {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl fmt::Display for SignalName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<SignalName> for String {
fn from(name: SignalName) -> String {
name.as_str().to_string()
}
}
impl AsRef<str> for SignalName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
fn validate_signal_name(name: &str) -> Result<(), SignalError> {
if name.is_empty() {
return Err(SignalError::new("Signal name cannot be empty"));
}
if SignalName::reserved_names().contains(&name) {
return Err(SignalError::new(format!(
"Signal name '{}' is reserved and cannot be used for custom signals",
name
)));
}
if !name
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
{
return Err(SignalError::new(format!(
"Signal name '{}' must use snake_case format (lowercase letters, numbers, and underscores only)",
name
)));
}
if let Some(first) = name.chars().next()
&& !first.is_ascii_lowercase()
&& first != '_'
{
return Err(SignalError::new(format!(
"Signal name '{}' must start with a lowercase letter or underscore",
name
)));
}
if name.contains("__") {
return Err(SignalError::new(format!(
"Signal name '{}' cannot contain consecutive underscores",
name
)));
}
if name.ends_with('_') {
return Err(SignalError::new(format!(
"Signal name '{}' cannot end with an underscore",
name
)));
}
Ok(())
}
pub trait SignalDispatcher<T: Send + Sync + 'static> {
fn receiver_count(&self) -> usize;
fn disconnect_all(&self);
fn disconnect(&self, dispatch_uid: &str) -> bool;
}
#[async_trait::async_trait]
pub trait AsyncSignalDispatcher<T: Send + Sync + 'static>: SignalDispatcher<T> {
async fn send(&self, instance: T) -> Result<(), SignalError>;
async fn send_with_sender(
&self,
instance: T,
sender_type_id: Option<TypeId>,
) -> Result<(), SignalError>;
async fn send_robust(
&self,
instance: T,
sender_type_id: Option<TypeId>,
) -> Vec<Result<(), SignalError>>;
}
pub type ReceiverFn<T> = Arc<
dyn Fn(Arc<T>) -> Pin<Box<dyn Future<Output = Result<(), SignalError>> + Send>> + Send + Sync,
>;
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn test_signal_name_static_constant() {
let name = SignalName::PRE_SAVE;
let str_repr = name.as_str();
assert_eq!(str_repr, "pre_save");
}
#[rstest]
fn test_signal_name_custom_static() {
let name = SignalName::custom("my_custom_signal");
let str_repr = name.as_str();
assert_eq!(str_repr, "my_custom_signal");
}
#[rstest]
fn test_signal_name_from_string_owned() {
let dynamic_name = format!("dynamic_signal_{}", 42);
let name = SignalName::from_string(dynamic_name.clone());
assert_eq!(name.as_str(), "dynamic_signal_42");
}
#[rstest]
fn test_signal_name_from_string_equality_with_static() {
let static_name = SignalName::custom("test_signal");
let owned_name = SignalName::from_string("test_signal");
assert_eq!(static_name, owned_name);
}
#[rstest]
fn test_signal_name_from_string_hash_consistency() {
use std::collections::HashSet;
let static_name = SignalName::custom("test_signal");
let owned_name = SignalName::from_string("test_signal");
let mut set = HashSet::new();
set.insert(static_name);
assert!(set.contains(&owned_name));
}
#[rstest]
fn test_signal_name_from_string_clone() {
let name = SignalName::from_string("cloneable_signal");
let cloned = name.clone();
assert_eq!(name, cloned);
assert_eq!(cloned.as_str(), "cloneable_signal");
}
#[rstest]
fn test_signal_name_display() {
let static_name = SignalName::PRE_SAVE;
let owned_name = SignalName::from_string("dynamic_signal");
assert_eq!(format!("{}", static_name), "pre_save");
assert_eq!(format!("{}", owned_name), "dynamic_signal");
}
#[rstest]
fn test_signal_name_into_string() {
let name = SignalName::from_string("convertible");
let s: String = name.into();
assert_eq!(s, "convertible");
}
#[rstest]
fn test_signal_name_as_ref() {
let name = SignalName::from_string("referenceable");
let s: &str = name.as_ref();
assert_eq!(s, "referenceable");
}
}