Skip to main content

canlink_hal/
version.rs

1//! Version management and compatibility checking.
2//!
3//! This module provides types for managing backend versions and checking compatibility.
4
5use semver::{Version, VersionReq};
6use std::fmt;
7
8/// Backend version information.
9///
10/// Uses semantic versioning (`SemVer`) for version management. Backends with the
11/// same major version are considered compatible.
12///
13/// # Examples
14///
15/// ```
16/// use canlink_hal::BackendVersion;
17///
18/// let version = BackendVersion::new(1, 2, 3);
19/// assert_eq!(version.major(), 1);
20/// assert_eq!(version.minor(), 2);
21/// assert_eq!(version.patch(), 3);
22/// ```
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct BackendVersion {
25    version: Version,
26}
27
28impl BackendVersion {
29    /// Create a new backend version.
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// use canlink_hal::BackendVersion;
35    ///
36    /// let version = BackendVersion::new(1, 2, 3);
37    /// assert_eq!(version.to_string(), "1.2.3");
38    /// ```
39    #[must_use]
40    pub fn new(major: u64, minor: u64, patch: u64) -> Self {
41        Self {
42            version: Version::new(major, minor, patch),
43        }
44    }
45
46    /// Parse a version string.
47    ///
48    /// # Errors
49    ///
50    /// Returns an error if the version string is invalid.
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// use canlink_hal::BackendVersion;
56    ///
57    /// let version = BackendVersion::parse("1.2.3").unwrap();
58    /// assert_eq!(version.major(), 1);
59    /// ```
60    pub fn parse(s: &str) -> Result<Self, semver::Error> {
61        Ok(Self {
62            version: Version::parse(s)?,
63        })
64    }
65
66    /// Get the major version number.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use canlink_hal::BackendVersion;
72    ///
73    /// let version = BackendVersion::new(1, 2, 3);
74    /// assert_eq!(version.major(), 1);
75    /// ```
76    #[must_use]
77    pub fn major(&self) -> u64 {
78        self.version.major
79    }
80
81    /// Get the minor version number.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// use canlink_hal::BackendVersion;
87    ///
88    /// let version = BackendVersion::new(1, 2, 3);
89    /// assert_eq!(version.minor(), 2);
90    /// ```
91    #[must_use]
92    pub fn minor(&self) -> u64 {
93        self.version.minor
94    }
95
96    /// Get the patch version number.
97    ///
98    /// # Examples
99    ///
100    /// ```
101    /// use canlink_hal::BackendVersion;
102    ///
103    /// let version = BackendVersion::new(1, 2, 3);
104    /// assert_eq!(version.patch(), 3);
105    /// ```
106    #[must_use]
107    pub fn patch(&self) -> u64 {
108        self.version.patch
109    }
110
111    /// Check if this version is compatible with another version.
112    ///
113    /// Two versions are compatible if they have the same major version number.
114    /// This follows semantic versioning rules where major version changes
115    /// indicate breaking changes.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use canlink_hal::BackendVersion;
121    ///
122    /// let v1 = BackendVersion::new(1, 2, 3);
123    /// let v2 = BackendVersion::new(1, 3, 0);
124    /// let v3 = BackendVersion::new(2, 0, 0);
125    ///
126    /// assert!(v1.is_compatible_with(&v2));
127    /// assert!(!v1.is_compatible_with(&v3));
128    /// ```
129    #[must_use]
130    pub fn is_compatible_with(&self, other: &Self) -> bool {
131        self.version.major == other.version.major
132    }
133
134    /// Check if this version satisfies a version requirement.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use canlink_hal::BackendVersion;
140    ///
141    /// let version = BackendVersion::new(1, 2, 3);
142    /// assert!(version.satisfies("^1.0.0").unwrap());
143    /// assert!(!version.satisfies("^2.0.0").unwrap());
144    /// ```
145    ///
146    /// # Errors
147    ///
148    /// Returns an error if the requirement string cannot be parsed.
149    pub fn satisfies(&self, req: &str) -> Result<bool, semver::Error> {
150        let req = VersionReq::parse(req)?;
151        Ok(req.matches(&self.version))
152    }
153
154    /// Get the underlying semver Version.
155    #[must_use]
156    pub const fn as_semver(&self) -> &Version {
157        &self.version
158    }
159}
160
161impl fmt::Display for BackendVersion {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        write!(f, "{}", self.version)
164    }
165}
166
167impl From<Version> for BackendVersion {
168    fn from(version: Version) -> Self {
169        Self { version }
170    }
171}
172
173impl From<BackendVersion> for Version {
174    fn from(backend_version: BackendVersion) -> Self {
175        backend_version.version
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn test_version_creation() {
185        let version = BackendVersion::new(1, 2, 3);
186        assert_eq!(version.major(), 1);
187        assert_eq!(version.minor(), 2);
188        assert_eq!(version.patch(), 3);
189    }
190
191    #[test]
192    fn test_version_parse() {
193        let version = BackendVersion::parse("1.2.3").unwrap();
194        assert_eq!(version.major(), 1);
195        assert_eq!(version.minor(), 2);
196        assert_eq!(version.patch(), 3);
197    }
198
199    #[test]
200    fn test_version_display() {
201        let version = BackendVersion::new(1, 2, 3);
202        assert_eq!(version.to_string(), "1.2.3");
203    }
204
205    #[test]
206    fn test_version_compatibility() {
207        let v1 = BackendVersion::new(1, 2, 3);
208        let v2 = BackendVersion::new(1, 3, 0);
209        let v3 = BackendVersion::new(2, 0, 0);
210
211        assert!(v1.is_compatible_with(&v2));
212        assert!(v2.is_compatible_with(&v1));
213        assert!(!v1.is_compatible_with(&v3));
214        assert!(!v3.is_compatible_with(&v1));
215    }
216
217    #[test]
218    fn test_version_satisfies() {
219        let version = BackendVersion::new(1, 2, 3);
220
221        assert!(version.satisfies("^1.0.0").unwrap());
222        assert!(version.satisfies("^1.2.0").unwrap());
223        assert!(version.satisfies(">=1.0.0").unwrap());
224        assert!(!version.satisfies("^2.0.0").unwrap());
225        assert!(!version.satisfies("<1.0.0").unwrap());
226    }
227
228    #[test]
229    fn test_invalid_version_parse() {
230        assert!(BackendVersion::parse("invalid").is_err());
231        assert!(BackendVersion::parse("1.2").is_err());
232    }
233}