Skip to main content

zccache_core/
version.rs

1//! Lightweight semver parser for `major.minor.patch` version strings.
2//!
3//! No external dependencies — versions from Cargo.toml are always clean
4//! three-component strings, so a 10-line parser is all we need.
5
6/// A parsed `major.minor.patch` version.
7///
8/// `Ord` is derived over `(major, minor, patch)` which gives correct
9/// semver ordering for our use case (no pre-release tags).
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
11pub struct Version {
12    pub major: u32,
13    pub minor: u32,
14    pub patch: u32,
15}
16
17impl Version {
18    /// Parse a `"major.minor.patch"` string. Returns `None` on any
19    /// malformed input (wrong number of components, non-numeric, etc.).
20    pub fn parse(s: &str) -> Option<Self> {
21        let mut parts = s.split('.');
22        let major = parts.next()?.parse().ok()?;
23        let minor = parts.next()?.parse().ok()?;
24        let patch = parts.next()?.parse().ok()?;
25        if parts.next().is_some() {
26            return None; // reject "1.2.3.4"
27        }
28        Some(Self {
29            major,
30            minor,
31            patch,
32        })
33    }
34}
35
36/// Return the version of the currently compiled crate.
37pub fn current() -> Version {
38    Version::parse(crate::VERSION).expect("CARGO_PKG_VERSION is not valid semver")
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn parse_valid() {
47        assert_eq!(
48            Version::parse("1.2.3"),
49            Some(Version {
50                major: 1,
51                minor: 2,
52                patch: 3,
53            })
54        );
55    }
56
57    #[test]
58    fn parse_zeros() {
59        assert_eq!(
60            Version::parse("0.0.0"),
61            Some(Version {
62                major: 0,
63                minor: 0,
64                patch: 0,
65            })
66        );
67    }
68
69    #[test]
70    fn parse_large_numbers() {
71        assert_eq!(
72            Version::parse("100.200.300"),
73            Some(Version {
74                major: 100,
75                minor: 200,
76                patch: 300,
77            })
78        );
79    }
80
81    #[test]
82    fn parse_rejects_empty() {
83        assert_eq!(Version::parse(""), None);
84    }
85
86    #[test]
87    fn parse_rejects_one_component() {
88        assert_eq!(Version::parse("1"), None);
89    }
90
91    #[test]
92    fn parse_rejects_two_components() {
93        assert_eq!(Version::parse("1.2"), None);
94    }
95
96    #[test]
97    fn parse_rejects_four_components() {
98        assert_eq!(Version::parse("1.2.3.4"), None);
99    }
100
101    #[test]
102    fn parse_rejects_non_numeric() {
103        assert_eq!(Version::parse("1.2.beta"), None);
104    }
105
106    #[test]
107    fn parse_rejects_negative() {
108        assert_eq!(Version::parse("1.-2.3"), None);
109    }
110
111    #[test]
112    fn ordering_by_patch() {
113        let v1 = Version::parse("1.0.1").unwrap();
114        let v2 = Version::parse("1.0.2").unwrap();
115        assert!(v1 < v2);
116    }
117
118    #[test]
119    fn ordering_by_minor() {
120        let v1 = Version::parse("1.1.9").unwrap();
121        let v2 = Version::parse("1.2.0").unwrap();
122        assert!(v1 < v2);
123    }
124
125    #[test]
126    fn ordering_by_major() {
127        let v1 = Version::parse("1.9.9").unwrap();
128        let v2 = Version::parse("2.0.0").unwrap();
129        assert!(v1 < v2);
130    }
131
132    #[test]
133    fn ordering_equal() {
134        let v1 = Version::parse("1.2.3").unwrap();
135        let v2 = Version::parse("1.2.3").unwrap();
136        assert_eq!(v1, v2);
137        assert!(v1 >= v2);
138        assert!(v1 <= v2);
139    }
140
141    #[test]
142    fn current_version_parses() {
143        let v = current();
144        // Just verify it parsed — the exact value depends on Cargo.toml
145        assert!(v.major > 0 || v.minor > 0 || v.patch > 0);
146    }
147}