use chrono::{DateTime, Utc};
use rskit_errors::{AppError, AppResult, ErrorCode};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub id: String,
#[serde(rename = "type")]
pub event_type: String,
pub source: String,
#[serde(default)]
pub subject: String,
#[serde(default = "default_content_type")]
pub content_type: String,
#[serde(default)]
pub version: String,
pub timestamp: DateTime<Utc>,
pub data: serde_json::Value,
}
fn default_content_type() -> String {
"application/json".to_string()
}
impl Event {
pub fn new(event_type: impl Into<String>, source: impl Into<String>) -> Self {
Self {
id: Uuid::new_v4().to_string(),
event_type: event_type.into(),
source: source.into(),
subject: String::new(),
content_type: "application/json".to_string(),
version: "1.0".to_string(),
timestamp: Utc::now(),
data: serde_json::Value::Null,
}
}
pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
self.subject = subject.into();
self
}
pub fn with_data(mut self, data: impl Serialize) -> AppResult<Self> {
self.data = serde_json::to_value(data).map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("Failed to serialize event data: {e}"),
)
})?;
Ok(self)
}
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = version.into();
self
}
pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = content_type.into();
self
}
pub fn to_json(&self) -> AppResult<Vec<u8>> {
serde_json::to_vec(self).map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("Failed to serialize event: {e}"),
)
})
}
pub fn from_json(bytes: &[u8]) -> AppResult<Self> {
serde_json::from_slice(bytes).map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("Failed to deserialize event: {e}"),
)
})
}
pub fn parse_data<T: serde::de::DeserializeOwned>(&self) -> AppResult<T> {
serde_json::from_value(self.data.clone()).map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("Failed to parse event data: {e}"),
)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_event_has_defaults() {
let event = Event::new("user.created", "auth-service");
assert_eq!(event.event_type, "user.created");
assert_eq!(event.source, "auth-service");
assert!(!event.id.is_empty());
assert_eq!(event.content_type, "application/json");
assert_eq!(event.version, "1.0");
assert_eq!(event.data, serde_json::Value::Null);
}
#[test]
fn builder_methods() {
let event = Event::new("order.placed", "shop")
.with_subject("order-123")
.with_version("2.0")
.with_content_type("text/plain");
assert_eq!(event.subject, "order-123");
assert_eq!(event.version, "2.0");
assert_eq!(event.content_type, "text/plain");
}
#[test]
fn with_data_serializes_payload() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Payload {
name: String,
count: u32,
}
let payload = Payload {
name: "test".to_string(),
count: 42,
};
let event = Event::new("test.event", "test")
.with_data(&payload)
.unwrap();
let parsed: Payload = event.parse_data().unwrap();
assert_eq!(parsed, payload);
}
#[test]
fn json_round_trip() {
let event = Event::new("round.trip", "test-source")
.with_subject("sub")
.with_data(serde_json::json!({"key": "value"}))
.unwrap();
let bytes = event.to_json().unwrap();
let restored = Event::from_json(&bytes).unwrap();
assert_eq!(restored.id, event.id);
assert_eq!(restored.event_type, "round.trip");
assert_eq!(restored.source, "test-source");
assert_eq!(restored.subject, "sub");
assert_eq!(restored.data, serde_json::json!({"key": "value"}));
}
#[test]
fn serde_renames_type_field() {
let event = Event::new("check.rename", "src");
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains(r#""type":"check.rename""#));
assert!(!json.contains("event_type"));
}
}