1use std::fmt;
30use thiserror::Error;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct PackedVersion(u32);
37
38#[derive(Debug, Error, PartialEq)]
40pub enum VersionError {
41 #[error("Major version {0} exceeds maximum of 4294")]
42 MajorTooLarge(u32),
43
44 #[error("Minor version {0} exceeds maximum of 999")]
45 MinorTooLarge(u32),
46
47 #[error("Patch version {0} exceeds maximum of 999")]
48 PatchTooLarge(u32),
49
50 #[error("Invalid version string format: {0}")]
51 InvalidFormat(String),
52
53 #[error("Failed to parse version component: {0}")]
54 ParseError(String),
55}
56
57impl PackedVersion {
58 pub const MAX_MAJOR: u32 = 4293;
62
63 pub const MAX_MINOR: u32 = 999;
65
66 pub const MAX_PATCH: u32 = 999;
68
69 pub fn new(major: u32, minor: u32, patch: u32) -> Result<Self, VersionError> {
73 if major > Self::MAX_MAJOR {
74 return Err(VersionError::MajorTooLarge(major));
75 }
76 if minor > Self::MAX_MINOR {
77 return Err(VersionError::MinorTooLarge(minor));
78 }
79 if patch > Self::MAX_PATCH {
80 return Err(VersionError::PatchTooLarge(patch));
81 }
82
83 let major_part = major
85 .checked_mul(1_000_000)
86 .ok_or(VersionError::MajorTooLarge(major))?;
87 let minor_part = minor
88 .checked_mul(1_000)
89 .ok_or(VersionError::MinorTooLarge(minor))?;
90 let value = major_part
91 .checked_add(minor_part)
92 .and_then(|v| v.checked_add(patch))
93 .ok_or(VersionError::MajorTooLarge(major))?;
94
95 Ok(Self(value))
96 }
97
98 pub const fn from_raw(value: u32) -> Self {
103 Self(value)
104 }
105
106 pub fn parse(s: &str) -> Result<Self, VersionError> {
117 let parts: Vec<&str> = s.split('.').collect();
118
119 if parts.len() != 3 {
120 return Err(VersionError::InvalidFormat(format!(
121 "Expected format 'major.minor.patch', got '{}'",
122 s
123 )));
124 }
125
126 let major = parts[0]
127 .parse::<u32>()
128 .map_err(|e| VersionError::ParseError(format!("major: {}", e)))?;
129
130 let minor = parts[1]
131 .parse::<u32>()
132 .map_err(|e| VersionError::ParseError(format!("minor: {}", e)))?;
133
134 let patch = parts[2]
135 .parse::<u32>()
136 .map_err(|e| VersionError::ParseError(format!("patch: {}", e)))?;
137
138 Self::new(major, minor, patch)
139 }
140
141 pub const fn as_u32(&self) -> u32 {
143 self.0
144 }
145
146 pub const fn major(&self) -> u32 {
148 self.0 / 1_000_000
149 }
150
151 pub const fn minor(&self) -> u32 {
153 (self.0 / 1_000) % 1_000
154 }
155
156 pub const fn patch(&self) -> u32 {
158 self.0 % 1_000
159 }
160
161 pub fn is_compatible_with(&self, other: &Self) -> bool {
163 self.major() == other.major()
164 }
165
166 pub fn is_breaking_change_from(&self, other: &Self) -> bool {
168 self.major() > other.major()
169 }
170}
171
172pub fn pack_version(major: u32, minor: u32, patch: u32) -> Result<PackedVersion, VersionError> {
177 PackedVersion::new(major, minor, patch)
178}
179
180pub const fn pack_version_unchecked(major: u32, minor: u32, patch: u32) -> PackedVersion {
189 PackedVersion(major * 1_000_000 + minor * 1_000 + patch)
190}
191
192impl fmt::Display for PackedVersion {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 write!(f, "{}.{}.{}", self.major(), self.minor(), self.patch())
195 }
196}
197
198impl From<PackedVersion> for u32 {
199 fn from(v: PackedVersion) -> u32 {
200 v.0
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_pack_version() {
210 let v = pack_version(2, 3, 1).unwrap();
211 assert_eq!(v.as_u32(), 2_003_001);
212 assert_eq!(v.major(), 2);
213 assert_eq!(v.minor(), 3);
214 assert_eq!(v.patch(), 1);
215 }
216
217 #[test]
218 fn test_version_comparison() {
219 assert!(pack_version(2, 3, 1).unwrap() < pack_version(2, 4, 0).unwrap());
220 assert!(pack_version(2, 3, 99).unwrap() < pack_version(2, 4, 0).unwrap());
221 assert!(pack_version(2, 10, 0).unwrap() > pack_version(2, 9, 999).unwrap());
222 assert!(pack_version(3, 0, 0).unwrap() > pack_version(2, 999, 999).unwrap());
223 }
224
225 #[test]
226 fn test_parse_version() {
227 let v = PackedVersion::parse("2.3.1").unwrap();
228 assert_eq!(v.as_u32(), 2_003_001);
229
230 let v = PackedVersion::parse("10.999.999").unwrap();
231 assert_eq!(v.major(), 10);
232 assert_eq!(v.minor(), 999);
233 assert_eq!(v.patch(), 999);
234 }
235
236 #[test]
237 fn test_parse_errors() {
238 assert!(PackedVersion::parse("1.2").is_err());
239 assert!(PackedVersion::parse("1.2.3.4").is_err());
240 assert!(PackedVersion::parse("a.b.c").is_err());
241 assert!(PackedVersion::parse("1.1000.1").is_err()); }
243
244 #[test]
245 fn test_limits() {
246 assert!(pack_version(4293, 999, 999).is_ok());
248 assert!(pack_version(0, 0, 0).is_ok());
249
250 assert_eq!(
252 pack_version(4294, 0, 0),
253 Err(VersionError::MajorTooLarge(4294))
254 );
255 assert_eq!(
256 pack_version(0, 1000, 0),
257 Err(VersionError::MinorTooLarge(1000))
258 );
259 assert_eq!(
260 pack_version(0, 0, 1000),
261 Err(VersionError::PatchTooLarge(1000))
262 );
263 }
264
265 #[test]
266 fn test_display() {
267 let v = pack_version(2, 3, 1).unwrap();
268 assert_eq!(v.to_string(), "2.3.1");
269 }
270
271 #[test]
272 fn test_compatibility() {
273 let v1 = pack_version(2, 3, 1).unwrap();
274 let v2 = pack_version(2, 5, 0).unwrap();
275 let v3 = pack_version(3, 0, 0).unwrap();
276
277 assert!(v1.is_compatible_with(&v2));
278 assert!(!v1.is_compatible_with(&v3));
279
280 assert!(!v1.is_breaking_change_from(&v2));
281 assert!(v3.is_breaking_change_from(&v1));
282 }
283
284 #[test]
285 fn test_const_packing() {
286 const VERSION: PackedVersion = pack_version_unchecked(1, 0, 0);
287 assert_eq!(VERSION.as_u32(), 1_000_000);
288 }
289}