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