Skip to main content

allsource_core/domain/value_objects/
event_id.rs

1use crate::error::Result;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use uuid::Uuid;
5
6/// Value Object: EventId
7///
8/// Represents a unique identifier for an event in the event store.
9///
10/// Domain Rules:
11/// - Must be a valid UUID
12/// - Immutable once created
13/// - Globally unique within the event store
14///
15/// This is a Value Object:
16/// - Defined by its value, not identity
17/// - Immutable
18/// - Self-validating
19/// - Compared by value equality
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub struct EventId(Uuid);
22
23impl EventId {
24    /// Create a new EventId with a random UUID
25    ///
26    /// # Examples
27    /// ```
28    /// use allsource_core::domain::value_objects::EventId;
29    ///
30    /// let event_id = EventId::new();
31    /// assert!(!event_id.is_nil());
32    /// ```
33    pub fn new() -> Self {
34        Self(Uuid::new_v4())
35    }
36
37    /// Create an EventId from an existing UUID
38    ///
39    /// # Examples
40    /// ```
41    /// use allsource_core::domain::value_objects::EventId;
42    /// use uuid::Uuid;
43    ///
44    /// let uuid = Uuid::new_v4();
45    /// let event_id = EventId::from_uuid(uuid);
46    /// assert_eq!(event_id.as_uuid(), uuid);
47    /// ```
48    pub fn from_uuid(uuid: Uuid) -> Self {
49        Self(uuid)
50    }
51
52    /// Parse an EventId from a string
53    ///
54    /// # Errors
55    /// Returns error if the string is not a valid UUID
56    ///
57    /// # Examples
58    /// ```
59    /// use allsource_core::domain::value_objects::EventId;
60    ///
61    /// let event_id = EventId::parse("550e8400-e29b-41d4-a716-446655440000").unwrap();
62    /// assert_eq!(event_id.to_string(), "550e8400-e29b-41d4-a716-446655440000");
63    /// ```
64    pub fn parse(value: &str) -> Result<Self> {
65        let uuid = Uuid::parse_str(value).map_err(|e| {
66            crate::error::AllSourceError::InvalidInput(format!("Invalid event ID '{value}': {e}"))
67        })?;
68        Ok(Self(uuid))
69    }
70
71    /// Get the UUID value
72    pub fn as_uuid(&self) -> Uuid {
73        self.0
74    }
75
76    /// Check if this is a nil (all zeros) UUID
77    pub fn is_nil(&self) -> bool {
78        self.0.is_nil()
79    }
80
81    /// Create a nil EventId (for testing or placeholders)
82    pub fn nil() -> Self {
83        Self(Uuid::nil())
84    }
85}
86
87impl Default for EventId {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93impl fmt::Display for EventId {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "{}", self.0)
96    }
97}
98
99impl From<Uuid> for EventId {
100    fn from(uuid: Uuid) -> Self {
101        Self(uuid)
102    }
103}
104
105impl From<EventId> for Uuid {
106    fn from(event_id: EventId) -> Self {
107        event_id.0
108    }
109}
110
111impl TryFrom<&str> for EventId {
112    type Error = crate::error::AllSourceError;
113
114    fn try_from(value: &str) -> Result<Self> {
115        EventId::parse(value)
116    }
117}
118
119impl TryFrom<String> for EventId {
120    type Error = crate::error::AllSourceError;
121
122    fn try_from(value: String) -> Result<Self> {
123        EventId::parse(&value)
124    }
125}
126
127impl AsRef<Uuid> for EventId {
128    fn as_ref(&self) -> &Uuid {
129        &self.0
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_create_event_id() {
139        let event_id = EventId::new();
140        assert!(!event_id.is_nil());
141    }
142
143    #[test]
144    fn test_from_uuid() {
145        let uuid = Uuid::new_v4();
146        let event_id = EventId::from_uuid(uuid);
147        assert_eq!(event_id.as_uuid(), uuid);
148    }
149
150    #[test]
151    fn test_parse_valid_uuid() {
152        let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
153        let event_id = EventId::parse(uuid_str);
154        assert!(event_id.is_ok());
155        assert_eq!(event_id.unwrap().to_string(), uuid_str);
156    }
157
158    #[test]
159    fn test_parse_invalid_uuid() {
160        let invalid = "not-a-uuid";
161        let result = EventId::parse(invalid);
162        assert!(result.is_err());
163    }
164
165    #[test]
166    fn test_nil_event_id() {
167        let nil_id = EventId::nil();
168        assert!(nil_id.is_nil());
169        assert_eq!(nil_id.to_string(), "00000000-0000-0000-0000-000000000000");
170    }
171
172    #[test]
173    fn test_default_creates_new_uuid() {
174        let id1 = EventId::default();
175        let id2 = EventId::default();
176        assert_ne!(id1, id2);
177        assert!(!id1.is_nil());
178    }
179
180    #[test]
181    fn test_display_trait() {
182        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
183        let event_id = EventId::from_uuid(uuid);
184        assert_eq!(
185            format!("{event_id}"),
186            "550e8400-e29b-41d4-a716-446655440000"
187        );
188    }
189
190    #[test]
191    fn test_from_uuid_trait() {
192        let uuid = Uuid::new_v4();
193        let event_id: EventId = uuid.into();
194        assert_eq!(event_id.as_uuid(), uuid);
195    }
196
197    #[test]
198    fn test_into_uuid_trait() {
199        let event_id = EventId::new();
200        let uuid: Uuid = event_id.into();
201        assert_eq!(uuid, event_id.as_uuid());
202    }
203
204    #[test]
205    fn test_try_from_str() {
206        let event_id: Result<EventId> = "550e8400-e29b-41d4-a716-446655440000".try_into();
207        assert!(event_id.is_ok());
208
209        let invalid: Result<EventId> = "invalid".try_into();
210        assert!(invalid.is_err());
211    }
212
213    #[test]
214    fn test_try_from_string() {
215        let event_id: Result<EventId> = "550e8400-e29b-41d4-a716-446655440000"
216            .to_string()
217            .try_into();
218        assert!(event_id.is_ok());
219
220        let invalid: Result<EventId> = String::new().try_into();
221        assert!(invalid.is_err());
222    }
223
224    #[test]
225    fn test_equality() {
226        let uuid = Uuid::new_v4();
227        let id1 = EventId::from_uuid(uuid);
228        let id2 = EventId::from_uuid(uuid);
229        let id3 = EventId::new();
230
231        assert_eq!(id1, id2);
232        assert_ne!(id1, id3);
233    }
234
235    #[test]
236    fn test_cloning() {
237        let id1 = EventId::new();
238        let id2 = id1; // Copy
239        assert_eq!(id1, id2);
240    }
241
242    #[test]
243    fn test_hash_consistency() {
244        use std::collections::HashSet;
245
246        let uuid = Uuid::new_v4();
247        let id1 = EventId::from_uuid(uuid);
248        let id2 = EventId::from_uuid(uuid);
249
250        let mut set = HashSet::new();
251        set.insert(id1);
252
253        assert!(set.contains(&id2));
254    }
255
256    #[test]
257    fn test_serde_serialization() {
258        let event_id = EventId::parse("550e8400-e29b-41d4-a716-446655440000").unwrap();
259
260        // Serialize
261        let json = serde_json::to_string(&event_id).unwrap();
262        assert_eq!(json, "\"550e8400-e29b-41d4-a716-446655440000\"");
263
264        // Deserialize
265        let deserialized: EventId = serde_json::from_str(&json).unwrap();
266        assert_eq!(deserialized, event_id);
267    }
268
269    #[test]
270    fn test_as_ref() {
271        let event_id = EventId::new();
272        let uuid_ref: &Uuid = event_id.as_ref();
273        assert_eq!(*uuid_ref, event_id.as_uuid());
274    }
275}