Skip to main content

allsource_core/domain/value_objects/
version.rs

1use crate::error::Result;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5/// Value Object: Version
6///
7/// Represents a version number for entities like events, schemas, or projections.
8///
9/// Domain Rules:
10/// - Must be a positive integer (>= 1)
11/// - Immutable once created
12/// - Sequential within a stream
13///
14/// This is a Value Object:
15/// - Defined by its value, not identity
16/// - Immutable
17/// - Self-validating
18/// - Compared by value equality
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
20pub struct Version(u64);
21
22impl Version {
23    /// The first version number (always 1)
24    pub const FIRST: Version = Version(1);
25
26    /// Create a new Version with validation
27    ///
28    /// # Errors
29    /// Returns error if version is 0
30    ///
31    /// # Examples
32    /// ```
33    /// use allsource_core::domain::value_objects::Version;
34    ///
35    /// let version = Version::new(1).unwrap();
36    /// assert_eq!(version.as_u64(), 1);
37    /// ```
38    pub fn new(value: u64) -> Result<Self> {
39        Self::validate(value)?;
40        Ok(Self(value))
41    }
42
43    /// Create Version without validation (for internal use)
44    ///
45    /// # Safety
46    /// This bypasses validation. Only use when loading from trusted sources.
47    pub(crate) fn new_unchecked(value: u64) -> Self {
48        Self(value)
49    }
50
51    /// Create the first version
52    ///
53    /// # Examples
54    /// ```
55    /// use allsource_core::domain::value_objects::Version;
56    ///
57    /// let version = Version::first();
58    /// assert_eq!(version.as_u64(), 1);
59    /// assert!(version.is_first());
60    /// ```
61    pub fn first() -> Self {
62        Self::FIRST
63    }
64
65    /// Get the version value as u64
66    pub fn as_u64(&self) -> u64 {
67        self.0
68    }
69
70    /// Get the version value as i64 (for compatibility)
71    pub fn as_i64(&self) -> i64 {
72        self.0 as i64
73    }
74
75    /// Get the version value as u32 (for compatibility)
76    pub fn as_u32(&self) -> u32 {
77        self.0 as u32
78    }
79
80    /// Check if this is the first version
81    ///
82    /// # Examples
83    /// ```
84    /// use allsource_core::domain::value_objects::Version;
85    ///
86    /// let v1 = Version::first();
87    /// let v2 = Version::new(2).unwrap();
88    ///
89    /// assert!(v1.is_first());
90    /// assert!(!v2.is_first());
91    /// ```
92    pub fn is_first(&self) -> bool {
93        self.0 == 1
94    }
95
96    /// Get the next version
97    ///
98    /// # Examples
99    /// ```
100    /// use allsource_core::domain::value_objects::Version;
101    ///
102    /// let v1 = Version::first();
103    /// let v2 = v1.next();
104    ///
105    /// assert_eq!(v2.as_u64(), 2);
106    /// ```
107    pub fn next(&self) -> Self {
108        Self(self.0.saturating_add(1))
109    }
110
111    /// Get the previous version, if possible
112    ///
113    /// Returns None if this is the first version.
114    ///
115    /// # Examples
116    /// ```
117    /// use allsource_core::domain::value_objects::Version;
118    ///
119    /// let v2 = Version::new(2).unwrap();
120    /// let v1 = v2.previous();
121    ///
122    /// assert_eq!(v1, Some(Version::first()));
123    ///
124    /// let first = Version::first();
125    /// assert_eq!(first.previous(), None);
126    /// ```
127    pub fn previous(&self) -> Option<Self> {
128        if self.0 > 1 {
129            Some(Self(self.0 - 1))
130        } else {
131            None
132        }
133    }
134
135    /// Check if this version is immediately after another
136    ///
137    /// # Examples
138    /// ```
139    /// use allsource_core::domain::value_objects::Version;
140    ///
141    /// let v1 = Version::first();
142    /// let v2 = Version::new(2).unwrap();
143    /// let v3 = Version::new(3).unwrap();
144    ///
145    /// assert!(v2.is_immediately_after(&v1));
146    /// assert!(!v3.is_immediately_after(&v1));
147    /// ```
148    pub fn is_immediately_after(&self, other: &Version) -> bool {
149        self.0 == other.0 + 1
150    }
151
152    /// Validate a version number
153    fn validate(value: u64) -> Result<()> {
154        if value == 0 {
155            return Err(crate::error::AllSourceError::InvalidInput(
156                "Version must be >= 1".to_string(),
157            ));
158        }
159        Ok(())
160    }
161}
162
163impl fmt::Display for Version {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        write!(f, "{}", self.0)
166    }
167}
168
169impl TryFrom<u64> for Version {
170    type Error = crate::error::AllSourceError;
171
172    fn try_from(value: u64) -> Result<Self> {
173        Version::new(value)
174    }
175}
176
177impl TryFrom<i64> for Version {
178    type Error = crate::error::AllSourceError;
179
180    fn try_from(value: i64) -> Result<Self> {
181        if value < 1 {
182            return Err(crate::error::AllSourceError::InvalidInput(
183                "Version must be >= 1".to_string(),
184            ));
185        }
186        Version::new(value as u64)
187    }
188}
189
190impl TryFrom<u32> for Version {
191    type Error = crate::error::AllSourceError;
192
193    fn try_from(value: u32) -> Result<Self> {
194        Version::new(value as u64)
195    }
196}
197
198impl From<Version> for u64 {
199    fn from(version: Version) -> Self {
200        version.0
201    }
202}
203
204impl From<Version> for i64 {
205    fn from(version: Version) -> Self {
206        version.0 as i64
207    }
208}
209
210impl From<Version> for u32 {
211    fn from(version: Version) -> Self {
212        version.0 as u32
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn test_create_valid_version() {
222        let version = Version::new(1);
223        assert!(version.is_ok());
224        assert_eq!(version.unwrap().as_u64(), 1);
225
226        let version = Version::new(100);
227        assert!(version.is_ok());
228        assert_eq!(version.unwrap().as_u64(), 100);
229    }
230
231    #[test]
232    fn test_reject_zero_version() {
233        let result = Version::new(0);
234        assert!(result.is_err());
235
236        if let Err(e) = result {
237            assert!(e.to_string().contains("must be >= 1"));
238        }
239    }
240
241    #[test]
242    fn test_first_version() {
243        let version = Version::first();
244        assert_eq!(version.as_u64(), 1);
245        assert!(version.is_first());
246    }
247
248    #[test]
249    fn test_is_first() {
250        let v1 = Version::first();
251        let v2 = Version::new(2).unwrap();
252
253        assert!(v1.is_first());
254        assert!(!v2.is_first());
255    }
256
257    #[test]
258    fn test_next_version() {
259        let v1 = Version::first();
260        let v2 = v1.next();
261        let v3 = v2.next();
262
263        assert_eq!(v2.as_u64(), 2);
264        assert_eq!(v3.as_u64(), 3);
265    }
266
267    #[test]
268    fn test_previous_version() {
269        let v1 = Version::first();
270        let v2 = Version::new(2).unwrap();
271        let v3 = Version::new(3).unwrap();
272
273        assert_eq!(v1.previous(), None);
274        assert_eq!(v2.previous(), Some(Version::first()));
275        assert_eq!(v3.previous(), Some(Version::new(2).unwrap()));
276    }
277
278    #[test]
279    fn test_is_immediately_after() {
280        let v1 = Version::first();
281        let v2 = Version::new(2).unwrap();
282        let v3 = Version::new(3).unwrap();
283
284        assert!(v2.is_immediately_after(&v1));
285        assert!(v3.is_immediately_after(&v2));
286        assert!(!v3.is_immediately_after(&v1));
287    }
288
289    #[test]
290    fn test_version_ordering() {
291        let v1 = Version::first();
292        let v2 = Version::new(2).unwrap();
293        let v3 = Version::new(3).unwrap();
294
295        assert!(v1 < v2);
296        assert!(v2 < v3);
297        assert!(v1 < v3);
298        assert!(v3 > v1);
299    }
300
301    #[test]
302    fn test_as_conversions() {
303        let version = Version::new(42).unwrap();
304
305        assert_eq!(version.as_u64(), 42);
306        assert_eq!(version.as_i64(), 42);
307        assert_eq!(version.as_u32(), 42);
308    }
309
310    #[test]
311    fn test_display_trait() {
312        let version = Version::new(42).unwrap();
313        assert_eq!(format!("{}", version), "42");
314    }
315
316    #[test]
317    fn test_try_from_u64() {
318        let version: Result<Version> = 5u64.try_into();
319        assert!(version.is_ok());
320        assert_eq!(version.unwrap().as_u64(), 5);
321
322        let invalid: Result<Version> = 0u64.try_into();
323        assert!(invalid.is_err());
324    }
325
326    #[test]
327    fn test_try_from_i64() {
328        let version: Result<Version> = 5i64.try_into();
329        assert!(version.is_ok());
330        assert_eq!(version.unwrap().as_u64(), 5);
331
332        let invalid: Result<Version> = 0i64.try_into();
333        assert!(invalid.is_err());
334
335        let negative: Result<Version> = (-1i64).try_into();
336        assert!(negative.is_err());
337    }
338
339    #[test]
340    fn test_try_from_u32() {
341        let version: Result<Version> = 5u32.try_into();
342        assert!(version.is_ok());
343        assert_eq!(version.unwrap().as_u64(), 5);
344
345        let invalid: Result<Version> = 0u32.try_into();
346        assert!(invalid.is_err());
347    }
348
349    #[test]
350    fn test_into_conversions() {
351        let version = Version::new(42).unwrap();
352
353        let u64_val: u64 = version.into();
354        assert_eq!(u64_val, 42);
355
356        let version = Version::new(42).unwrap();
357        let i64_val: i64 = version.into();
358        assert_eq!(i64_val, 42);
359
360        let version = Version::new(42).unwrap();
361        let u32_val: u32 = version.into();
362        assert_eq!(u32_val, 42);
363    }
364
365    #[test]
366    fn test_equality() {
367        let v1 = Version::new(1).unwrap();
368        let v2 = Version::new(1).unwrap();
369        let v3 = Version::new(2).unwrap();
370
371        assert_eq!(v1, v2);
372        assert_ne!(v1, v3);
373    }
374
375    #[test]
376    fn test_cloning() {
377        let v1 = Version::new(5).unwrap();
378        let v2 = v1; // Copy
379        assert_eq!(v1, v2);
380    }
381
382    #[test]
383    fn test_hash_consistency() {
384        use std::collections::HashSet;
385
386        let v1 = Version::new(5).unwrap();
387        let v2 = Version::new(5).unwrap();
388
389        let mut set = HashSet::new();
390        set.insert(v1);
391
392        assert!(set.contains(&v2));
393    }
394
395    #[test]
396    fn test_serde_serialization() {
397        let version = Version::new(42).unwrap();
398
399        // Serialize
400        let json = serde_json::to_string(&version).unwrap();
401        assert_eq!(json, "42");
402
403        // Deserialize
404        let deserialized: Version = serde_json::from_str(&json).unwrap();
405        assert_eq!(deserialized, version);
406    }
407
408    #[test]
409    fn test_const_first() {
410        assert_eq!(Version::FIRST.as_u64(), 1);
411        assert_eq!(Version::FIRST, Version::first());
412    }
413
414    #[test]
415    fn test_next_saturation() {
416        let max_version = Version::new_unchecked(u64::MAX);
417        let next = max_version.next();
418        assert_eq!(next.as_u64(), u64::MAX); // Saturates, doesn't overflow
419    }
420
421    #[test]
422    fn test_new_unchecked() {
423        // Should create without validation (for internal use)
424        let version = Version::new_unchecked(0);
425        assert_eq!(version.as_u64(), 0);
426    }
427}