alpm_types/version/
requirement.rs

1//! Version requirement declarations and comparisons based on them.
2
3use std::{
4    cmp::Ordering,
5    fmt::{Display, Formatter},
6    str::FromStr,
7};
8
9use alpm_parsers::iter_str_context;
10use serde::{Deserialize, Serialize};
11use strum::VariantNames;
12use winnow::{
13    ModalResult,
14    Parser,
15    combinator::{alt, eof, fail, seq},
16    error::{StrContext, StrContextValue},
17    token::take_while,
18};
19
20use crate::{Error, Version};
21
22/// A version requirement, e.g. for a dependency package.
23///
24/// It consists of a target version and a comparison function. A version requirement of `>=1.5` has
25/// a target version of `1.5` and a comparison function of [`VersionComparison::GreaterOrEqual`].
26/// See [alpm-comparison] for details on the format.
27///
28/// ## Examples
29///
30/// ```
31/// use std::str::FromStr;
32///
33/// use alpm_types::{Version, VersionComparison, VersionRequirement};
34///
35/// # fn main() -> Result<(), alpm_types::Error> {
36/// let requirement = VersionRequirement::from_str(">=1.5")?;
37///
38/// assert_eq!(requirement.comparison, VersionComparison::GreaterOrEqual);
39/// assert_eq!(requirement.version, Version::from_str("1.5")?);
40/// # Ok(())
41/// # }
42/// ```
43///
44/// [alpm-comparison]: https://alpm.archlinux.page/specifications/alpm-comparison.7.html
45#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
46pub struct VersionRequirement {
47    /// Version comparison function
48    pub comparison: VersionComparison,
49    /// Target version
50    pub version: Version,
51}
52
53impl VersionRequirement {
54    /// Create a new `VersionRequirement`
55    pub fn new(comparison: VersionComparison, version: Version) -> Self {
56        VersionRequirement {
57            comparison,
58            version,
59        }
60    }
61
62    /// Returns `true` if the requirement is satisfied by the given package version.
63    ///
64    /// ## Examples
65    ///
66    /// ```
67    /// use std::str::FromStr;
68    ///
69    /// use alpm_types::{Version, VersionRequirement};
70    ///
71    /// # fn main() -> Result<(), alpm_types::Error> {
72    /// let requirement = VersionRequirement::from_str(">=1.5-3")?;
73    ///
74    /// assert!(!requirement.is_satisfied_by(&Version::from_str("1.5")?));
75    /// assert!(requirement.is_satisfied_by(&Version::from_str("1.5-3")?));
76    /// assert!(requirement.is_satisfied_by(&Version::from_str("1.6")?));
77    /// assert!(requirement.is_satisfied_by(&Version::from_str("2:1.0")?));
78    /// assert!(!requirement.is_satisfied_by(&Version::from_str("1.0")?));
79    ///
80    /// // If pkgrel is not specified in the requirement, it is ignored in the comparison.
81    /// let requirement = VersionRequirement::from_str("=1.5")?;
82    /// assert!(requirement.is_satisfied_by(&Version::from_str("1.5-3")?));
83    /// # Ok(())
84    /// # }
85    /// ```
86    pub fn is_satisfied_by(&self, ver: &Version) -> bool {
87        // If the requirement does not specify a pkgrel, we ignore it in the comparison.
88        // so that `foo=1` can be satisfied by `foo=1-1`.
89        let other_version = if self.version.pkgrel.is_none() {
90            &Version {
91                pkgrel: None,
92                ..ver.clone()
93            }
94        } else {
95            ver
96        };
97        self.comparison
98            .is_compatible_with(other_version.cmp(&self.version))
99    }
100
101    /// Recognizes a [`VersionRequirement`] in a string slice.
102    ///
103    /// Consumes all of its input.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if `input` is not a valid _alpm-comparison_.
108    pub fn parser(input: &mut &str) -> ModalResult<Self> {
109        seq!(Self {
110            comparison: take_while(1.., ('<', '>', '='))
111                // add context here because otherwise take_while can fail and provide no information
112                .context(StrContext::Expected(StrContextValue::Description(
113                    "version comparison operator"
114                )))
115                .and_then(VersionComparison::parser),
116            version: Version::parser,
117        })
118        .parse_next(input)
119    }
120
121    /// Checks whether another [`VersionRequirement`] forms an intersection with this one.
122    ///
123    /// The intersection operation `∩` on versions simply checks if there is _any_ possible set of
124    /// versions that can exist while upholding the constraints (e.g. `>`/`<=`) on both versions.
125    ///
126    /// # Examples
127    ///
128    /// - The expression `<3 ∩ <1` forms the intersection of all versions `<1`
129    /// - The expression `<2 ∩ >1` forms the intersection `X` of all versions `1<X<2`
130    /// - The expression `=2 ∩ <3` forms the intersection of the exact version `2`
131    ///
132    /// ```
133    /// use std::str::FromStr;
134    ///
135    /// use alpm_types::VersionRequirement;
136    ///
137    /// # fn main() -> testresult::TestResult {
138    /// let requirement: VersionRequirement = "<1".parse()?;
139    /// assert!(requirement.is_intersection(&"<0.1".parse()?));
140    ///
141    /// let requirement: VersionRequirement = "<2".parse()?;
142    /// assert!(requirement.is_intersection(&">1".parse()?));
143    ///
144    /// let requirement: VersionRequirement = "=2".parse()?;
145    /// assert!(!requirement.is_intersection(&"<3".parse()?));
146    /// # Ok(())
147    /// # }
148    /// ```
149    pub fn is_intersection(&self, other: &VersionRequirement) -> bool {
150        // This documentation uses the `∩` set intersection operator to better visualize examples.
151        //
152        // In the following, we need to consider the ordering relationship between the actual
153        // versions of the two `VersionRequirement`s.
154        // If we have `self = ">1.0.1"` and `other = "<2"`, this handles the part of
155        // `"1.0.1".cmp("2")`.
156        let version_comparison = self.version.cmp(&other.version);
157
158        match self.comparison {
159            // Consider the case where we have a `Less`, e.g. `<1`.
160            VersionComparison::Less => {
161                match version_comparison {
162                    // The other version is greater, so its comparison must be "Less" or
163                    // "LessOrEqual" to form an intersection.
164                    //
165                    // Example:
166                    // - `<1.0.1 ∩ <2` forms the intersection of all versions `<1.0.1`
167                    Ordering::Less => matches!(
168                        other.comparison,
169                        VersionComparison::Less | VersionComparison::LessOrEqual
170                    ),
171                    // Both versions are matching. The comparison for other must be "Less" or
172                    // "LessOrEqual"
173                    //
174                    // Example:
175                    // - `<=2 ∩ <2` forms the intersection of all versions `<2`
176                    // - `<2 ∩ <=2` forms the intersection of all versions `<2`
177                    Ordering::Equal => matches!(
178                        other.comparison,
179                        VersionComparison::Less | VersionComparison::LessOrEqual
180                    ),
181
182                    // The other version is smaller.
183                    // Since `self` enforces the `Less` constraint, there will always be at least
184                    // **some** intersection.
185                    //
186                    // Example: Even if `other` also has a "Less" constraint, the expression
187                    // `<3 ∩ <1` forms the intersection of all versions `<1`
188                    Ordering::Greater => true,
189                }
190            }
191            // Consider the case where we have a `LessOrEqual`, e.g. `<=1`.
192            VersionComparison::LessOrEqual => {
193                match version_comparison {
194                    // The other version is greater, so its comparison must be "Less" or
195                    // "LessOrEqual" to form an intersection.
196                    //
197                    // Example:
198                    // - `>=1.0.1 ∩ <2` forms the intersection of all versions `1.0.1<=X<2`
199                    // - `>= 1.0.1 <= 1.2` forms the intersection of all versions `1.0.1<=X<=1.2`
200                    Ordering::Less => matches!(
201                        other.comparison,
202                        VersionComparison::Less | VersionComparison::LessOrEqual
203                    ),
204                    // Both versions are matching, the comparison for other must be either "Less"
205                    // or one of "Equal", "LessOrEqual", or "GreaterOrEqual".
206                    // Any `other` "*Equal" constraint will directly match the `self`
207                    // "Less**Equal**" constraint.
208                    //
209                    // Examples:
210                    // - `<=1 ∩ >=1` forms the intersection of the version `1`
211                    // - `<=1 ∩ <1` forms the intersection of all version `<1`
212                    // - `<=1 ∩ <=1` forms the intersection of all version `<=1`
213                    Ordering::Equal => matches!(
214                        other.comparison,
215                        VersionComparison::Less
216                            | VersionComparison::LessOrEqual
217                            | VersionComparison::Equal
218                            | VersionComparison::GreaterOrEqual
219                    ),
220                    // The other version is smaller.
221                    // Since `self` enforces the `Less` constraint, there will always be at least
222                    // **some** intersection.
223                    //
224                    // Example: Even if `other` also has a "Less" constraint, the expression
225                    // `=<3 ∩ <1` forms the intersection of all versions `<1`
226                    Ordering::Greater => true,
227                }
228            }
229            // Consider the case where we have a `Equal`, e.g. `=1`.
230            VersionComparison::Equal => match version_comparison {
231                // Both versions are matching, the comparison for `other` must be
232                // "LessOrEqual", "Equal", or "GreaterOrEqual" to match the "Equal" constraint on
233                // `self`
234                //
235                // Examples:
236                // - `=1 ∩ >=1` forms the intersection of the version `1`
237                // - `=1 ∩ <=1` forms the intersection of the version `1`
238                // - `=2 ∩ =2` forms the intersection of the version `2`
239                Ordering::Equal => matches!(
240                    other.comparison,
241                    VersionComparison::LessOrEqual
242                        | VersionComparison::Equal
243                        | VersionComparison::GreaterOrEqual
244                ),
245                // The other version must be greater or smaller, so it can be inherently not be
246                // equal.
247                Ordering::Less | Ordering::Greater => false,
248            },
249            // Consider the case where we have a `GreaterOrEqual`, e.g. `>=1`.
250            VersionComparison::GreaterOrEqual => match version_comparison {
251                // The other version is greater.
252                // Since `self` enforces a `Greater` constraint, so there will always be at least
253                // **some** intersection.
254                //
255                // Example: Even if `other` also has a "Less" constraint, the expression
256                // `>=1 ∩ <3` forms the intersection of all versions `1<=X<3`
257                Ordering::Less => true,
258                // Both versions are matching, the comparison for other must be either "Greater"
259                // or one of "Equal", "LessOrEqual", or "GreaterOrEqual".
260                // Any `other` "*Equal" constraint will directly match `self`'s
261                // "LesserOr**Equal**" constraint.
262                //
263                // Examples:
264                // - `>=1 ∩ <=1` forms the intersection of the version `1`
265                // - `>=1 ∩ >1` forms the intersection of all version `>1`
266                // - `>=1 ∩ >=1` forms the intersection of all version `>=1`
267                Ordering::Equal => matches!(
268                    other.comparison,
269                    VersionComparison::LessOrEqual
270                        | VersionComparison::Equal
271                        | VersionComparison::GreaterOrEqual
272                        | VersionComparison::Greater
273                ),
274                // The other version is smaller, so its comparison must be at least "Greater" or
275                // "GreaterOrEqual" to form an intersection.
276                //
277                // Example:
278                // - `>=2 ∩ >1.1` forms the intersection of all versions `>=2`
279                Ordering::Greater => matches!(
280                    other.comparison,
281                    VersionComparison::GreaterOrEqual | VersionComparison::Greater
282                ),
283            },
284            // Consider the case where we have a `Greater`, e.g. `>1`.
285            VersionComparison::Greater => {
286                match version_comparison {
287                    // The other version is greater.
288                    // Since `self` enforces a `Greater` constraint, so there will always be at
289                    // least **some** intersection.
290                    //
291                    // Example: Even if `other` also has a "Less" constraint, the expression
292                    // `>1 ∩ <3` forms the intersection of all versions `1<X<3`
293                    Ordering::Less => true,
294                    // Both versions are matching. The comparison for other must be "Greater" or
295                    // "GreaterOrEqual"
296                    //
297                    // Example:
298                    // - `>2 ∩ >2` forms the intersection of all versions `>2`
299                    // - `>2 ∩ >=2` forms the intersection of all versions `>2`
300                    Ordering::Equal => matches!(
301                        other.comparison,
302                        VersionComparison::GreaterOrEqual | VersionComparison::Greater
303                    ),
304                    // The other version is smaller, so its comparison must be at least "Greater" or
305                    // "GreaterOrEqual" to form an intersection.
306                    //
307                    // Example:
308                    // - `>2 ∩ >=1.1` forms the intersection of all versions `>=2`
309                    Ordering::Greater => matches!(
310                        other.comparison,
311                        VersionComparison::GreaterOrEqual | VersionComparison::Greater
312                    ),
313                }
314            }
315        }
316    }
317}
318
319impl Display for VersionRequirement {
320    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
321        write!(f, "{}{}", self.comparison, self.version)
322    }
323}
324
325impl FromStr for VersionRequirement {
326    type Err = Error;
327
328    /// Creates a new [`VersionRequirement`] from a string slice.
329    ///
330    /// Delegates to [`VersionRequirement::parser`].
331    ///
332    /// # Errors
333    ///
334    /// Returns an error if [`VersionRequirement::parser`] fails.
335    fn from_str(s: &str) -> Result<Self, Self::Err> {
336        Ok(Self::parser.parse(s)?)
337    }
338}
339
340/// Specifies the comparison function for a [`VersionRequirement`].
341///
342/// The package version can be required to be:
343/// - less than (`<`)
344/// - less than or equal to (`<=`)
345/// - equal to (`=`)
346/// - greater than or equal to (`>=`)
347/// - greater than (`>`)
348///
349/// the specified version.
350///
351/// See [alpm-comparison] for details on the format.
352///
353/// ## Note
354///
355/// The variants of this enum are sorted in a way, that prefers the two-letter comparators over
356/// the one-letter ones.
357/// This is because when splitting a string on the string representation of [`VersionComparison`]
358/// variant and relying on the ordering of [`strum::EnumIter`], the two-letter comparators must be
359/// checked before checking the one-letter ones to yield robust results.
360///
361/// [alpm-comparison]: https://alpm.archlinux.page/specifications/alpm-comparison.7.html
362#[derive(
363    strum::AsRefStr,
364    Clone,
365    Copy,
366    Debug,
367    strum::Display,
368    strum::EnumIter,
369    PartialEq,
370    Eq,
371    strum::VariantNames,
372    Serialize,
373    Deserialize,
374)]
375pub enum VersionComparison {
376    /// Less than or equal to
377    #[strum(to_string = "<=")]
378    LessOrEqual,
379
380    /// Greater than or equal to
381    #[strum(to_string = ">=")]
382    GreaterOrEqual,
383
384    /// Equal to
385    #[strum(to_string = "=")]
386    Equal,
387
388    /// Less than
389    #[strum(to_string = "<")]
390    Less,
391
392    /// Greater than
393    #[strum(to_string = ">")]
394    Greater,
395}
396
397impl VersionComparison {
398    /// Returns `true` if the result of a comparison between the actual and required package
399    /// versions satisfies the comparison function.
400    fn is_compatible_with(self, ord: Ordering) -> bool {
401        match (self, ord) {
402            (VersionComparison::Less, Ordering::Less)
403            | (VersionComparison::LessOrEqual, Ordering::Less | Ordering::Equal)
404            | (VersionComparison::Equal, Ordering::Equal)
405            | (VersionComparison::GreaterOrEqual, Ordering::Greater | Ordering::Equal)
406            | (VersionComparison::Greater, Ordering::Greater) => true,
407
408            (VersionComparison::Less, Ordering::Equal | Ordering::Greater)
409            | (VersionComparison::LessOrEqual, Ordering::Greater)
410            | (VersionComparison::Equal, Ordering::Less | Ordering::Greater)
411            | (VersionComparison::GreaterOrEqual, Ordering::Less)
412            | (VersionComparison::Greater, Ordering::Less | Ordering::Equal) => false,
413        }
414    }
415
416    /// Recognizes a [`VersionComparison`] in a string slice.
417    ///
418    /// Consumes all of its input.
419    ///
420    /// # Errors
421    ///
422    /// Returns an error if `input` is not a valid _alpm-comparison_.
423    pub fn parser(input: &mut &str) -> ModalResult<Self> {
424        alt((
425            // insert eofs here (instead of after alt call) so correct error message is thrown
426            ("<=", eof).value(Self::LessOrEqual),
427            (">=", eof).value(Self::GreaterOrEqual),
428            ("=", eof).value(Self::Equal),
429            ("<", eof).value(Self::Less),
430            (">", eof).value(Self::Greater),
431            fail.context(StrContext::Label("comparison operator"))
432                .context_with(iter_str_context!([VersionComparison::VARIANTS])),
433        ))
434        .parse_next(input)
435    }
436}
437
438impl FromStr for VersionComparison {
439    type Err = Error;
440
441    /// Creates a new [`VersionComparison`] from a string slice.
442    ///
443    /// Delegates to [`VersionComparison::parser`].
444    ///
445    /// # Errors
446    ///
447    /// Returns an error if [`VersionComparison::parser`] fails.
448    fn from_str(s: &str) -> Result<Self, Self::Err> {
449        Ok(Self::parser.parse(s)?)
450    }
451}
452
453#[cfg(test)]
454mod tests {
455    use rstest::rstest;
456    use testresult::TestResult;
457
458    use super::*;
459    /// Ensure that valid version comparison strings can be parsed.
460    #[rstest]
461    #[case("<", VersionComparison::Less)]
462    #[case("<=", VersionComparison::LessOrEqual)]
463    #[case("=", VersionComparison::Equal)]
464    #[case(">=", VersionComparison::GreaterOrEqual)]
465    #[case(">", VersionComparison::Greater)]
466    fn valid_version_comparison(#[case] comparison: &str, #[case] expected: VersionComparison) {
467        assert_eq!(comparison.parse(), Ok(expected));
468    }
469
470    /// Ensure that invalid version comparisons will throw an error.
471    #[rstest]
472    #[case("", "invalid comparison operator")]
473    #[case("<<", "invalid comparison operator")]
474    #[case("==", "invalid comparison operator")]
475    #[case("!=", "invalid comparison operator")]
476    #[case(" =", "invalid comparison operator")]
477    #[case("= ", "invalid comparison operator")]
478    #[case("<1", "invalid comparison operator")]
479    fn invalid_version_comparison(#[case] comparison: &str, #[case] err_snippet: &str) {
480        let Err(Error::ParseError(err_msg)) = VersionComparison::from_str(comparison) else {
481            panic!("'{comparison}' did not fail as expected")
482        };
483        assert!(
484            err_msg.contains(err_snippet),
485            "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
486        );
487    }
488
489    /// Test successful parsing for version requirement strings.
490    #[rstest]
491    #[case("=1", VersionRequirement {
492        comparison: VersionComparison::Equal,
493        version: Version::from_str("1").unwrap(),
494    })]
495    #[case("<=42:abcd-2.4", VersionRequirement {
496        comparison: VersionComparison::LessOrEqual,
497        version: Version::from_str("42:abcd-2.4").unwrap(),
498    })]
499    #[case(">3.1", VersionRequirement {
500        comparison: VersionComparison::Greater,
501        version: Version::from_str("3.1").unwrap(),
502    })]
503    fn valid_version_requirement(#[case] requirement: &str, #[case] expected: VersionRequirement) {
504        assert_eq!(
505            requirement.parse(),
506            Ok(expected),
507            "Expected successful parse for version requirement '{requirement}'"
508        );
509    }
510
511    #[rstest]
512    #[case::bad_operator("<>3.1", "invalid comparison operator")]
513    #[case::no_operator("3.1", "expected version comparison operator")]
514    #[case::arrow_operator("=>3.1", "invalid comparison operator")]
515    #[case::no_version("<=", "expected pkgver string")]
516    fn invalid_version_requirement(#[case] requirement: &str, #[case] err_snippet: &str) {
517        let Err(Error::ParseError(err_msg)) = VersionRequirement::from_str(requirement) else {
518            panic!("'{requirement}' erroneously parsed as VersionRequirement")
519        };
520        assert!(
521            err_msg.contains(err_snippet),
522            "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
523        );
524    }
525
526    #[rstest]
527    #[case("<3.1>3.2", "invalid pkgver character")]
528    fn invalid_version_requirement_pkgver_parse(
529        #[case] requirement: &str,
530        #[case] err_snippet: &str,
531    ) {
532        let Err(Error::ParseError(err_msg)) = VersionRequirement::from_str(requirement) else {
533            panic!("'{requirement}' erroneously parsed as VersionRequirement")
534        };
535        assert!(
536            err_msg.contains(err_snippet),
537            "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
538        );
539    }
540
541    /// Check whether a version requirement (>= 1.0) is fulfilled by a given version string.
542    #[rstest]
543    #[case("=1", "1", true)]
544    #[case("=1", "1.0", false)]
545    #[case("=1", "1-1", true)]
546    #[case("=1", "1:1", false)]
547    #[case("=1", "0.9", false)]
548    #[case("<42", "41", true)]
549    #[case("<42", "42", false)]
550    #[case("<42", "43", false)]
551    #[case("<=42", "41", true)]
552    #[case("<=42", "42", true)]
553    #[case("<=42", "43", false)]
554    #[case(">42", "41", false)]
555    #[case(">42", "42", false)]
556    #[case(">42", "43", true)]
557    #[case(">=42", "41", false)]
558    #[case(">=42", "42", true)]
559    #[case(">=42", "43", true)]
560    fn version_requirement_satisfied(
561        #[case] requirement: &str,
562        #[case] version: &str,
563        #[case] result: bool,
564    ) {
565        let requirement = VersionRequirement::from_str(requirement).unwrap();
566        let version = Version::from_str(version).unwrap();
567        assert_eq!(requirement.is_satisfied_by(&version), result);
568    }
569
570    #[rstest]
571    #[case::self_less_matching_other_less("<1", "<1")]
572    #[case::self_less_matching_other_less_or_equal("<1", "<=1")]
573    #[case::self_less_bigger_other_less("<1", "<2")]
574    #[case::self_less_bigger_other_less_or_equal("<1", "<=2")]
575    #[case::self_less_smaller_other_less("<1", "<0.1")]
576    #[case::self_less_smaller_other_less_or_equal("<1", "<=0.1")]
577    #[case::self_less_smaller_other_equal("<1", "=0.1")]
578    #[case::self_less_smaller_other_greater_or_equal("<1", ">=0.1")]
579    #[case::self_less_smaller_other_greater("<1", ">0.1")]
580    #[case::self_less_smaller_other_equal("<1", "=0.1")]
581    #[case::self_less_or_equal_matching_other_less("<=1", "<1")]
582    #[case::self_less_or_equal_matching_other_less_or_equal("<=1", "<=1")]
583    #[case::self_less_or_equal_matching_other_equal("<=1", "=1")]
584    #[case::self_less_or_equal_matching_other_greater_or_equal("<=1", ">=1")]
585    #[case::self_less_or_equal_bigger_other_less("<=1", "<2")]
586    #[case::self_less_or_equal_bigger_other_less_or_equal("<=1", "<=2")]
587    #[case::self_less_or_equal_smaller_other_greater_or_equal("<=1", ">=0.1")]
588    #[case::self_less_or_equal_smaller_other_greater("<=1", ">0.1")]
589    #[case::self_equal_matching_other_less_or_equal("=1", "<=1")]
590    #[case::self_equal_matching_other_equal("=1", "=1")]
591    #[case::self_equal_matching_other_greater_or_equal("=1", ">=1")]
592    #[case::self_greater_or_equal_matching_other_less_or_equal(">=1", "<=1")]
593    #[case::self_greater_or_equal_matching_other_equal(">=1", "=1")]
594    #[case::self_greater_or_equal_matching_other_greater_or_equal(">=1", ">=1")]
595    #[case::self_greater_or_equal_matching_other_greater(">=1", ">1")]
596    #[case::self_greater_or_equal_bigger_other_less(">=1", "<2")]
597    #[case::self_greater_or_equal_bigger_other_less_or_equal(">=1", "<=2")]
598    #[case::self_greater_or_equal_bigger_other_equal(">=1", "=2")]
599    #[case::self_greater_or_equal_bigger_other_greater_or_equal(">=1", ">=2")]
600    #[case::self_greater_or_equal_bigger_other_greater(">=1", ">2")]
601    #[case::self_greater_or_equal_smaller_other_greater_or_equal(">=1", ">=0.1")]
602    #[case::self_greater_or_equal_smaller_other_greater(">=1", ">0.1")]
603    #[case::self_greater_matching_other_greater_or_equal(">1", ">=1")]
604    #[case::self_greater_matching_other_greater(">1", ">1")]
605    #[case::self_greater_bigger_other_less(">1", "<2")]
606    #[case::self_greater_bigger_other_less_or_equal(">1", "<=2")]
607    #[case::self_greater_bigger_other_equal(">1", "=2")]
608    #[case::self_greater_bigger_other_greater_or_equal(">1", ">=2")]
609    #[case::self_greater_bigger_other_greater(">1", ">2")]
610    #[case::self_greater_smaller_other_greater_or_equal(">1", ">=0.1")]
611    #[case::self_greater_smaller_other_greater(">1", ">0.1")]
612    fn version_requirements_form_intersection(
613        #[case] self_requirement: &str,
614        #[case] other_requirement: &str,
615    ) -> TestResult {
616        let self_requirement: VersionRequirement = self_requirement.parse()?;
617        let other_requirement: VersionRequirement = other_requirement.parse()?;
618
619        assert!(self_requirement.is_intersection(&other_requirement));
620
621        Ok(())
622    }
623
624    #[rstest]
625    #[case::self_less_matching_other_equal("<1", "=1")]
626    #[case::self_less_matching_other_greater_or_equal("<1", ">=1")]
627    #[case::self_less_matching_other_greater("<1", ">1")]
628    #[case::self_less_or_equal_matching_other_greater("<=1", ">1")]
629    #[case::self_equal_matching_other_less("=1", "<1")]
630    #[case::self_equal_matching_other_greater("=1", ">1")]
631    #[case::self_equal_bigger_other_less("=1", "<2")]
632    #[case::self_equal_bigger_other_greater("=1", ">2")]
633    #[case::self_equal_smaller_other_less("=1", "<0.1")]
634    #[case::self_equal_smaller_other_greater("=1", ">0.1")]
635    #[case::self_greater_or_equal_matching_other_less(">=1", "<1")]
636    #[case::self_greater_matching_other_less(">1", "<1")]
637    #[case::self_greater_matching_other_less_or_equal(">1", "<=1")]
638    #[case::self_greater_matching_other_equal(">1", "=1")]
639    fn version_requirements_do_not_form_intersection(
640        #[case] self_requirement: &str,
641        #[case] other_requirement: &str,
642    ) -> TestResult {
643        let self_requirement: VersionRequirement = self_requirement.parse()?;
644        let other_requirement: VersionRequirement = other_requirement.parse()?;
645
646        assert!(!self_requirement.is_intersection(&other_requirement));
647        Ok(())
648    }
649}