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}