Skip to main content

origin_xtask/
version.rs

1//! The Origin version a project tracks.
2//!
3//! Only `major.minor.patch` — enough to order migrations, and nothing more, so that a
4//! project cannot end up on a version this cannot compare.
5
6use std::cmp::Ordering;
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Version {
11    pub major: u32,
12    pub minor: u32,
13    pub patch: u32,
14}
15
16impl Version {
17    pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
18        Self {
19            major,
20            minor,
21            patch,
22        }
23    }
24
25    pub fn parse(value: &str) -> Result<Self, String> {
26        let mut parts = value.trim().split('.');
27
28        let mut next = |what: &str| -> Result<u32, String> {
29            parts
30                .next()
31                .ok_or_else(|| format!("version `{value}` has no {what}"))?
32                .parse()
33                .map_err(|_| format!("version `{value}` has a non-numeric {what}"))
34        };
35
36        let version = Self {
37            major: next("major")?,
38            minor: next("minor")?,
39            patch: next("patch")?,
40        };
41
42        if parts.next().is_some() {
43            return Err(format!("version `{value}` has more than three parts"));
44        }
45
46        Ok(version)
47    }
48}
49
50impl Ord for Version {
51    fn cmp(&self, other: &Self) -> Ordering {
52        (self.major, self.minor, self.patch).cmp(&(other.major, other.minor, other.patch))
53    }
54}
55
56impl PartialOrd for Version {
57    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
58        Some(self.cmp(other))
59    }
60}
61
62impl fmt::Display for Version {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn versions_order_by_component() {
74        assert!(Version::parse("0.2.0").unwrap() > Version::parse("0.1.9").unwrap());
75        assert!(Version::parse("1.0.0").unwrap() > Version::parse("0.99.99").unwrap());
76        assert!(Version::parse("0.1.10").unwrap() > Version::parse("0.1.9").unwrap());
77    }
78
79    #[test]
80    fn a_version_that_cannot_be_compared_is_rejected_rather_than_guessed() {
81        assert!(Version::parse("0.1").is_err());
82        assert!(Version::parse("0.1.0-beta").is_err());
83        assert!(Version::parse("0.1.0.1").is_err());
84    }
85}