Skip to main content

allsource_core/domain/entities/
event.rs

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