use anyhow::Result;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ExecutorEvent {
pub execution_id: String,
pub event_type: String,
pub step: String,
pub status: String,
pub created_at: DateTime<Utc>,
pub context: serde_json::Value,
}
#[async_trait]
pub trait EventSink: Send + Sync {
async fn emit(&self, event: ExecutorEvent) -> Result<()>;
}
pub struct EventEmitter {
pub sink: std::sync::Arc<dyn EventSink>,
pub execution_id: String,
}
impl EventEmitter {
pub fn new(execution_id: String, sink: std::sync::Arc<dyn EventSink>) -> Self {
Self { sink, execution_id }
}
pub async fn emit(
&self,
event_type: &str,
step: &str,
status: &str,
context: serde_json::Value,
) -> Result<()> {
let event = ExecutorEvent {
execution_id: self.execution_id.clone(),
event_type: event_type.to_string(),
step: step.to_string(),
status: status.to_string(),
created_at: Utc::now(),
context,
};
self.sink.emit(event).await
}
}
#[derive(Default)]
pub struct NoopSink;
#[async_trait]
impl EventSink for NoopSink {
async fn emit(&self, _event: ExecutorEvent) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[tokio::test]
async fn noop_sink_accepts_any_event() {
let sink: Arc<dyn EventSink> = Arc::new(NoopSink);
let emitter = EventEmitter::new("exec_test".into(), sink);
emitter
.emit(
"batch.completed",
"start",
"COMPLETED",
serde_json::json!({"processing_ms": 12.3}),
)
.await
.expect("noop emit");
}
}