1use serde::de::{self, Visitor};
2use serde::{Deserialize, Deserializer};
3use std::cmp::PartialOrd;
4use std::fmt::{Display, Formatter};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct Version {
9 pub major: u32,
11
12 pub minor: u32,
14
15 pub patch: u32,
17
18 pub build: u32,
20}
21
22impl Display for Version {
23 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24 f.write_fmt(format_args!(
25 "{}.{}.{}.{}",
26 self.major, self.minor, self.patch, self.build
27 ))
28 }
29}
30
31impl PartialOrd for Version {
32 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
33 if self.major != other.major {
34 return self.major.partial_cmp(&other.major);
35 }
36 if self.minor != other.minor {
37 return self.minor.partial_cmp(&other.minor);
38 }
39 if self.patch != other.patch {
40 return self.patch.partial_cmp(&other.patch);
41 }
42 if self.build != other.build {
43 return self.build.partial_cmp(&other.build);
44 }
45 Some(std::cmp::Ordering::Equal)
46 }
47
48 fn lt(&self, other: &Self) -> bool {
49 self.partial_cmp(other) == Some(std::cmp::Ordering::Less)
50 }
51
52 fn le(&self, other: &Self) -> bool {
53 self.partial_cmp(other) != Some(std::cmp::Ordering::Greater)
54 }
55
56 fn gt(&self, other: &Self) -> bool {
57 self.partial_cmp(other) == Some(std::cmp::Ordering::Greater)
58 }
59
60 fn ge(&self, other: &Self) -> bool {
61 self.partial_cmp(other) != Some(std::cmp::Ordering::Less)
62 }
63}
64
65impl<'de> Deserialize<'de> for Version {
66 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67 where
68 D: Deserializer<'de>,
69 {
70 struct VersionVisitor;
71
72 impl Visitor<'_> for VersionVisitor {
73 type Value = Version;
74
75 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
76 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")
77 }
78
79 fn visit_str<E>(self, value: &str) -> Result<Version, E>
80 where
81 E: de::Error,
82 {
83 fn parse_part<'i, E: de::Error>(
84 parts: &mut impl Iterator<Item = &'i str>,
85 named: &'static str,
86 ) -> Result<u32, E> {
87 parts
88 .next()
89 .ok_or_else(|| de::Error::custom(format!("Did not find part '{named}'.")))?
90 .parse::<u32>()
91 .map_err(|err| {
92 de::Error::custom(format!(
93 "Failed to parse '{named}' part as an u32: {err}"
94 ))
95 })
96 }
97
98 let mut parts = value.split('.');
99 let major = parse_part(&mut parts, "major")?;
100 let minor = parse_part(&mut parts, "minor")?;
101 let patch = parse_part(&mut parts, "patch")?;
102 let build = parse_part(&mut parts, "build")?;
103
104 if let Some(next) = parts.next() {
105 return Err(de::Error::custom(format!(
106 "Invalid version string format. Did not expect any additional parts. Got at least the additional part: {next}"
107 )));
108 }
109
110 Ok(Version {
111 major,
112 minor,
113 patch,
114 build,
115 })
116 }
117 }
118
119 deserializer.deserialize_str(VersionVisitor)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use assertr::prelude::*;
127
128 #[test]
129 fn deserialize_valid_version() {
130 let test_cases = vec![
131 (
132 "0.0.0.0",
133 Version {
134 major: 0,
135 minor: 0,
136 patch: 0,
137 build: 0,
138 },
139 ),
140 (
141 "1.2.3.4",
142 Version {
143 major: 1,
144 minor: 2,
145 patch: 3,
146 build: 4,
147 },
148 ),
149 (
150 "115.785.5763.42",
151 Version {
152 major: 115,
153 minor: 785,
154 patch: 5763,
155 build: 42,
156 },
157 ),
158 (
159 "4294967295.4294967295.4294967295.4294967295",
160 Version {
161 major: u32::MAX,
162 minor: u32::MAX,
163 patch: u32::MAX,
164 build: u32::MAX,
165 },
166 ),
167 ];
168
169 for (input, expected) in test_cases {
170 let json = format!(
171 r#"
172 "{input}"
173 "#
174 );
175 let result = serde_json::from_str::<Version>(&json);
176 assert_that!(result).is_ok().is_equal_to(expected);
177 }
178 }
179
180 #[test]
181 fn deserialize_invalid_version() {
182 let invalid_cases = vec![
183 ("", "Failed to parse 'major' part as an u32"),
184 ("1", "Did not find part 'minor'"),
185 ("1.2", "Did not find part 'patch'"),
186 ("1.2.3", "Did not find part 'build'"),
187 ("1.2.3.4.5", "Did not expect any additional parts"),
188 ("a.b.c.d", "Failed to parse 'major' part as an u32"),
189 ("1.b.c.d", "Failed to parse 'minor' part as an u32"),
190 ("1.2.c.d", "Failed to parse 'patch' part as an u32"),
191 ("1.2.3.d", "Failed to parse 'build' part as an u32"),
192 ("-1.2.3.4", "Failed to parse 'major' part as an u32"),
193 ("1.-2.3.4", "Failed to parse 'minor' part as an u32"),
194 ("1.2.-3.4", "Failed to parse 'patch' part as an u32"),
195 ("1.2.3.-4", "Failed to parse 'build' part as an u32"),
196 ("4294967296.0.0.0", "Failed to parse 'major' part as an u32"),
197 ("0.4294967296.0.0", "Failed to parse 'minor' part as an u32"),
198 ("0.0.4294967296.0", "Failed to parse 'patch' part as an u32"),
199 ("0.0.0.4294967296", "Failed to parse 'build' part as an u32"),
200 (".2.3.4", "Failed to parse 'major' part as an u32"),
201 ("1..3.4", "Failed to parse 'minor' part as an u32"),
202 ("1.2..4", "Failed to parse 'patch' part as an u32"),
203 ("1.2.3.", "Failed to parse 'build' part as an u32"),
204 ];
205
206 for (input, expected_error_substring) in invalid_cases {
207 let json = format!(
208 r#"
209 "{input}"
210 "#
211 );
212 let result = serde_json::from_str::<Version>(&json);
213 assert_that!(result)
214 .is_err()
215 .derive(|it| it.to_string())
216 .contains(expected_error_substring);
217 }
218 }
219
220 #[test]
221 fn display_value() {
222 let version = Version {
223 major: 115,
224 minor: 785,
225 patch: 5763,
226 build: 42,
227 };
228 assert_that!(version).has_display_value("115.785.5763.42");
229 }
230
231 #[test]
232 fn debug_value() {
233 let version = Version {
234 major: 115,
235 minor: 785,
236 patch: 5763,
237 build: 42,
238 };
239 assert_that!(version)
240 .has_debug_value("Version { major: 115, minor: 785, patch: 5763, build: 42 }");
241 }
242
243 #[test]
244 fn comparisons() {
245 let v1 = Version {
246 major: 1,
247 minor: 0,
248 patch: 0,
249 build: 0,
250 };
251 let v2 = Version {
252 major: 1,
253 minor: 0,
254 patch: 0,
255 build: 1,
256 };
257 let v3 = Version {
258 major: 1,
259 minor: 0,
260 patch: 1,
261 build: 0,
262 };
263 let v4 = Version {
264 major: 1,
265 minor: 1,
266 patch: 0,
267 build: 0,
268 };
269 let v5 = Version {
270 major: 2,
271 minor: 0,
272 patch: 0,
273 build: 0,
274 };
275
276 assert_that!(v1).is_less_than(v2);
277 assert_that!(v1).is_less_than(v3);
278 assert_that!(v1).is_less_than(v4);
279 assert_that!(v1).is_less_than(v5);
280 assert_that!(v2).is_less_than(v3);
281 assert_that!(v2).is_less_than(v4);
282 assert_that!(v2).is_less_than(v5);
283 assert_that!(v3).is_less_than(v4);
284 assert_that!(v3).is_less_than(v5);
285 assert_that!(v4).is_less_than(v5);
286
287 assert_that!(v2).is_greater_than(v1);
288 assert_that!(v3).is_greater_than(v1);
289 assert_that!(v4).is_greater_than(v1);
290 assert_that!(v5).is_greater_than(v1);
291 assert_that!(v3).is_greater_than(v2);
292 assert_that!(v4).is_greater_than(v2);
293 assert_that!(v5).is_greater_than(v2);
294 assert_that!(v4).is_greater_than(v3);
295 assert_that!(v5).is_greater_than(v3);
296 assert_that!(v5).is_greater_than(v4);
297
298 assert_that!(v1).is_equal_to(v1);
299 assert_that!(v1).is_less_or_equal_to(v1);
300 assert_that!(v1).is_greater_or_equal_to(v1);
301
302 assert_that!(v2).is_equal_to(v2);
303 assert_that!(v2).is_less_or_equal_to(v2);
304 assert_that!(v2).is_greater_or_equal_to(v2);
305
306 assert_that!(v3).is_equal_to(v3);
307 assert_that!(v3).is_less_or_equal_to(v3);
308 assert_that!(v3).is_greater_or_equal_to(v3);
309
310 assert_that!(v4).is_equal_to(v4);
311 assert_that!(v4).is_less_or_equal_to(v4);
312 assert_that!(v4).is_greater_or_equal_to(v4);
313
314 assert_that!(v5).is_equal_to(v5);
315 assert_that!(v5).is_less_or_equal_to(v5);
316 assert_that!(v5).is_greater_or_equal_to(v5);
317 }
318}