Skip to main content

atm_protocol/
version.rs

1//! Protocol versioning for safe upgrades.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use thiserror::Error;
6
7/// Protocol version for client-daemon communication.
8///
9/// Uses semantic versioning: major.minor
10/// - Major version bump: breaking changes, incompatible
11/// - Minor version bump: additive changes, backward compatible
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct ProtocolVersion {
14    pub major: u16,
15    pub minor: u16,
16}
17
18impl ProtocolVersion {
19    /// Current protocol version.
20    pub const CURRENT: ProtocolVersion = ProtocolVersion { major: 1, minor: 0 };
21
22    /// Creates a new ProtocolVersion.
23    pub const fn new(major: u16, minor: u16) -> Self {
24        Self { major, minor }
25    }
26
27    /// Parses a version string like "1.0".
28    pub fn parse(s: &str) -> Result<Self, VersionError> {
29        let parts: Vec<&str> = s.split('.').collect();
30        if parts.len() != 2 {
31            return Err(VersionError::InvalidFormat(s.to_string()));
32        }
33
34        let major = parts
35            .first()
36            .ok_or_else(|| VersionError::InvalidFormat(s.to_string()))?
37            .parse::<u16>()
38            .map_err(|_| VersionError::InvalidFormat(s.to_string()))?;
39
40        let minor = parts
41            .get(1)
42            .ok_or_else(|| VersionError::InvalidFormat(s.to_string()))?
43            .parse::<u16>()
44            .map_err(|_| VersionError::InvalidFormat(s.to_string()))?;
45
46        Ok(Self { major, minor })
47    }
48
49    /// Returns true if this version is compatible with another.
50    ///
51    /// Compatibility rules:
52    /// - Major versions must match
53    /// - Any minor version is compatible within the same major version
54    pub fn is_compatible_with(&self, other: &ProtocolVersion) -> bool {
55        self.major == other.major
56    }
57
58    /// Returns true if this version is newer than another.
59    pub fn is_newer_than(&self, other: &ProtocolVersion) -> bool {
60        (self.major, self.minor) > (other.major, other.minor)
61    }
62
63    /// Returns true if this version is the current version.
64    pub fn is_current(&self) -> bool {
65        *self == Self::CURRENT
66    }
67}
68
69impl Default for ProtocolVersion {
70    fn default() -> Self {
71        Self::CURRENT
72    }
73}
74
75impl fmt::Display for ProtocolVersion {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(f, "{}.{}", self.major, self.minor)
78    }
79}
80
81/// Errors that can occur with version handling.
82#[derive(Error, Debug, Clone)]
83pub enum VersionError {
84    #[error("Invalid version format: {0}")]
85    InvalidFormat(String),
86
87    #[error("Incompatible version: got {got}, expected {expected}")]
88    Incompatible { got: String, expected: String },
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_version_parse() {
97        let v = ProtocolVersion::parse("1.0").unwrap();
98        assert_eq!(v.major, 1);
99        assert_eq!(v.minor, 0);
100    }
101
102    #[test]
103    fn test_version_parse_invalid() {
104        assert!(ProtocolVersion::parse("1").is_err());
105        assert!(ProtocolVersion::parse("1.0.0").is_err());
106        assert!(ProtocolVersion::parse("abc").is_err());
107    }
108
109    #[test]
110    fn test_version_compatibility() {
111        let v1_0 = ProtocolVersion::new(1, 0);
112        let v1_1 = ProtocolVersion::new(1, 1);
113        let v2_0 = ProtocolVersion::new(2, 0);
114
115        assert!(v1_0.is_compatible_with(&v1_1));
116        assert!(v1_1.is_compatible_with(&v1_0));
117        assert!(!v1_0.is_compatible_with(&v2_0));
118    }
119
120    #[test]
121    fn test_version_display() {
122        let v = ProtocolVersion::new(1, 2);
123        assert_eq!(format!("{v}"), "1.2");
124    }
125}