use std::fmt;
use tokio::sync::broadcast;
const EVENT_CHANNEL_CAPACITY: usize = 256;
#[derive(Debug, Clone)]
pub enum Event {
ConfigReloaded,
ConfigReloadFailed { error: String },
CertIssued { domain: String },
CertRenewalFailed { domain: String, error: String },
UpstreamHealthChanged { address: String, healthy: bool },
ShutdownInitiated,
Custom { name: String, data: String },
}
impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Event::ConfigReloaded => write!(f, "config_reloaded"),
Event::ConfigReloadFailed { error } => write!(f, "config_reload_failed: {error}"),
Event::CertIssued { domain } => write!(f, "cert_issued: {domain}"),
Event::CertRenewalFailed { domain, error } => {
write!(f, "cert_renewal_failed: {domain}: {error}")
}
Event::UpstreamHealthChanged { address, healthy } => {
write!(f, "upstream_health: {address} healthy={healthy}")
}
Event::ShutdownInitiated => write!(f, "shutdown_initiated"),
Event::Custom { name, data } => write!(f, "custom:{name}: {data}"),
}
}
}
#[derive(Clone)]
pub struct EventBus {
tx: broadcast::Sender<Event>,
}
impl EventBus {
pub fn new() -> Self {
let (tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
Self { tx }
}
pub fn emit(&self, event: Event) -> usize {
self.tx.send(event).unwrap_or(0)
}
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
self.tx.subscribe()
}
}
impl Default for EventBus {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn emit_and_receive() {
let bus = EventBus::new();
let mut rx = bus.subscribe();
bus.emit(Event::ConfigReloaded);
let event = rx.recv().await.unwrap();
assert!(matches!(event, Event::ConfigReloaded));
}
#[tokio::test]
async fn multiple_subscribers() {
let bus = EventBus::new();
let mut rx1 = bus.subscribe();
let mut rx2 = bus.subscribe();
let count = bus.emit(Event::ShutdownInitiated);
assert_eq!(count, 2);
assert!(matches!(
rx1.recv().await.unwrap(),
Event::ShutdownInitiated
));
assert!(matches!(
rx2.recv().await.unwrap(),
Event::ShutdownInitiated
));
}
#[tokio::test]
async fn no_subscribers() {
let bus = EventBus::new();
let count = bus.emit(Event::ConfigReloaded);
assert_eq!(count, 0);
}
#[tokio::test]
async fn custom_event() {
let bus = EventBus::new();
let mut rx = bus.subscribe();
bus.emit(Event::Custom {
name: "my_plugin".into(),
data: "something happened".into(),
});
let event = rx.recv().await.unwrap();
if let Event::Custom { name, data } = event {
assert_eq!(name, "my_plugin");
assert_eq!(data, "something happened");
} else {
panic!("expected Custom event");
}
}
}