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