Skip to main content

driven/format/
versioning.rs

1//! Format versioning and migration
2
3use crate::{DrivenError, Result};
4
5/// Format version information
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7pub struct FormatVersion {
8    pub major: u8,
9    pub minor: u8,
10}
11
12impl FormatVersion {
13    /// Current version
14    pub const CURRENT: FormatVersion = FormatVersion { major: 1, minor: 0 };
15
16    /// Create a new version
17    pub const fn new(major: u8, minor: u8) -> Self {
18        Self { major, minor }
19    }
20
21    /// Convert to u16 for binary format
22    pub const fn to_u16(self) -> u16 {
23        (self.major as u16) << 8 | self.minor as u16
24    }
25
26    /// Create from u16
27    pub const fn from_u16(value: u16) -> Self {
28        Self {
29            major: (value >> 8) as u8,
30            minor: (value & 0xFF) as u8,
31        }
32    }
33
34    /// Check if this version is compatible with current
35    #[allow(clippy::absurd_extreme_comparisons)]
36    pub fn is_compatible(&self) -> bool {
37        self.major == Self::CURRENT.major && self.minor <= Self::CURRENT.minor
38    }
39}
40
41impl std::fmt::Display for FormatVersion {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        write!(f, "{}.{}", self.major, self.minor)
44    }
45}
46
47/// Version migration handler
48#[derive(Debug)]
49pub struct VersionMigrator;
50
51impl VersionMigrator {
52    /// Migrate data from an older version to current
53    pub fn migrate(data: &[u8], from: FormatVersion) -> Result<Vec<u8>> {
54        if from >= FormatVersion::CURRENT {
55            // No migration needed
56            return Ok(data.to_vec());
57        }
58
59        if from.major != FormatVersion::CURRENT.major {
60            return Err(DrivenError::InvalidBinary(format!(
61                "Cannot migrate from version {} to {} (major version mismatch)",
62                from,
63                FormatVersion::CURRENT
64            )));
65        }
66
67        // Minor version migrations
68        let migrated = data.to_vec();
69
70        // Add migration logic here as versions evolve
71        // Example:
72        // if from.minor < 1 {
73        //     migrated = migrate_0_to_1(&migrated)?;
74        // }
75
76        Ok(migrated)
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_version_roundtrip() {
86        let version = FormatVersion::new(1, 5);
87        let as_u16 = version.to_u16();
88        let back = FormatVersion::from_u16(as_u16);
89
90        assert_eq!(version, back);
91    }
92
93    #[test]
94    fn test_version_ordering() {
95        let v1_0 = FormatVersion::new(1, 0);
96        let v1_1 = FormatVersion::new(1, 1);
97        let v2_0 = FormatVersion::new(2, 0);
98
99        assert!(v1_0 < v1_1);
100        assert!(v1_1 < v2_0);
101    }
102
103    #[test]
104    fn test_compatibility() {
105        let current = FormatVersion::CURRENT;
106        assert!(current.is_compatible());
107
108        let older = FormatVersion::new(current.major, 0);
109        assert!(older.is_compatible());
110
111        let newer_minor = FormatVersion::new(current.major, current.minor + 1);
112        assert!(!newer_minor.is_compatible());
113
114        let newer_major = FormatVersion::new(current.major + 1, 0);
115        assert!(!newer_major.is_compatible());
116    }
117
118    #[test]
119    fn test_display() {
120        let version = FormatVersion::new(1, 5);
121        assert_eq!(format!("{}", version), "1.5");
122    }
123}