Skip to main content

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                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}