Skip to main content

allsource_core/domain/entities/
event.rs

1use crate::{
2    domain::value_objects::{EntityId, EventType, TenantId},
3    error::Result,
4};
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9/// Domain Entity: Event
10///
11/// Core event structure representing a domain event in the event store.
12/// This is an immutable, timestamped record of something that happened.
13///
14/// Domain Rules:
15/// - Events are immutable once created
16/// - Event type must follow naming convention (enforced by EventType value object)
17/// - Entity ID cannot be empty (enforced by EntityId value object)
18/// - Tenant ID cannot be empty (enforced by TenantId value object)
19/// - Timestamp must not be in the future
20/// - Version starts at 1
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22pub struct Event {
23    pub id: Uuid,
24    pub event_type: EventType,
25    pub entity_id: EntityId,
26    #[serde(default = "default_tenant_id")]
27    pub tenant_id: TenantId,
28    pub payload: serde_json::Value,
29    pub timestamp: DateTime<Utc>,
30    pub metadata: Option<serde_json::Value>,
31    pub version: i64,
32}
33
34fn default_tenant_id() -> TenantId {
35    TenantId::default_tenant()
36}
37
38impl Event {
39    /// Create a new Event with value objects (recommended)
40    pub fn new(
41        event_type: EventType,
42        entity_id: EntityId,
43        tenant_id: TenantId,
44        payload: serde_json::Value,
45    ) -> Self {
46        Self {
47            id: Uuid::new_v4(),
48            event_type,
49            entity_id,
50            tenant_id,
51            payload,
52            timestamp: Utc::now(),
53            metadata: None,
54            version: 1,
55        }
56    }
57
58    /// Create event with optional metadata
59    pub fn with_metadata(
60        event_type: EventType,
61        entity_id: EntityId,
62        tenant_id: TenantId,
63        payload: serde_json::Value,
64        metadata: serde_json::Value,
65    ) -> Self {
66        Self {
67            id: Uuid::new_v4(),
68            event_type,
69            entity_id,
70            tenant_id,
71            payload,
72            timestamp: Utc::now(),
73            metadata: Some(metadata),
74            version: 1,
75        }
76    }
77
78    /// Create event with default tenant (for single-tenant use)
79    pub fn with_default_tenant(
80        event_type: EventType,
81        entity_id: EntityId,
82        payload: serde_json::Value,
83    ) -> Self {
84        Self::new(event_type, entity_id, TenantId::default_tenant(), payload)
85    }
86
87    /// Create event from strings (for backward compatibility)
88    ///
89    /// This validates the strings and creates value objects.
90    /// Use the value object constructor for new code.
91    pub fn from_strings(
92        event_type: String,
93        entity_id: String,
94        tenant_id: String,
95        payload: serde_json::Value,
96        metadata: Option<serde_json::Value>,
97    ) -> Result<Self> {
98        let event_type = EventType::new(event_type)?;
99        let entity_id = EntityId::new(entity_id)?;
100        let tenant_id = TenantId::new(tenant_id)?;
101
102        Ok(Self {
103            id: Uuid::new_v4(),
104            event_type,
105            entity_id,
106            tenant_id,
107            payload,
108            timestamp: Utc::now(),
109            metadata,
110            version: 1,
111        })
112    }
113
114    /// Reconstruct an Event from storage (bypasses validation for stored events)
115    pub fn reconstruct(
116        id: Uuid,
117        event_type: EventType,
118        entity_id: EntityId,
119        tenant_id: TenantId,
120        payload: serde_json::Value,
121        timestamp: DateTime<Utc>,
122        metadata: Option<serde_json::Value>,
123        version: i64,
124    ) -> Self {
125        Self {
126            id,
127            event_type,
128            entity_id,
129            tenant_id,
130            payload,
131            timestamp,
132            metadata,
133            version,
134        }
135    }
136
137    /// Reconstruct from raw strings (for loading from old storage)
138    pub fn reconstruct_from_strings(
139        id: Uuid,
140        event_type: String,
141        entity_id: String,
142        tenant_id: String,
143        payload: serde_json::Value,
144        timestamp: DateTime<Utc>,
145        metadata: Option<serde_json::Value>,
146        version: i64,
147    ) -> Self {
148        Self {
149            id,
150            event_type: EventType::new_unchecked(event_type),
151            entity_id: EntityId::new_unchecked(entity_id),
152            tenant_id: TenantId::new_unchecked(tenant_id),
153            payload,
154            timestamp,
155            metadata,
156            version,
157        }
158    }
159
160    // Getters (Events are immutable)
161
162    pub fn id(&self) -> Uuid {
163        self.id
164    }
165
166    pub fn event_type(&self) -> &EventType {
167        &self.event_type
168    }
169
170    pub fn event_type_str(&self) -> &str {
171        self.event_type.as_str()
172    }
173
174    pub fn entity_id(&self) -> &EntityId {
175        &self.entity_id
176    }
177
178    pub fn entity_id_str(&self) -> &str {
179        self.entity_id.as_str()
180    }
181
182    pub fn tenant_id(&self) -> &TenantId {
183        &self.tenant_id
184    }
185
186    pub fn tenant_id_str(&self) -> &str {
187        self.tenant_id.as_str()
188    }
189
190    pub fn payload(&self) -> &serde_json::Value {
191        &self.payload
192    }
193
194    pub fn timestamp(&self) -> DateTime<Utc> {
195        self.timestamp
196    }
197
198    pub fn metadata(&self) -> Option<&serde_json::Value> {
199        self.metadata.as_ref()
200    }
201
202    pub fn version(&self) -> i64 {
203        self.version
204    }
205
206    // Domain behavior methods
207
208    /// Check if this event belongs to a specific tenant
209    pub fn belongs_to_tenant(&self, tenant_id: &TenantId) -> bool {
210        &self.tenant_id == tenant_id
211    }
212
213    /// Check if this event belongs to a tenant (by string)
214    pub fn belongs_to_tenant_str(&self, tenant_id: &str) -> bool {
215        self.tenant_id.as_str() == tenant_id
216    }
217
218    /// Check if this event relates to a specific entity
219    pub fn relates_to_entity(&self, entity_id: &EntityId) -> bool {
220        &self.entity_id == entity_id
221    }
222
223    /// Check if this event relates to an entity (by string)
224    pub fn relates_to_entity_str(&self, entity_id: &str) -> bool {
225        self.entity_id.as_str() == entity_id
226    }
227
228    /// Check if this event is of a specific type
229    pub fn is_type(&self, event_type: &EventType) -> bool {
230        &self.event_type == event_type
231    }
232
233    /// Check if this event is of a type (by string)
234    pub fn is_type_str(&self, event_type: &str) -> bool {
235        self.event_type.as_str() == event_type
236    }
237
238    /// Check if this event is in a specific namespace
239    pub fn is_in_namespace(&self, namespace: &str) -> bool {
240        self.event_type.is_in_namespace(namespace)
241    }
242
243    /// Check if this event occurred within a time range
244    pub fn occurred_between(&self, start: DateTime<Utc>, end: DateTime<Utc>) -> bool {
245        self.timestamp >= start && self.timestamp <= end
246    }
247
248    /// Check if event occurred before a specific time
249    pub fn occurred_before(&self, time: DateTime<Utc>) -> bool {
250        self.timestamp < time
251    }
252
253    /// Check if event occurred after a specific time
254    pub fn occurred_after(&self, time: DateTime<Utc>) -> bool {
255        self.timestamp > time
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use serde_json::json;
263
264    fn test_event_type() -> EventType {
265        EventType::new("user.created".to_string()).unwrap()
266    }
267
268    fn test_entity_id() -> EntityId {
269        EntityId::new("user-123".to_string()).unwrap()
270    }
271
272    fn test_tenant_id() -> TenantId {
273        TenantId::new("tenant-1".to_string()).unwrap()
274    }
275
276    #[test]
277    fn test_event_creation_with_value_objects() {
278        let event = Event::new(
279            test_event_type(),
280            test_entity_id(),
281            test_tenant_id(),
282            json!({"name": "Alice"}),
283        );
284
285        assert_eq!(event.event_type_str(), "user.created");
286        assert_eq!(event.entity_id_str(), "user-123");
287        assert_eq!(event.tenant_id_str(), "tenant-1");
288        assert_eq!(event.version(), 1);
289    }
290
291    #[test]
292    fn test_event_creation_from_strings() {
293        let event = Event::from_strings(
294            "user.created".to_string(),
295            "user-123".to_string(),
296            "tenant-1".to_string(),
297            json!({"name": "Alice"}),
298            None,
299        );
300
301        assert!(event.is_ok());
302        let event = event.unwrap();
303        assert_eq!(event.event_type_str(), "user.created");
304        assert_eq!(event.entity_id_str(), "user-123");
305        assert_eq!(event.tenant_id_str(), "tenant-1");
306    }
307
308    #[test]
309    fn test_event_with_metadata() {
310        let event = Event::with_metadata(
311            test_event_type(),
312            test_entity_id(),
313            test_tenant_id(),
314            json!({"name": "Bob"}),
315            json!({"source": "api"}),
316        );
317
318        assert!(event.metadata().is_some());
319        assert_eq!(event.metadata().unwrap(), &json!({"source": "api"}));
320    }
321
322    #[test]
323    fn test_event_with_default_tenant() {
324        let event = Event::with_default_tenant(test_event_type(), test_entity_id(), json!({}));
325
326        assert_eq!(event.tenant_id_str(), "default");
327    }
328
329    #[test]
330    fn test_from_strings_validates_event_type() {
331        // Invalid: uppercase
332        let result = Event::from_strings(
333            "User.Created".to_string(),
334            "e1".to_string(),
335            "t1".to_string(),
336            json!({}),
337            None,
338        );
339        assert!(result.is_err());
340
341        // Invalid: empty
342        let result = Event::from_strings(
343            String::new(),
344            "e1".to_string(),
345            "t1".to_string(),
346            json!({}),
347            None,
348        );
349        assert!(result.is_err());
350    }
351
352    #[test]
353    fn test_from_strings_validates_entity_id() {
354        // Invalid: empty entity_id
355        let result = Event::from_strings(
356            "user.created".to_string(),
357            String::new(),
358            "t1".to_string(),
359            json!({}),
360            None,
361        );
362        assert!(result.is_err());
363    }
364
365    #[test]
366    fn test_from_strings_validates_tenant_id() {
367        // Invalid: empty tenant_id
368        let result = Event::from_strings(
369            "user.created".to_string(),
370            "e1".to_string(),
371            String::new(),
372            json!({}),
373            None,
374        );
375        assert!(result.is_err());
376    }
377
378    #[test]
379    fn test_belongs_to_tenant() {
380        let tenant1 = TenantId::new("tenant-1".to_string()).unwrap();
381        let tenant2 = TenantId::new("tenant-2".to_string()).unwrap();
382
383        let event = Event::new(
384            test_event_type(),
385            test_entity_id(),
386            tenant1.clone(),
387            json!({}),
388        );
389
390        assert!(event.belongs_to_tenant(&tenant1));
391        assert!(!event.belongs_to_tenant(&tenant2));
392    }
393
394    #[test]
395    fn test_belongs_to_tenant_str() {
396        let event = Event::new(
397            test_event_type(),
398            test_entity_id(),
399            test_tenant_id(),
400            json!({}),
401        );
402
403        assert!(event.belongs_to_tenant_str("tenant-1"));
404        assert!(!event.belongs_to_tenant_str("tenant-2"));
405    }
406
407    #[test]
408    fn test_relates_to_entity() {
409        let entity1 = EntityId::new("order-456".to_string()).unwrap();
410        let entity2 = EntityId::new("order-789".to_string()).unwrap();
411
412        let event = Event::new(
413            EventType::new("order.placed".to_string()).unwrap(),
414            entity1.clone(),
415            test_tenant_id(),
416            json!({}),
417        );
418
419        assert!(event.relates_to_entity(&entity1));
420        assert!(!event.relates_to_entity(&entity2));
421    }
422
423    #[test]
424    fn test_relates_to_entity_str() {
425        let event = Event::new(
426            EventType::new("order.placed".to_string()).unwrap(),
427            EntityId::new("order-456".to_string()).unwrap(),
428            test_tenant_id(),
429            json!({}),
430        );
431
432        assert!(event.relates_to_entity_str("order-456"));
433        assert!(!event.relates_to_entity_str("order-789"));
434    }
435
436    #[test]
437    fn test_is_type() {
438        let type1 = EventType::new("order.placed".to_string()).unwrap();
439        let type2 = EventType::new("order.cancelled".to_string()).unwrap();
440
441        let event = Event::new(type1.clone(), test_entity_id(), test_tenant_id(), json!({}));
442
443        assert!(event.is_type(&type1));
444        assert!(!event.is_type(&type2));
445    }
446
447    #[test]
448    fn test_is_type_str() {
449        let event = Event::new(
450            EventType::new("order.placed".to_string()).unwrap(),
451            test_entity_id(),
452            test_tenant_id(),
453            json!({}),
454        );
455
456        assert!(event.is_type_str("order.placed"));
457        assert!(!event.is_type_str("order.cancelled"));
458    }
459
460    #[test]
461    fn test_is_in_namespace() {
462        let event = Event::new(
463            EventType::new("order.placed".to_string()).unwrap(),
464            test_entity_id(),
465            test_tenant_id(),
466            json!({}),
467        );
468
469        assert!(event.is_in_namespace("order"));
470        assert!(!event.is_in_namespace("user"));
471    }
472
473    #[test]
474    fn test_time_range_queries() {
475        let event = Event::new(
476            test_event_type(),
477            test_entity_id(),
478            test_tenant_id(),
479            json!({}),
480        );
481
482        let past = Utc::now() - chrono::Duration::hours(1);
483        let future = Utc::now() + chrono::Duration::hours(1);
484
485        assert!(event.occurred_after(past));
486        assert!(event.occurred_before(future));
487        assert!(event.occurred_between(past, future));
488    }
489
490    #[test]
491    fn test_serde_serialization() {
492        let event = Event::new(
493            test_event_type(),
494            test_entity_id(),
495            test_tenant_id(),
496            json!({"test": "data"}),
497        );
498
499        // Should be able to serialize
500        let json = serde_json::to_string(&event);
501        assert!(json.is_ok());
502
503        // Should be able to deserialize
504        let deserialized = serde_json::from_str::<Event>(&json.unwrap());
505        assert!(deserialized.is_ok());
506
507        let deserialized = deserialized.unwrap();
508        assert_eq!(deserialized.event_type_str(), "user.created");
509        assert_eq!(deserialized.entity_id_str(), "user-123");
510    }
511
512    #[test]
513    fn test_reconstruct_from_strings() {
514        let id = Uuid::new_v4();
515        let timestamp = Utc::now();
516
517        let event = Event::reconstruct_from_strings(
518            id,
519            "order.placed".to_string(),
520            "order-123".to_string(),
521            "tenant-1".to_string(),
522            json!({"amount": 100}),
523            timestamp,
524            Some(json!({"source": "api"})),
525            1,
526        );
527
528        assert_eq!(event.id(), id);
529        assert_eq!(event.event_type_str(), "order.placed");
530        assert_eq!(event.entity_id_str(), "order-123");
531        assert_eq!(event.tenant_id_str(), "tenant-1");
532        assert_eq!(event.version(), 1);
533    }
534}