use chrono::{DateTime, Utc};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EventMetadata {
pub event_id: Uuid,
pub timestamp: DateTime<Utc>,
pub correlation_id: Option<Uuid>,
pub causation_id: Option<Uuid>,
pub source: Option<String>,
pub user_id: Option<String>,
pub session_id: Option<String>,
pub custom: HashMap<String, String>,
}
impl EventMetadata {
pub fn new() -> Self {
Self {
event_id: Uuid::max(),
timestamp: Utc::now(),
correlation_id: None,
causation_id: None,
source: None,
user_id: None,
session_id: None,
custom: HashMap::new(),
}
}
pub fn with_correlation(correlation_id: Uuid) -> Self {
let mut metadata = Self::new();
metadata.correlation_id = Some(correlation_id);
metadata
}
pub fn set_correlation_id(mut self, id: Uuid) -> Self {
self.correlation_id = Some(id);
self
}
pub fn set_causation_id(mut self, id: Uuid) -> Self {
self.causation_id = Some(id);
self
}
pub fn set_source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
pub fn set_user_id(mut self, user_id: impl Into<String>) -> Self {
self.user_id = Some(user_id.into());
self
}
pub fn set_session_id(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
pub fn add_custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.custom.insert(key.into(), value.into());
self
}
pub fn get_custom(&self, key: &str) -> Option<&String> {
self.custom.get(key)
}
pub fn chain_from(&mut self, parent: &EventMetadata) {
self.causation_id = Some(parent.event_id);
self.correlation_id = parent.correlation_id.or(Some(parent.event_id));
if self.user_id.is_none() {
self.user_id = parent.user_id.clone();
}
if self.session_id.is_none() {
self.session_id = parent.session_id.clone();
}
}
}
impl Default for EventMetadata {
fn default() -> Self {
Self::new()
}
}
#[allow(missing_debug_implementations)]
pub struct MetadataBuilder {
metadata: EventMetadata,
}
impl Default for MetadataBuilder {
fn default() -> Self {
Self::new()
}
}
impl MetadataBuilder {
pub fn new() -> Self {
Self {
metadata: EventMetadata::new(),
}
}
pub fn correlation_id(mut self, id: Uuid) -> Self {
self.metadata.correlation_id = Some(id);
self
}
pub fn causation_id(mut self, id: Uuid) -> Self {
self.metadata.causation_id = Some(id);
self
}
pub fn source(mut self, source: impl Into<String>) -> Self {
self.metadata.source = Some(source.into());
self
}
pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
self.metadata.user_id = Some(user_id.into());
self
}
pub fn session_id(mut self, session_id: impl Into<String>) -> Self {
self.metadata.session_id = Some(session_id.into());
self
}
pub fn custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.custom.insert(key.into(), value.into());
self
}
pub fn build(self) -> EventMetadata {
self.metadata
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metadata_creation() {
let metadata = EventMetadata::new();
assert_ne!(metadata.event_id, Uuid::nil());
assert!(metadata.correlation_id.is_none());
assert!(metadata.custom.is_empty());
}
#[test]
fn test_metadata_builder() {
let correlation_id = Uuid::max();
let metadata = MetadataBuilder::new()
.correlation_id(correlation_id)
.source("test-service")
.user_id("user123")
.custom("environment", "test")
.build();
assert_eq!(metadata.correlation_id, Some(correlation_id));
assert_eq!(metadata.source, Some("test-service".to_string()));
assert_eq!(metadata.user_id, Some("user123".to_string()));
assert_eq!(
metadata.get_custom("environment"),
Some(&"test".to_string())
);
}
#[test]
fn test_metadata_chaining() {
let parent = EventMetadata::new()
.set_user_id("user123")
.set_session_id("session456");
let mut child = EventMetadata::new();
child.chain_from(&parent);
assert_eq!(child.causation_id, Some(parent.event_id));
assert_eq!(child.correlation_id, Some(parent.event_id));
assert_eq!(child.user_id, Some("user123".to_string()));
assert_eq!(child.session_id, Some("session456".to_string()));
}
}