chrome_for_testing/api/
version.rs

1use serde::de::{self, Visitor};
2use serde::{Deserialize, Deserializer};
3use std::cmp::PartialOrd;
4use std::fmt::{Display, Formatter};
5
6/// A version identifier.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct Version {
9    /// The major version number.
10    pub major: u32,
11
12    /// The minor version number.
13    pub minor: u32,
14
15    /// The patch version number.
16    pub patch: u32,
17
18    /// The build version number.
19    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}