Skip to main content

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