1use serde::de::{self, Visitor};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::cmp::Ordering;
4use std::fmt::{Display, Formatter};
5use std::str::FromStr;
6
7#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
9#[error("{message}")]
10pub struct ParseVersionError {
11 message: String,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct Version {
17 pub major: u32,
19
20 pub minor: u32,
22
23 pub patch: u32,
25
26 pub build: u32,
28}
29
30impl Display for Version {
31 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32 f.write_fmt(format_args!(
33 "{}.{}.{}.{}",
34 self.major, self.minor, self.patch, self.build
35 ))
36 }
37}
38
39impl Ord for Version {
40 fn cmp(&self, other: &Self) -> Ordering {
41 self.major
42 .cmp(&other.major)
43 .then(self.minor.cmp(&other.minor))
44 .then(self.patch.cmp(&other.patch))
45 .then(self.build.cmp(&other.build))
46 }
47}
48
49impl PartialOrd for Version {
50 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
51 Some(self.cmp(other))
52 }
53}
54
55fn parse_version(value: &str) -> Result<Version, String> {
56 fn parse_part<'i>(
57 parts: &mut impl Iterator<Item = &'i str>,
58 named: &'static str,
59 ) -> Result<u32, String> {
60 parts
61 .next()
62 .ok_or_else(|| format!("Did not find part '{named}'."))?
63 .parse::<u32>()
64 .map_err(|err| format!("Failed to parse '{named}' part as an u32: {err}"))
65 }
66
67 let mut parts = value.split('.');
68 let major = parse_part(&mut parts, "major")?;
69 let minor = parse_part(&mut parts, "minor")?;
70 let patch = parse_part(&mut parts, "patch")?;
71 let build = parse_part(&mut parts, "build")?;
72
73 if let Some(next) = parts.next() {
74 return Err(format!(
75 "Invalid version string format. Did not expect any additional parts. Got at least the additional part: {next}"
76 ));
77 }
78
79 Ok(Version {
80 major,
81 minor,
82 patch,
83 build,
84 })
85}
86
87impl FromStr for Version {
88 type Err = ParseVersionError;
89
90 fn from_str(s: &str) -> Result<Self, Self::Err> {
91 parse_version(s).map_err(|message| ParseVersionError { message })
92 }
93}
94
95impl Serialize for Version {
96 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97 where
98 S: Serializer,
99 {
100 serializer.serialize_str(&self.to_string())
101 }
102}
103
104impl<'de> Deserialize<'de> for Version {
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: Deserializer<'de>,
108 {
109 struct VersionVisitor;
110
111 impl Visitor<'_> for VersionVisitor {
112 type Value = Version;
113
114 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
115 formatter.write_str("a version string in dot format \"{major}.{minor}.{patch}.{build}\", like `1.0.0.0`, with each part being a `u32`, and all parts being required")
116 }
117
118 fn visit_str<E>(self, value: &str) -> Result<Version, E>
119 where
120 E: de::Error,
121 {
122 parse_version(value).map_err(de::Error::custom)
123 }
124 }
125
126 deserializer.deserialize_str(VersionVisitor)
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use assertr::prelude::*;
134 use std::collections::HashSet;
135
136 #[test]
137 fn deserialize_valid_version() {
138 let test_cases = vec![
139 (
140 "0.0.0.0",
141 Version {
142 major: 0,
143 minor: 0,
144 patch: 0,
145 build: 0,
146 },
147 ),
148 (
149 "1.2.3.4",
150 Version {
151 major: 1,
152 minor: 2,
153 patch: 3,
154 build: 4,
155 },
156 ),
157 (
158 "115.785.5763.42",
159 Version {
160 major: 115,
161 minor: 785,
162 patch: 5763,
163 build: 42,
164 },
165 ),
166 (
167 "4294967295.4294967295.4294967295.4294967295",
168 Version {
169 major: u32::MAX,
170 minor: u32::MAX,
171 patch: u32::MAX,
172 build: u32::MAX,
173 },
174 ),
175 ];
176
177 for (input, expected) in test_cases {
178 let json = format!(
179 r#"
180 "{input}"
181 "#
182 );
183 let result = serde_json::from_str::<Version>(&json);
184 assert_that!(result).is_ok().is_equal_to(expected);
185 }
186 }
187
188 #[test]
189 fn deserialize_invalid_version() {
190 let invalid_cases = vec![
191 ("", "Failed to parse 'major' part as an u32"),
192 ("1", "Did not find part 'minor'"),
193 ("1.2", "Did not find part 'patch'"),
194 ("1.2.3", "Did not find part 'build'"),
195 ("1.2.3.4.5", "Did not expect any additional parts"),
196 ("a.b.c.d", "Failed to parse 'major' part as an u32"),
197 ("1.b.c.d", "Failed to parse 'minor' part as an u32"),
198 ("1.2.c.d", "Failed to parse 'patch' part as an u32"),
199 ("1.2.3.d", "Failed to parse 'build' part as an u32"),
200 ("-1.2.3.4", "Failed to parse 'major' part as an u32"),
201 ("1.-2.3.4", "Failed to parse 'minor' part as an u32"),
202 ("1.2.-3.4", "Failed to parse 'patch' part as an u32"),
203 ("1.2.3.-4", "Failed to parse 'build' part as an u32"),
204 ("4294967296.0.0.0", "Failed to parse 'major' part as an u32"),
205 ("0.4294967296.0.0", "Failed to parse 'minor' part as an u32"),
206 ("0.0.4294967296.0", "Failed to parse 'patch' part as an u32"),
207 ("0.0.0.4294967296", "Failed to parse 'build' part as an u32"),
208 (".2.3.4", "Failed to parse 'major' part as an u32"),
209 ("1..3.4", "Failed to parse 'minor' part as an u32"),
210 ("1.2..4", "Failed to parse 'patch' part as an u32"),
211 ("1.2.3.", "Failed to parse 'build' part as an u32"),
212 ];
213
214 for (input, expected_error_substring) in invalid_cases {
215 let json = format!(
216 r#"
217 "{input}"
218 "#
219 );
220 let result = serde_json::from_str::<Version>(&json);
221 assert_that!(result)
222 .is_err()
223 .derive(|it| it.to_string())
224 .contains(expected_error_substring);
225 }
226 }
227
228 #[test]
229 fn display_value() {
230 let version = Version {
231 major: 115,
232 minor: 785,
233 patch: 5763,
234 build: 42,
235 };
236 assert_that!(version).has_display_value("115.785.5763.42");
237 }
238
239 #[test]
240 fn debug_value() {
241 let version = Version {
242 major: 115,
243 minor: 785,
244 patch: 5763,
245 build: 42,
246 };
247 assert_that!(version)
248 .has_debug_value("Version { major: 115, minor: 785, patch: 5763, build: 42 }");
249 }
250
251 #[test]
252 fn comparisons() {
253 let v1 = Version {
254 major: 1,
255 minor: 0,
256 patch: 0,
257 build: 0,
258 };
259 let v2 = Version {
260 major: 1,
261 minor: 0,
262 patch: 0,
263 build: 1,
264 };
265 let v3 = Version {
266 major: 1,
267 minor: 0,
268 patch: 1,
269 build: 0,
270 };
271 let v4 = Version {
272 major: 1,
273 minor: 1,
274 patch: 0,
275 build: 0,
276 };
277 let v5 = Version {
278 major: 2,
279 minor: 0,
280 patch: 0,
281 build: 0,
282 };
283
284 assert_that!(v1).is_less_than(v2);
285 assert_that!(v1).is_less_than(v3);
286 assert_that!(v1).is_less_than(v4);
287 assert_that!(v1).is_less_than(v5);
288 assert_that!(v2).is_less_than(v3);
289 assert_that!(v2).is_less_than(v4);
290 assert_that!(v2).is_less_than(v5);
291 assert_that!(v3).is_less_than(v4);
292 assert_that!(v3).is_less_than(v5);
293 assert_that!(v4).is_less_than(v5);
294
295 assert_that!(v2).is_greater_than(v1);
296 assert_that!(v3).is_greater_than(v1);
297 assert_that!(v4).is_greater_than(v1);
298 assert_that!(v5).is_greater_than(v1);
299 assert_that!(v3).is_greater_than(v2);
300 assert_that!(v4).is_greater_than(v2);
301 assert_that!(v5).is_greater_than(v2);
302 assert_that!(v4).is_greater_than(v3);
303 assert_that!(v5).is_greater_than(v3);
304 assert_that!(v5).is_greater_than(v4);
305
306 assert_that!(v1).is_equal_to(v1);
307 assert_that!(v1).is_less_or_equal_to(v1);
308 assert_that!(v1).is_greater_or_equal_to(v1);
309
310 assert_that!(v2).is_equal_to(v2);
311 assert_that!(v2).is_less_or_equal_to(v2);
312 assert_that!(v2).is_greater_or_equal_to(v2);
313
314 assert_that!(v3).is_equal_to(v3);
315 assert_that!(v3).is_less_or_equal_to(v3);
316 assert_that!(v3).is_greater_or_equal_to(v3);
317
318 assert_that!(v4).is_equal_to(v4);
319 assert_that!(v4).is_less_or_equal_to(v4);
320 assert_that!(v4).is_greater_or_equal_to(v4);
321
322 assert_that!(v5).is_equal_to(v5);
323 assert_that!(v5).is_less_or_equal_to(v5);
324 assert_that!(v5).is_greater_or_equal_to(v5);
325 }
326
327 #[test]
328 fn parse_valid() {
329 assert_that!("1.2.3.4".parse::<Version>())
330 .is_ok()
331 .is_equal_to(Version {
332 major: 1,
333 minor: 2,
334 patch: 3,
335 build: 4,
336 });
337 assert_that!("0.0.0.0".parse::<Version>())
338 .is_ok()
339 .is_equal_to(Version {
340 major: 0,
341 minor: 0,
342 patch: 0,
343 build: 0,
344 });
345 }
346
347 #[test]
348 fn parse_invalid_fails() {
349 assert_that!("".parse::<Version>()).is_err();
350 assert_that!("1".parse::<Version>()).is_err();
351 assert_that!("1.2".parse::<Version>()).is_err();
352 assert_that!("1.2.3".parse::<Version>()).is_err();
353 assert_that!("1.2.3.4.5".parse::<Version>()).is_err();
354 assert_that!("a.b.c.d".parse::<Version>()).is_err();
355 }
356
357 #[test]
358 fn serialize_round_trip() {
359 let version = Version {
360 major: 132,
361 minor: 0,
362 patch: 6834,
363 build: 83,
364 };
365 let json = serde_json::to_string(&version).unwrap();
366 assert_that!(json.clone()).is_equal_to(String::from("\"132.0.6834.83\""));
367 let deserialized: Version = serde_json::from_str(&json).unwrap();
368 assert_that!(deserialized).is_equal_to(version);
369 }
370
371 #[test]
372 fn ord_sorting() {
373 let v3 = Version {
374 major: 2,
375 minor: 0,
376 patch: 0,
377 build: 0,
378 };
379 let v1 = Version {
380 major: 1,
381 minor: 0,
382 patch: 0,
383 build: 0,
384 };
385 let v2 = Version {
386 major: 1,
387 minor: 1,
388 patch: 0,
389 build: 0,
390 };
391 let mut versions = vec![v3, v1, v2];
392 versions.sort();
393 assert_that!(versions).is_equal_to(vec![v1, v2, v3]);
394 }
395
396 #[test]
397 fn hash_in_set() {
398 let v1 = Version {
399 major: 1,
400 minor: 0,
401 patch: 0,
402 build: 0,
403 };
404 let v2 = Version {
405 major: 1,
406 minor: 0,
407 patch: 0,
408 build: 0,
409 };
410 let mut set = HashSet::new();
411 set.insert(v1);
412 set.insert(v2);
413 assert_that!(set.len()).is_equal_to(1);
414 }
415}