Skip to main content

tl_package/
version.rs

1use std::fmt;
2
3/// A semantic version (wrapper around semver::Version).
4#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
5pub struct Version {
6    inner: semver::Version,
7}
8
9/// A version requirement (wrapper around semver::VersionReq).
10#[derive(Debug, Clone)]
11pub struct VersionReq {
12    inner: semver::VersionReq,
13}
14
15impl Version {
16    /// Parse a version string like "1.2.3".
17    pub fn parse(s: &str) -> Result<Self, String> {
18        semver::Version::parse(s)
19            .map(|v| Version { inner: v })
20            .map_err(|e| format!("Invalid version '{s}': {e}"))
21    }
22
23    /// Get the inner semver::Version.
24    pub fn as_semver(&self) -> &semver::Version {
25        &self.inner
26    }
27}
28
29impl fmt::Display for Version {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "{}", self.inner)
32    }
33}
34
35impl VersionReq {
36    /// Parse a version requirement like "^1.0", ">=2.0", "1.2".
37    pub fn parse(s: &str) -> Result<Self, String> {
38        // Try as a requirement first
39        if let Ok(req) = semver::VersionReq::parse(s) {
40            return Ok(VersionReq { inner: req });
41        }
42        // If it looks like a bare version, treat as "^version"
43        if let Ok(ver) = semver::Version::parse(s) {
44            let req = semver::VersionReq::parse(&format!("^{ver}"))
45                .map_err(|e| format!("Invalid version req '{s}': {e}"))?;
46            return Ok(VersionReq { inner: req });
47        }
48        Err(format!("Invalid version requirement '{s}'"))
49    }
50
51    /// Check if a version matches this requirement.
52    pub fn matches(&self, version: &Version) -> bool {
53        self.inner.matches(&version.inner)
54    }
55}
56
57impl fmt::Display for VersionReq {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "{}", self.inner)
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn version_parse_and_display() {
69        let v = Version::parse("1.2.3").unwrap();
70        assert_eq!(v.to_string(), "1.2.3");
71
72        assert!(Version::parse("not-a-version").is_err());
73    }
74
75    #[test]
76    fn version_req_matching() {
77        let req = VersionReq::parse("^1.0").unwrap();
78        assert!(req.matches(&Version::parse("1.0.0").unwrap()));
79        assert!(req.matches(&Version::parse("1.9.9").unwrap()));
80        assert!(!req.matches(&Version::parse("2.0.0").unwrap()));
81
82        let req2 = VersionReq::parse(">=2.0, <3.0").unwrap();
83        assert!(req2.matches(&Version::parse("2.5.0").unwrap()));
84        assert!(!req2.matches(&Version::parse("3.0.0").unwrap()));
85    }
86}