use std::any::Any;
use std::fmt::Debug;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub trait Event: Send + Sync + Debug + Clone + 'static {
fn event_type() -> &'static str
where
Self: Sized;
fn event_type_id(&self) -> &'static str;
fn as_any(&self) -> &dyn Any;
fn clone_boxed(&self) -> Box<dyn AnyEvent>;
#[must_use]
fn to_json(&self) -> serde_json::Value;
}
pub trait AnyEvent: Send + Sync + Debug {
fn event_type_id(&self) -> &'static str;
fn as_any(&self) -> &dyn Any;
fn clone_boxed(&self) -> Box<dyn AnyEvent>;
#[must_use]
fn to_json(&self) -> serde_json::Value;
}
impl<T> AnyEvent for T
where
T: Event + Serialize,
{
fn event_type_id(&self) -> &'static str {
Event::event_type_id(self)
}
fn as_any(&self) -> &dyn Any {
Event::as_any(self)
}
fn clone_boxed(&self) -> Box<dyn AnyEvent> {
Event::clone_boxed(self)
}
fn to_json(&self) -> serde_json::Value {
Event::to_json(self)
}
}
impl dyn AnyEvent {
#[must_use]
pub fn downcast_ref<T: Event>(&self) -> Option<&T> {
self.as_any().downcast_ref::<T>()
}
}
impl Clone for Box<dyn AnyEvent> {
fn clone(&self) -> Self {
self.clone_boxed()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StartEvent {
pub data: serde_json::Value,
}
impl Event for StartEvent {
fn event_type() -> &'static str {
static REGISTER: std::sync::Once = std::sync::Once::new();
REGISTER.call_once(|| {
register_event_deserializer("blazen::StartEvent", |value| {
serde_json::from_value::<StartEvent>(value)
.ok()
.map(|e| Box::new(e) as _)
});
});
"blazen::StartEvent"
}
fn event_type_id(&self) -> &'static str {
"blazen::StartEvent"
}
fn as_any(&self) -> &dyn Any {
self
}
fn clone_boxed(&self) -> Box<dyn AnyEvent> {
Box::new(self.clone())
}
fn to_json(&self) -> serde_json::Value {
serde_json::to_value(self).expect("StartEvent serialization should never fail")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StopEvent {
pub result: serde_json::Value,
}
impl Event for StopEvent {
fn event_type() -> &'static str {
static REGISTER: std::sync::Once = std::sync::Once::new();
REGISTER.call_once(|| {
register_event_deserializer("blazen::StopEvent", |value| {
serde_json::from_value::<StopEvent>(value)
.ok()
.map(|e| Box::new(e) as _)
});
});
"blazen::StopEvent"
}
fn event_type_id(&self) -> &'static str {
"blazen::StopEvent"
}
fn as_any(&self) -> &dyn Any {
self
}
fn clone_boxed(&self) -> Box<dyn AnyEvent> {
Box::new(self.clone())
}
fn to_json(&self) -> serde_json::Value {
serde_json::to_value(self).expect("StopEvent serialization should never fail")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputRequestEvent {
pub request_id: String,
pub prompt: String,
pub metadata: serde_json::Value,
}
impl Event for InputRequestEvent {
fn event_type() -> &'static str {
static REGISTER: std::sync::Once = std::sync::Once::new();
REGISTER.call_once(|| {
register_event_deserializer("blazen::InputRequestEvent", |value| {
serde_json::from_value::<InputRequestEvent>(value)
.ok()
.map(|e| Box::new(e) as _)
});
});
"blazen::InputRequestEvent"
}
fn event_type_id(&self) -> &'static str {
"blazen::InputRequestEvent"
}
fn as_any(&self) -> &dyn Any {
self
}
fn clone_boxed(&self) -> Box<dyn AnyEvent> {
Box::new(self.clone())
}
fn to_json(&self) -> serde_json::Value {
serde_json::to_value(self).expect("InputRequestEvent serialization should never fail")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputResponseEvent {
pub request_id: String,
pub response: serde_json::Value,
}
impl Event for InputResponseEvent {
fn event_type() -> &'static str {
static REGISTER: std::sync::Once = std::sync::Once::new();
REGISTER.call_once(|| {
register_event_deserializer("blazen::InputResponseEvent", |value| {
serde_json::from_value::<InputResponseEvent>(value)
.ok()
.map(|e| Box::new(e) as _)
});
});
"blazen::InputResponseEvent"
}
fn event_type_id(&self) -> &'static str {
"blazen::InputResponseEvent"
}
fn as_any(&self) -> &dyn Any {
self
}
fn clone_boxed(&self) -> Box<dyn AnyEvent> {
Box::new(self.clone())
}
fn to_json(&self) -> serde_json::Value {
serde_json::to_value(self).expect("InputResponseEvent serialization should never fail")
}
}
#[derive(Debug)]
pub struct EventEnvelope {
pub event: Box<dyn AnyEvent>,
pub source_step: Option<String>,
pub timestamp: DateTime<Utc>,
pub id: Uuid,
}
impl EventEnvelope {
#[must_use]
pub fn new(event: Box<dyn AnyEvent>, source_step: Option<String>) -> Self {
Self {
event,
source_step,
timestamp: Utc::now(),
id: Uuid::new_v4(),
}
}
}
pub type EventDeserializerFn = fn(serde_json::Value) -> Option<Box<dyn AnyEvent>>;
static EVENT_DESERIALIZER_REGISTRY: std::sync::LazyLock<
dashmap::DashMap<&'static str, EventDeserializerFn>,
> = std::sync::LazyLock::new(dashmap::DashMap::new);
pub fn register_event_deserializer(event_type: &'static str, deserializer: EventDeserializerFn) {
EVENT_DESERIALIZER_REGISTRY.insert(event_type, deserializer);
}
pub fn try_deserialize_event(
event_type: &str,
data: &serde_json::Value,
) -> Option<Box<dyn AnyEvent>> {
let entry = EVENT_DESERIALIZER_REGISTRY.get(event_type)?;
let deserializer = *entry.value();
deserializer(data.clone())
}
static EVENT_TYPE_REGISTRY: std::sync::LazyLock<dashmap::DashMap<String, &'static str>> =
std::sync::LazyLock::new(dashmap::DashMap::new);
pub fn intern_event_type(name: &str) -> &'static str {
if let Some(entry) = EVENT_TYPE_REGISTRY.get(name) {
return entry.value();
}
let leaked: &'static str = Box::leak(name.to_string().into_boxed_str());
EVENT_TYPE_REGISTRY.insert(name.to_string(), leaked);
leaked
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DynamicEvent {
pub event_type: String,
pub data: serde_json::Value,
}
impl Event for DynamicEvent {
fn event_type() -> &'static str
where
Self: Sized,
{
"dynamic"
}
fn event_type_id(&self) -> &'static str {
intern_event_type(&self.event_type)
}
fn as_any(&self) -> &dyn Any {
self
}
fn clone_boxed(&self) -> Box<dyn AnyEvent> {
Box::new(self.clone())
}
fn to_json(&self) -> serde_json::Value {
self.data.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn start_event_type_id() {
assert_eq!(StartEvent::event_type(), "blazen::StartEvent");
let evt = StartEvent {
data: serde_json::json!({"key": "value"}),
};
assert_eq!(Event::event_type_id(&evt), "blazen::StartEvent");
}
#[test]
fn stop_event_type_id() {
assert_eq!(StopEvent::event_type(), "blazen::StopEvent");
let evt = StopEvent {
result: serde_json::json!(42),
};
assert_eq!(Event::event_type_id(&evt), "blazen::StopEvent");
}
#[test]
fn any_event_downcast() {
let evt = StartEvent {
data: serde_json::json!(null),
};
let boxed: Box<dyn AnyEvent> = Box::new(evt.clone());
let downcasted = boxed.downcast_ref::<StartEvent>().unwrap();
assert_eq!(downcasted.data, evt.data);
assert!(boxed.downcast_ref::<StopEvent>().is_none());
}
#[test]
fn clone_boxed_any_event() {
let evt = StopEvent {
result: serde_json::json!("done"),
};
let boxed: Box<dyn AnyEvent> = Box::new(evt);
let cloned = boxed.clone();
assert_eq!(boxed.event_type_id(), cloned.event_type_id());
assert_eq!(boxed.to_json(), cloned.to_json());
}
#[test]
fn event_envelope_constructor() {
let evt = StartEvent {
data: serde_json::json!({"hello": "world"}),
};
let envelope = EventEnvelope::new(Box::new(evt), Some("my_step".to_string()));
assert_eq!(envelope.source_step.as_deref(), Some("my_step"));
assert_eq!(envelope.event.event_type_id(), "blazen::StartEvent");
}
#[test]
fn to_json_roundtrip() {
let start = StartEvent {
data: serde_json::json!({"nums": [1, 2, 3]}),
};
let json = Event::to_json(&start);
let deserialized: StartEvent = serde_json::from_value(json).unwrap();
assert_eq!(start.data, deserialized.data);
let stop = StopEvent {
result: serde_json::json!("ok"),
};
let json = Event::to_json(&stop);
let deserialized: StopEvent = serde_json::from_value(json).unwrap();
assert_eq!(stop.result, deserialized.result);
}
#[test]
fn intern_event_type_returns_same_pointer() {
let a = intern_event_type("TestEventInEvents");
let b = intern_event_type("TestEventInEvents");
assert!(std::ptr::eq(a, b));
}
#[test]
fn dynamic_event_roundtrip() {
let evt = DynamicEvent {
event_type: "MyEvent".to_owned(),
data: serde_json::json!({"key": "value"}),
};
let json = Event::to_json(&evt);
assert_eq!(json["key"], "value");
}
#[test]
fn dynamic_event_type_id() {
let evt = DynamicEvent {
event_type: "CustomEvent".to_owned(),
data: serde_json::json!({}),
};
assert_eq!(Event::event_type_id(&evt), "CustomEvent");
}
#[test]
fn input_request_event_type_id() {
assert_eq!(InputRequestEvent::event_type(), "blazen::InputRequestEvent");
let evt = InputRequestEvent {
request_id: "req-1".to_string(),
prompt: "What is your name?".to_string(),
metadata: serde_json::json!({"choices": ["Alice", "Bob"]}),
};
assert_eq!(Event::event_type_id(&evt), "blazen::InputRequestEvent");
}
#[test]
fn input_response_event_type_id() {
assert_eq!(
InputResponseEvent::event_type(),
"blazen::InputResponseEvent"
);
let evt = InputResponseEvent {
request_id: "req-1".to_string(),
response: serde_json::json!("Alice"),
};
assert_eq!(Event::event_type_id(&evt), "blazen::InputResponseEvent");
}
#[test]
fn input_request_event_roundtrip() {
let evt = InputRequestEvent {
request_id: "req-42".to_string(),
prompt: "Pick a number".to_string(),
metadata: serde_json::json!({"min": 1, "max": 100}),
};
let json = Event::to_json(&evt);
let deserialized: InputRequestEvent = serde_json::from_value(json).unwrap();
assert_eq!(evt.request_id, deserialized.request_id);
assert_eq!(evt.prompt, deserialized.prompt);
assert_eq!(evt.metadata, deserialized.metadata);
}
#[test]
fn input_response_event_roundtrip() {
let evt = InputResponseEvent {
request_id: "req-42".to_string(),
response: serde_json::json!(77),
};
let json = Event::to_json(&evt);
let deserialized: InputResponseEvent = serde_json::from_value(json).unwrap();
assert_eq!(evt.request_id, deserialized.request_id);
assert_eq!(evt.response, deserialized.response);
}
#[test]
fn input_request_event_downcast() {
let evt = InputRequestEvent {
request_id: "req-99".to_string(),
prompt: "Confirm?".to_string(),
metadata: serde_json::json!(null),
};
let boxed: Box<dyn AnyEvent> = Box::new(evt.clone());
let downcasted = boxed.downcast_ref::<InputRequestEvent>().unwrap();
assert_eq!(downcasted.request_id, evt.request_id);
assert!(boxed.downcast_ref::<InputResponseEvent>().is_none());
}
#[test]
fn input_response_event_downcast() {
let evt = InputResponseEvent {
request_id: "req-99".to_string(),
response: serde_json::json!({"answer": true}),
};
let boxed: Box<dyn AnyEvent> = Box::new(evt.clone());
let downcasted = boxed.downcast_ref::<InputResponseEvent>().unwrap();
assert_eq!(downcasted.request_id, evt.request_id);
assert!(boxed.downcast_ref::<InputRequestEvent>().is_none());
}
}