rattler_conda_types/version_spec/
constraint.rs

1use super::ParseConstraintError;
2use super::RangeOperator;
3use crate::version_spec::parse::constraint_parser;
4use crate::version_spec::{EqualityOperator, StrictRangeOperator};
5use crate::{ParseStrictness, Version};
6use std::str::FromStr;
7
8/// A single version constraint (e.g. `>3.4.5` or `1.2.*`)
9#[allow(clippy::large_enum_variant)]
10#[derive(Debug, Clone, Eq, PartialEq, Hash)]
11pub enum Constraint {
12    /// Matches anything (`*`)
13    Any,
14
15    /// Version comparison (e.g `>1.2.3`)
16    Comparison(RangeOperator, Version),
17
18    /// Strict comparison (e.g `~=1.2.3`)
19    StrictComparison(StrictRangeOperator, Version),
20
21    /// Exact Version
22    Exact(EqualityOperator, Version),
23}
24
25/// Returns true if the specified character is the first character of a version constraint.
26pub(crate) fn is_start_of_version_constraint(c: char) -> bool {
27    matches!(c, '>' | '<' | '=' | '!' | '~')
28}
29
30impl FromStr for Constraint {
31    type Err = ParseConstraintError;
32
33    fn from_str(s: &str) -> Result<Self, Self::Err> {
34        Constraint::from_str(s, ParseStrictness::Lenient)
35    }
36}
37
38impl Constraint {
39    pub fn from_str(
40        input: &str,
41        strictness: ParseStrictness,
42    ) -> Result<Self, ParseConstraintError> {
43        match constraint_parser(strictness)(input) {
44            Ok(("", version)) => Ok(version),
45            Ok((_, _)) => Err(ParseConstraintError::ExpectedEof),
46            Err(nom::Err::Failure(e) | nom::Err::Error(e)) => Err(e),
47            Err(_) => unreachable!("not streaming, so no other error possible"),
48        }
49    }
50}
51
52#[cfg(test)]
53mod test {
54    use super::Constraint;
55    use crate::version_spec::constraint::ParseConstraintError;
56    use crate::version_spec::{EqualityOperator, RangeOperator, StrictRangeOperator};
57    use crate::{ParseStrictness, ParseStrictness::*, Version};
58    use assert_matches::assert_matches;
59    use rstest::rstest;
60    use std::str::FromStr;
61
62    #[rstest]
63    fn test_empty(#[values(Lenient, Strict)] strictness: ParseStrictness) {
64        assert!(matches!(
65            Constraint::from_str("", strictness),
66            Err(ParseConstraintError::InvalidVersion(_))
67        ));
68    }
69
70    #[test]
71    fn test_any() {
72        assert_eq!(Constraint::from_str("*", Lenient), Ok(Constraint::Any));
73        assert_eq!(Constraint::from_str("*", Strict), Ok(Constraint::Any));
74        assert_eq!(Constraint::from_str("*.*", Lenient), Ok(Constraint::Any));
75        assert_eq!(
76            Constraint::from_str("*.*", Strict),
77            Err(ParseConstraintError::InvalidGlob)
78        );
79    }
80
81    #[rstest]
82    fn test_invalid_op(#[values(Lenient, Strict)] strictness: ParseStrictness) {
83        assert_eq!(
84            Constraint::from_str("<>1.2.3", strictness),
85            Err(ParseConstraintError::InvalidOperator(String::from("<>")))
86        );
87        assert_eq!(
88            Constraint::from_str("=!1.2.3", strictness),
89            Err(ParseConstraintError::InvalidOperator(String::from("=!")))
90        );
91        assert_eq!(
92            Constraint::from_str("<!=1.2.3", strictness),
93            Err(ParseConstraintError::InvalidOperator(String::from("<!=")))
94        );
95        assert_eq!(
96            Constraint::from_str("<!>1.2.3", strictness),
97            Err(ParseConstraintError::InvalidOperator(String::from("<!>")))
98        );
99        assert_eq!(
100            Constraint::from_str("!=!1.2.3", strictness),
101            Err(ParseConstraintError::InvalidOperator(String::from("!=!")))
102        );
103        assert_eq!(
104            Constraint::from_str("<=>1.2.3", strictness),
105            Err(ParseConstraintError::InvalidOperator(String::from("<=>")))
106        );
107        assert_eq!(
108            Constraint::from_str("=>1.2.3", strictness),
109            Err(ParseConstraintError::InvalidOperator(String::from("=>")))
110        );
111    }
112
113    #[rstest]
114    fn test_op(#[values(Lenient, Strict)] strictness: ParseStrictness) {
115        assert_eq!(
116            Constraint::from_str(">1.2.3", strictness),
117            Ok(Constraint::Comparison(
118                RangeOperator::Greater,
119                Version::from_str("1.2.3").unwrap(),
120            ))
121        );
122        assert_eq!(
123            Constraint::from_str("<1.2.3", strictness),
124            Ok(Constraint::Comparison(
125                RangeOperator::Less,
126                Version::from_str("1.2.3").unwrap(),
127            ))
128        );
129        assert_eq!(
130            Constraint::from_str("=1.2.3", strictness),
131            Ok(Constraint::StrictComparison(
132                StrictRangeOperator::StartsWith,
133                Version::from_str("1.2.3").unwrap(),
134            ))
135        );
136        assert_eq!(
137            Constraint::from_str("==1.2.3", strictness),
138            Ok(Constraint::Exact(
139                EqualityOperator::Equals,
140                Version::from_str("1.2.3").unwrap(),
141            ))
142        );
143        assert_eq!(
144            Constraint::from_str("!=1.2.3", strictness),
145            Ok(Constraint::Exact(
146                EqualityOperator::NotEquals,
147                Version::from_str("1.2.3").unwrap(),
148            ))
149        );
150        assert_eq!(
151            Constraint::from_str("~=1.2.3", strictness),
152            Ok(Constraint::StrictComparison(
153                StrictRangeOperator::Compatible,
154                Version::from_str("1.2.3").unwrap(),
155            ))
156        );
157        assert_eq!(
158            Constraint::from_str(">=1.2.3", strictness),
159            Ok(Constraint::Comparison(
160                RangeOperator::GreaterEquals,
161                Version::from_str("1.2.3").unwrap(),
162            ))
163        );
164        assert_eq!(
165            Constraint::from_str("<=1.2.3", strictness),
166            Ok(Constraint::Comparison(
167                RangeOperator::LessEquals,
168                Version::from_str("1.2.3").unwrap(),
169            ))
170        );
171        assert_eq!(
172            Constraint::from_str(">=1!1.2", strictness),
173            Ok(Constraint::Comparison(
174                RangeOperator::GreaterEquals,
175                Version::from_str("1!1.2").unwrap(),
176            ))
177        );
178    }
179
180    #[test]
181    fn test_glob_op_lenient() {
182        assert_eq!(
183            Constraint::from_str("=1.2.*", Lenient),
184            Ok(Constraint::StrictComparison(
185                StrictRangeOperator::StartsWith,
186                Version::from_str("1.2").unwrap(),
187            ))
188        );
189        assert_eq!(
190            Constraint::from_str("!=1.2.*", Lenient),
191            Ok(Constraint::StrictComparison(
192                StrictRangeOperator::NotStartsWith,
193                Version::from_str("1.2").unwrap(),
194            ))
195        );
196        assert_eq!(
197            Constraint::from_str(">=1.2.*", Lenient),
198            Ok(Constraint::Comparison(
199                RangeOperator::GreaterEquals,
200                Version::from_str("1.2").unwrap(),
201            ))
202        );
203        assert_eq!(
204            Constraint::from_str("==1.2.*", Lenient),
205            Ok(Constraint::Exact(
206                EqualityOperator::Equals,
207                Version::from_str("1.2").unwrap(),
208            ))
209        );
210        assert_eq!(
211            Constraint::from_str(">1.2.*", Lenient),
212            Ok(Constraint::Comparison(
213                RangeOperator::GreaterEquals,
214                Version::from_str("1.2").unwrap(),
215            ))
216        );
217        assert_eq!(
218            Constraint::from_str("<=1.2.*", Lenient),
219            Ok(Constraint::Comparison(
220                RangeOperator::LessEquals,
221                Version::from_str("1.2").unwrap(),
222            ))
223        );
224        assert_eq!(
225            Constraint::from_str("<1.2.*", Lenient),
226            Ok(Constraint::Comparison(
227                RangeOperator::Less,
228                Version::from_str("1.2").unwrap(),
229            ))
230        );
231    }
232
233    #[test]
234    fn test_glob_op_strict() {
235        assert_matches!(
236            Constraint::from_str("=1.2.*", Strict),
237            Err(ParseConstraintError::GlobVersionIncompatibleWithOperator(_))
238        );
239        assert_eq!(
240            Constraint::from_str("!=1.2.*", Lenient),
241            Ok(Constraint::StrictComparison(
242                StrictRangeOperator::NotStartsWith,
243                Version::from_str("1.2").unwrap(),
244            ))
245        );
246        assert_matches!(
247            Constraint::from_str(">=1.2.*", Strict),
248            Err(ParseConstraintError::GlobVersionIncompatibleWithOperator(_))
249        );
250        assert_matches!(
251            Constraint::from_str("==1.2.*", Strict),
252            Err(ParseConstraintError::GlobVersionIncompatibleWithOperator(_))
253        );
254        assert_matches!(
255            Constraint::from_str(">1.2.*", Strict),
256            Err(ParseConstraintError::GlobVersionIncompatibleWithOperator(_))
257        );
258        assert_matches!(
259            Constraint::from_str("<=1.2.*", Strict),
260            Err(ParseConstraintError::GlobVersionIncompatibleWithOperator(_))
261        );
262        assert_matches!(
263            Constraint::from_str("<1.2.*", Strict),
264            Err(ParseConstraintError::GlobVersionIncompatibleWithOperator(_))
265        );
266    }
267
268    #[rstest]
269    fn test_starts_with(#[values(Lenient, Strict)] strictness: ParseStrictness) {
270        assert_eq!(
271            Constraint::from_str("1.2.*", strictness),
272            Ok(Constraint::StrictComparison(
273                StrictRangeOperator::StartsWith,
274                Version::from_str("1.2").unwrap(),
275            ))
276        );
277    }
278
279    #[rstest]
280    fn test_exact(#[values(Lenient, Strict)] strictness: ParseStrictness) {
281        assert_eq!(
282            Constraint::from_str("==1.2.3", strictness),
283            Ok(Constraint::Exact(
284                EqualityOperator::Equals,
285                Version::from_str("1.2.3").unwrap(),
286            ))
287        );
288    }
289
290    #[rstest]
291    fn test_regex(#[values(Lenient, Strict)] strictness: ParseStrictness) {
292        assert_eq!(
293            Constraint::from_str("^1.2.3", strictness),
294            Err(ParseConstraintError::UnterminatedRegex)
295        );
296        assert_eq!(
297            Constraint::from_str("1.2.3$", strictness),
298            Err(ParseConstraintError::UnterminatedRegex)
299        );
300        assert_eq!(
301            Constraint::from_str("1.*.3", strictness),
302            Err(ParseConstraintError::RegexConstraintsNotSupported)
303        );
304    }
305}