use futures_core::Stream;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::sync::broadcast;
pub type EventKey = String;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum EventPayload {
Empty,
String(String),
Map(HashMap<String, serde_json::Value>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LaneEvent {
pub key: EventKey,
pub payload: EventPayload,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
impl LaneEvent {
pub fn new(key: impl Into<String>, payload: EventPayload) -> Self {
Self {
key: key.into(),
payload,
timestamp: chrono::Utc::now(),
}
}
pub fn empty(key: impl Into<String>) -> Self {
Self::new(key, EventPayload::Empty)
}
pub fn with_string(key: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(key, EventPayload::String(message.into()))
}
pub fn with_map(key: impl Into<String>, map: HashMap<String, serde_json::Value>) -> Self {
Self::new(key, EventPayload::Map(map))
}
}
#[derive(Clone)]
pub struct EventEmitter {
sender: Arc<broadcast::Sender<LaneEvent>>,
}
impl EventEmitter {
pub fn new(capacity: usize) -> Self {
let (sender, _) = broadcast::channel(capacity);
Self {
sender: Arc::new(sender),
}
}
pub fn emit(&self, event: LaneEvent) {
let _ = self.sender.send(event);
}
pub fn subscribe(&self) -> broadcast::Receiver<LaneEvent> {
self.sender.subscribe()
}
pub fn subscribe_filtered(
&self,
filter: impl Fn(&LaneEvent) -> bool + Send + Sync + 'static,
) -> EventStream {
use tokio_stream::wrappers::BroadcastStream;
use tokio_stream::StreamExt as TokioStreamExt;
let stream = BroadcastStream::new(self.sender.subscribe())
.filter_map(|r: Result<LaneEvent, _>| r.ok())
.filter(move |e| filter(e));
EventStream {
inner: Box::pin(stream),
}
}
pub fn subscribe_stream(&self) -> EventStream {
self.subscribe_filtered(|_| true)
}
}
pub struct EventStream {
inner: Pin<Box<dyn Stream<Item = LaneEvent> + Send>>,
}
impl Stream for EventStream {
type Item = LaneEvent;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.inner.as_mut().poll_next(cx)
}
}
impl EventStream {
pub async fn recv(&mut self) -> Option<LaneEvent> {
use tokio_stream::StreamExt;
self.next().await
}
}
pub mod events {
pub const QUEUE_LANE_PRESSURE: &str = "queue.lane.pressure";
pub const QUEUE_LANE_IDLE: &str = "queue.lane.idle";
pub const QUEUE_COMMAND_SUBMITTED: &str = "queue.command.submitted";
pub const QUEUE_COMMAND_STARTED: &str = "queue.command.started";
pub const QUEUE_COMMAND_COMPLETED: &str = "queue.command.completed";
pub const QUEUE_COMMAND_FAILED: &str = "queue.command.failed";
pub const QUEUE_COMMAND_TIMEOUT: &str = "queue.command.timeout";
pub const QUEUE_COMMAND_RETRY: &str = "queue.command.retry";
pub const QUEUE_COMMAND_DEAD_LETTERED: &str = "queue.command.dead_lettered";
pub const QUEUE_SHUTDOWN_STARTED: &str = "queue.shutdown.started";
pub const QUEUE_SHUTDOWN_COMPLETE: &str = "queue.shutdown.complete";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lane_event_new() {
let event = LaneEvent::new("test.event", EventPayload::Empty);
assert_eq!(event.key, "test.event");
assert!(matches!(event.payload, EventPayload::Empty));
}
#[test]
fn test_lane_event_empty() {
let event = LaneEvent::empty("queue.ready");
assert_eq!(event.key, "queue.ready");
assert!(matches!(event.payload, EventPayload::Empty));
}
#[test]
fn test_lane_event_with_string() {
let event = LaneEvent::with_string("queue.error", "Connection lost");
assert_eq!(event.key, "queue.error");
if let EventPayload::String(msg) = &event.payload {
assert_eq!(msg, "Connection lost");
} else {
panic!("Expected string payload");
}
}
#[test]
fn test_lane_event_with_map() {
let mut map = HashMap::new();
map.insert("lane_id".to_string(), serde_json::json!("query"));
map.insert("pending".to_string(), serde_json::json!(10));
let event = LaneEvent::with_map("queue.status", map);
assert_eq!(event.key, "queue.status");
if let EventPayload::Map(m) = &event.payload {
assert_eq!(m.get("lane_id").unwrap(), &serde_json::json!("query"));
assert_eq!(m.get("pending").unwrap(), &serde_json::json!(10));
} else {
panic!("Expected map payload");
}
}
#[test]
fn test_lane_event_timestamp() {
let before = chrono::Utc::now();
let event = LaneEvent::empty("test.event");
let after = chrono::Utc::now();
assert!(event.timestamp >= before);
assert!(event.timestamp <= after);
}
#[test]
fn test_event_emitter_new() {
let emitter = EventEmitter::new(100);
let _receiver = emitter.subscribe();
}
#[test]
fn test_event_emitter_clone() {
let emitter = EventEmitter::new(100);
let cloned = emitter.clone();
emitter.emit(LaneEvent::empty("test.1"));
cloned.emit(LaneEvent::empty("test.2"));
}
#[tokio::test]
async fn test_event_emitter_subscribe() {
let emitter = EventEmitter::new(100);
let mut receiver = emitter.subscribe();
emitter.emit(LaneEvent::empty("test.event"));
let event = receiver.recv().await.unwrap();
assert_eq!(event.key, "test.event");
}
#[tokio::test]
async fn test_event_emitter_multiple_subscribers() {
let emitter = EventEmitter::new(100);
let mut receiver1 = emitter.subscribe();
let mut receiver2 = emitter.subscribe();
emitter.emit(LaneEvent::with_string("broadcast", "hello"));
let event1 = receiver1.recv().await.unwrap();
let event2 = receiver2.recv().await.unwrap();
assert_eq!(event1.key, "broadcast");
assert_eq!(event2.key, "broadcast");
}
#[tokio::test]
async fn test_event_emitter_multiple_events() {
let emitter = EventEmitter::new(100);
let mut receiver = emitter.subscribe();
emitter.emit(LaneEvent::empty("event.1"));
emitter.emit(LaneEvent::empty("event.2"));
emitter.emit(LaneEvent::empty("event.3"));
assert_eq!(receiver.recv().await.unwrap().key, "event.1");
assert_eq!(receiver.recv().await.unwrap().key, "event.2");
assert_eq!(receiver.recv().await.unwrap().key, "event.3");
}
#[tokio::test]
async fn test_event_stream_filtered() {
let emitter = EventEmitter::new(100);
let mut stream = emitter.subscribe_filtered(|e| e.key.starts_with("queue."));
emitter.emit(LaneEvent::empty("other.event"));
emitter.emit(LaneEvent::empty("queue.created"));
emitter.emit(LaneEvent::empty("another.event"));
emitter.emit(LaneEvent::empty("queue.destroyed"));
let event1 = stream.recv().await.unwrap();
assert_eq!(event1.key, "queue.created");
let event2 = stream.recv().await.unwrap();
assert_eq!(event2.key, "queue.destroyed");
}
#[test]
fn test_event_payload_serialization() {
let payload = EventPayload::String("test message".to_string());
let json = serde_json::to_string(&payload).unwrap();
let parsed: EventPayload = serde_json::from_str(&json).unwrap();
if let EventPayload::String(s) = parsed {
assert_eq!(s, "test message");
} else {
panic!("Expected string payload");
}
}
#[test]
fn test_lane_event_serialization() {
let event = LaneEvent::with_string("test.event", "hello");
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("test.event"));
assert!(json.contains("hello"));
assert!(json.contains("timestamp"));
let parsed: LaneEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.key, "test.event");
}
#[tokio::test]
async fn test_event_stream_implements_stream() {
use tokio_stream::StreamExt;
let emitter = EventEmitter::new(100);
let mut stream = emitter.subscribe_stream();
emitter.emit(LaneEvent::empty("test.stream.event"));
let event = tokio::time::timeout(std::time::Duration::from_millis(200), stream.next())
.await
.expect("Timeout waiting for event via Stream::next")
.expect("Stream ended unexpectedly");
assert_eq!(event.key, "test.stream.event");
}
#[tokio::test]
async fn test_event_stream_subscribe_stream() {
use tokio_stream::StreamExt;
let emitter = EventEmitter::new(100);
let mut stream = emitter.subscribe_stream();
emitter.emit(LaneEvent::empty("stream.1"));
emitter.emit(LaneEvent::empty("stream.2"));
emitter.emit(LaneEvent::empty("stream.3"));
for expected in ["stream.1", "stream.2", "stream.3"] {
let event = tokio::time::timeout(std::time::Duration::from_millis(200), stream.next())
.await
.expect("Timeout")
.expect("Stream ended");
assert_eq!(event.key, expected);
}
}
#[tokio::test]
async fn test_event_stream_recv_still_works() {
let emitter = EventEmitter::new(100);
let mut stream = emitter.subscribe_filtered(|e| e.key.starts_with("ok."));
emitter.emit(LaneEvent::empty("skip.this"));
emitter.emit(LaneEvent::empty("ok.recv.event"));
let event = tokio::time::timeout(std::time::Duration::from_millis(200), stream.recv())
.await
.expect("Timeout waiting for event via recv()")
.expect("Stream ended");
assert_eq!(event.key, "ok.recv.event");
}
#[test]
fn test_event_catalog() {
assert_eq!(events::QUEUE_LANE_PRESSURE, "queue.lane.pressure");
assert_eq!(events::QUEUE_LANE_IDLE, "queue.lane.idle");
assert_eq!(events::QUEUE_COMMAND_SUBMITTED, "queue.command.submitted");
assert_eq!(events::QUEUE_COMMAND_STARTED, "queue.command.started");
assert_eq!(events::QUEUE_COMMAND_COMPLETED, "queue.command.completed");
assert_eq!(events::QUEUE_COMMAND_FAILED, "queue.command.failed");
assert_eq!(events::QUEUE_COMMAND_TIMEOUT, "queue.command.timeout");
assert_eq!(events::QUEUE_COMMAND_RETRY, "queue.command.retry");
assert_eq!(
events::QUEUE_COMMAND_DEAD_LETTERED,
"queue.command.dead_lettered"
);
assert_eq!(events::QUEUE_SHUTDOWN_STARTED, "queue.shutdown.started");
assert_eq!(events::QUEUE_SHUTDOWN_COMPLETE, "queue.shutdown.complete");
}
}