Skip to main content

mur_common/skill/
constraint.rs

1use semver::{Version, VersionReq};
2
3#[derive(Debug, Clone)]
4pub struct Constraint(pub VersionReq);
5
6#[derive(Debug, thiserror::Error)]
7pub enum ConstraintError {
8    #[error("invalid version requirement '{0}': {1}")]
9    Parse(String, String),
10}
11
12impl Constraint {
13    /// Parse "requires:[].version".
14    /// Accepts: `>=1.0.0`, `^1.2.3`, `~1.2.3`, exact `1.2.3`, `*`.
15    /// Empty string and `*` both mean "any".
16    pub fn parse(s: &str) -> Result<Self, ConstraintError> {
17        let trimmed = s.trim();
18        if trimmed.is_empty() || trimmed == "*" {
19            return Ok(Self(VersionReq::STAR));
20        }
21        VersionReq::parse(trimmed)
22            .map(Self)
23            .map_err(|e| ConstraintError::Parse(trimmed.into(), e.to_string()))
24    }
25
26    pub fn matches(&self, v: &Version) -> bool {
27        self.0.matches(v)
28    }
29
30    pub fn any() -> Self {
31        Self(VersionReq::STAR)
32    }
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    #[test]
40    fn star_matches_anything() {
41        let c = Constraint::parse("*").unwrap();
42        assert!(c.matches(&Version::parse("0.0.1").unwrap()));
43        assert!(c.matches(&Version::parse("99.99.99").unwrap()));
44    }
45
46    #[test]
47    fn ge_pins_minimum() {
48        let c = Constraint::parse(">=1.0.0").unwrap();
49        assert!(!c.matches(&Version::parse("0.9.9").unwrap()));
50        assert!(c.matches(&Version::parse("1.0.0").unwrap()));
51        assert!(c.matches(&Version::parse("2.0.0").unwrap()));
52    }
53
54    #[test]
55    fn caret_locks_major() {
56        let c = Constraint::parse("^1.2.3").unwrap();
57        assert!(c.matches(&Version::parse("1.9.0").unwrap()));
58        assert!(!c.matches(&Version::parse("2.0.0").unwrap()));
59    }
60
61    #[test]
62    fn tilde_locks_minor() {
63        let c = Constraint::parse("~1.2.3").unwrap();
64        assert!(c.matches(&Version::parse("1.2.9").unwrap()));
65        assert!(!c.matches(&Version::parse("1.3.0").unwrap()));
66    }
67
68    #[test]
69    fn exact_pins_exact() {
70        let c = Constraint::parse("=1.2.3").unwrap();
71        assert!(c.matches(&Version::parse("1.2.3").unwrap()));
72        assert!(!c.matches(&Version::parse("1.2.4").unwrap()));
73    }
74
75    #[test]
76    fn empty_means_any() {
77        let c = Constraint::parse("").unwrap();
78        assert!(c.matches(&Version::parse("0.0.1").unwrap()));
79    }
80
81    #[test]
82    fn garbage_fails() {
83        assert!(Constraint::parse("not-a-version").is_err());
84    }
85}