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 let mut parts = value.split('.');
84
85 fn parse_part<'i, E: de::Error>(
86 parts: &mut impl Iterator<Item = &'i str>,
87 named: &'static str,
88 ) -> Result<u32, E> {
89 parts
90 .next()
91 .ok_or_else(|| de::Error::custom(format!("Did not find part '{named}'.")))?
92 .parse::<u32>()
93 .map_err(|err| {
94 de::Error::custom(format!(
95 "Failed to parse '{named}' part as an u32: {err}"
96 ))
97 })
98 }
99
100 let major = parse_part(&mut parts, "major")?;
101 let minor = parse_part(&mut parts, "minor")?;
102 let patch = parse_part(&mut parts, "patch")?;
103 let build = parse_part(&mut parts, "build")?;
104
105 if let Some(next) = parts.next() {
106 return Err(de::Error::custom(format!("Invalid version string format. Did not expect any additional parts. Got at least the additional part: {next}")));
107 }
108
109 Ok(Version {
110 major,
111 minor,
112 patch,
113 build,
114 })
115 }
116 }
117
118 deserializer.deserialize_str(VersionVisitor)
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use assertr::prelude::*;
126
127 #[test]
128 fn deserialize_valid_version() {
129 let test_cases = vec![
130 (
131 "0.0.0.0",
132 Version {
133 major: 0,
134 minor: 0,
135 patch: 0,
136 build: 0,
137 },
138 ),
139 (
140 "1.2.3.4",
141 Version {
142 major: 1,
143 minor: 2,
144 patch: 3,
145 build: 4,
146 },
147 ),
148 (
149 "115.785.5763.42",
150 Version {
151 major: 115,
152 minor: 785,
153 patch: 5763,
154 build: 42,
155 },
156 ),
157 (
158 "4294967295.4294967295.4294967295.4294967295",
159 Version {
160 major: u32::MAX,
161 minor: u32::MAX,
162 patch: u32::MAX,
163 build: u32::MAX,
164 },
165 ),
166 ];
167
168 for (input, expected) in test_cases {
169 let json = format!(
170 r#"
171 "{input}"
172 "#
173 );
174 let result = serde_json::from_str::<Version>(&json);
175 assert_that(result).is_ok().is_equal_to(expected);
176 }
177 }
178
179 #[test]
180 fn deserialize_invalid_version() {
181 let invalid_cases = vec![
182 ("", "Failed to parse 'major' part as an u32"),
183 ("1", "Did not find part 'minor'"),
184 ("1.2", "Did not find part 'patch'"),
185 ("1.2.3", "Did not find part 'build'"),
186 ("1.2.3.4.5", "Did not expect any additional parts"),
187 ("a.b.c.d", "Failed to parse 'major' part as an u32"),
188 ("1.b.c.d", "Failed to parse 'minor' part as an u32"),
189 ("1.2.c.d", "Failed to parse 'patch' part as an u32"),
190 ("1.2.3.d", "Failed to parse 'build' part as an u32"),
191 ("-1.2.3.4", "Failed to parse 'major' part as an u32"),
192 ("1.-2.3.4", "Failed to parse 'minor' part as an u32"),
193 ("1.2.-3.4", "Failed to parse 'patch' part as an u32"),
194 ("1.2.3.-4", "Failed to parse 'build' part as an u32"),
195 ("4294967296.0.0.0", "Failed to parse 'major' part as an u32"),
196 ("0.4294967296.0.0", "Failed to parse 'minor' part as an u32"),
197 ("0.0.4294967296.0", "Failed to parse 'patch' part as an u32"),
198 ("0.0.0.4294967296", "Failed to parse 'build' part as an u32"),
199 (".2.3.4", "Failed to parse 'major' part as an u32"),
200 ("1..3.4", "Failed to parse 'minor' part as an u32"),
201 ("1.2..4", "Failed to parse 'patch' part as an u32"),
202 ("1.2.3.", "Failed to parse 'build' part as an u32"),
203 ];
204
205 for (input, expected_error_substring) in invalid_cases {
206 let json = format!(
207 r#"
208 "{input}"
209 "#
210 );
211 let result = serde_json::from_str::<Version>(&json);
212 assert_that(result)
213 .is_err()
214 .derive(|it| it.to_string())
215 .contains(expected_error_substring);
216 }
217 }
218
219 #[test]
220 fn display_value() {
221 let version = Version {
222 major: 115,
223 minor: 785,
224 patch: 5763,
225 build: 42,
226 };
227 assert_that(version).has_display_value("115.785.5763.42");
228 }
229
230 #[test]
231 fn debug_value() {
232 let version = Version {
233 major: 115,
234 minor: 785,
235 patch: 5763,
236 build: 42,
237 };
238 assert_that(version)
239 .has_debug_value("Version { major: 115, minor: 785, patch: 5763, build: 42 }");
240 }
241
242 #[test]
243 fn comparisons() {
244 let v1 = Version {
245 major: 1,
246 minor: 0,
247 patch: 0,
248 build: 0,
249 };
250 let v2 = Version {
251 major: 1,
252 minor: 0,
253 patch: 0,
254 build: 1,
255 };
256 let v3 = Version {
257 major: 1,
258 minor: 0,
259 patch: 1,
260 build: 0,
261 };
262 let v4 = Version {
263 major: 1,
264 minor: 1,
265 patch: 0,
266 build: 0,
267 };
268 let v5 = Version {
269 major: 2,
270 minor: 0,
271 patch: 0,
272 build: 0,
273 };
274
275 assert_that(v1).is_less_than(v2);
276 assert_that(v1).is_less_than(v3);
277 assert_that(v1).is_less_than(v4);
278 assert_that(v1).is_less_than(v5);
279 assert_that(v2).is_less_than(v3);
280 assert_that(v2).is_less_than(v4);
281 assert_that(v2).is_less_than(v5);
282 assert_that(v3).is_less_than(v4);
283 assert_that(v3).is_less_than(v5);
284 assert_that(v4).is_less_than(v5);
285
286 assert_that(v2).is_greater_than(v1);
287 assert_that(v3).is_greater_than(v1);
288 assert_that(v4).is_greater_than(v1);
289 assert_that(v5).is_greater_than(v1);
290 assert_that(v3).is_greater_than(v2);
291 assert_that(v4).is_greater_than(v2);
292 assert_that(v5).is_greater_than(v2);
293 assert_that(v4).is_greater_than(v3);
294 assert_that(v5).is_greater_than(v3);
295 assert_that(v5).is_greater_than(v4);
296
297 assert_that(v1).is_equal_to(v1);
298 assert_that(v1).is_less_or_equal_to(v1);
299 assert_that(v1).is_greater_or_equal_to(v1);
300
301 assert_that(v2).is_equal_to(v2);
302 assert_that(v2).is_less_or_equal_to(v2);
303 assert_that(v2).is_greater_or_equal_to(v2);
304
305 assert_that(v3).is_equal_to(v3);
306 assert_that(v3).is_less_or_equal_to(v3);
307 assert_that(v3).is_greater_or_equal_to(v3);
308
309 assert_that(v4).is_equal_to(v4);
310 assert_that(v4).is_less_or_equal_to(v4);
311 assert_that(v4).is_greater_or_equal_to(v4);
312
313 assert_that(v5).is_equal_to(v5);
314 assert_that(v5).is_less_or_equal_to(v5);
315 assert_that(v5).is_greater_or_equal_to(v5);
316 }
317}