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