use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use crate::dx::DxComponent;
type BoxFuture = Pin<Box<dyn Future<Output = Result<(), DispatchError>> + Send>>;
type ErasedListener = Arc<dyn Fn(serde_json::Value) -> BoxFuture + Send + Sync>;
pub trait Event: DxComponent + Send + Sync + 'static {}
#[derive(Debug)]
pub enum DispatchError {
Serialize(String),
Deserialize,
Listener(String),
}
impl std::fmt::Display for DispatchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Serialize(msg) => write!(f, "event serialization failed: {msg}"),
Self::Deserialize => write!(f, "event payload did not deserialize by listener"),
Self::Listener(msg) => write!(f, "listener error: {msg}"),
}
}
}
impl std::error::Error for DispatchError {}
#[derive(Clone)]
pub struct Dispatcher {
listeners: Arc<HashMap<String, Vec<ErasedListener>>>,
record: Option<Arc<Mutex<Vec<String>>>>,
}
impl Dispatcher {
#[must_use]
pub fn new() -> Self {
Self {
listeners: Arc::new(HashMap::new()),
record: None,
}
}
#[must_use]
pub fn recording() -> Self {
Self {
listeners: Arc::new(HashMap::new()),
record: Some(Arc::new(Mutex::new(Vec::new()))),
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn register<E, F, Fut>(self, handler: F) -> Self
where
E: Event + serde::Serialize + serde::de::DeserializeOwned,
F: Fn(E) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), DispatchError>> + Send + 'static,
{
let handler = Arc::new(handler);
let erased: ErasedListener = Arc::new(move |event: serde_json::Value| {
let handler = handler.clone();
Box::pin(async move {
let event: E =
serde_json::from_value(event).map_err(|_| DispatchError::Deserialize)?;
handler(event).await
})
});
let mut map = (*self.listeners).clone();
map.entry(E::NAME.to_string()).or_default().push(erased);
Self {
listeners: Arc::new(map),
record: self.record,
}
}
pub async fn dispatch<E>(&self, event: &E) -> Result<(), DispatchError>
where
E: Event + serde::Serialize,
{
if let Some(record) = &self.record
&& let Ok(mut guard) = record.lock()
{
guard.push(E::NAME.to_string());
}
let listeners = self.listeners.get(E::NAME);
if listeners.is_none_or(|l| l.is_empty()) {
return Ok(());
}
let value =
serde_json::to_value(event).map_err(|e| DispatchError::Serialize(e.to_string()))?;
let listeners = listeners.expect("checked non-empty above");
let mut first_error: Option<DispatchError> = None;
for listener in listeners {
match listener(value.clone()).await {
Ok(()) => {}
Err(e) => {
eprintln!("event listener error for {}: {e}", E::NAME);
if first_error.is_none() {
first_error = Some(e);
}
}
}
}
match first_error {
Some(e) => Err(e),
None => Ok(()),
}
}
#[must_use]
pub fn was_dispatched(&self, name: &str) -> bool {
self.record
.as_ref()
.map(|r| {
r.lock()
.map(|guard| guard.iter().any(|n| n == name))
.unwrap_or(false)
})
.unwrap_or(false)
}
#[must_use]
pub fn dispatched_events(&self) -> Vec<String> {
self.record
.as_ref()
.and_then(|r| r.lock().ok())
.map(|guard| guard.clone())
.unwrap_or_default()
}
#[must_use]
pub fn listener_count(&self, event_name: &str) -> usize {
self.listeners.get(event_name).map(|l| l.len()).unwrap_or(0)
}
}
impl Default for Dispatcher {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for Dispatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Dispatcher")
.field("event_types", &self.listeners.len())
.field("is_recording", &self.record.is_some())
.finish_non_exhaustive()
}
}