rez_next_package/
requirement.rs

1//! Package requirement parsing and handling
2
3use rez_next_version::Version;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::str::FromStr;
7
8/// A package requirement specification
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct Requirement {
11    /// Package name
12    pub name: String,
13
14    /// Version constraint
15    pub version_constraint: Option<VersionConstraint>,
16
17    /// Whether this is a weak requirement (optional)
18    pub weak: bool,
19}
20
21/// Version constraint types
22#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub enum VersionConstraint {
24    /// Exact version match (==)
25    Exact(Version),
26
27    /// Greater than (>)
28    GreaterThan(Version),
29
30    /// Greater than or equal (>=)
31    GreaterThanOrEqual(Version),
32
33    /// Less than (<)
34    LessThan(Version),
35
36    /// Less than or equal (<=)
37    LessThanOrEqual(Version),
38
39    /// Compatible version (~=)
40    Compatible(Version),
41
42    /// Range constraint (>=min, <max)
43    Range(Version, Version),
44
45    /// Any version
46    Any,
47}
48
49impl Requirement {
50    /// Create a new requirement
51    pub fn new(name: String) -> Self {
52        Self {
53            name,
54            version_constraint: None,
55            weak: false,
56        }
57    }
58
59    /// Check if a version satisfies this requirement
60    pub fn is_satisfied_by(&self, version: &Version) -> bool {
61        match &self.version_constraint {
62            None => true, // No constraint means any version is acceptable
63            Some(constraint) => constraint.is_satisfied_by(version),
64        }
65    }
66
67    /// Get the package name
68    pub fn package_name(&self) -> &str {
69        &self.name
70    }
71}
72
73impl VersionConstraint {
74    /// Check if a version satisfies this constraint
75    pub fn is_satisfied_by(&self, version: &Version) -> bool {
76        match self {
77            VersionConstraint::Exact(v) => version == v,
78            VersionConstraint::GreaterThan(v) => version > v,
79            VersionConstraint::GreaterThanOrEqual(v) => version >= v,
80            VersionConstraint::LessThan(v) => version < v,
81            VersionConstraint::LessThanOrEqual(v) => version <= v,
82            VersionConstraint::Compatible(v) => {
83                // Compatible version (~=) means >= v but < next major version
84                version >= v && version.as_str().split('.').next() == v.as_str().split('.').next()
85            }
86            VersionConstraint::Range(min, max) => version >= min && version < max,
87            VersionConstraint::Any => true,
88        }
89    }
90}
91
92impl FromStr for Requirement {
93    type Err = String;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        let s = s.trim();
97
98        // Handle weak requirements (starting with ~)
99        let (s, weak) = if s.starts_with("~") {
100            (&s[1..], true)
101        } else {
102            (s, false)
103        };
104
105        // Parse version constraints
106        if s.ends_with("+") {
107            // Handle "package-1.0+" syntax (equivalent to >=1.0)
108            let without_plus = &s[..s.len() - 1];
109            if let Some(dash_pos) = without_plus.rfind("-") {
110                let name = without_plus[..dash_pos].to_string();
111                let version_str = &without_plus[dash_pos + 1..];
112                let version = Version::parse(version_str)
113                    .map_err(|e| format!("Invalid version {}: {}", version_str, e))?;
114                Ok(Requirement {
115                    name,
116                    version_constraint: Some(VersionConstraint::GreaterThanOrEqual(version)),
117                    weak,
118                })
119            } else {
120                Ok(Requirement {
121                    name: s.to_string(),
122                    version_constraint: None,
123                    weak,
124                })
125            }
126        } else {
127            // Just a package name
128            Ok(Requirement {
129                name: s.to_string(),
130                version_constraint: None,
131                weak,
132            })
133        }
134    }
135}
136
137impl fmt::Display for Requirement {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        if self.weak {
140            write!(f, "~")?;
141        }
142
143        write!(f, "{}", self.name)?;
144
145        if let Some(ref constraint) = self.version_constraint {
146            write!(f, "{}", constraint)?;
147        }
148
149        Ok(())
150    }
151}
152
153impl fmt::Display for VersionConstraint {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        match self {
156            VersionConstraint::Exact(v) => write!(f, "=={}", v.as_str()),
157            VersionConstraint::GreaterThan(v) => write!(f, ">{}", v.as_str()),
158            VersionConstraint::GreaterThanOrEqual(v) => write!(f, ">={}", v.as_str()),
159            VersionConstraint::LessThan(v) => write!(f, "<{}", v.as_str()),
160            VersionConstraint::LessThanOrEqual(v) => write!(f, "<={}", v.as_str()),
161            VersionConstraint::Compatible(v) => write!(f, "~={}", v.as_str()),
162            VersionConstraint::Range(min, max) => write!(f, ">={},<{}", min.as_str(), max.as_str()),
163            VersionConstraint::Any => write!(f, ""),
164        }
165    }
166}