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}