Skip to main content

uv_pep440/
version_specifier.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::fmt::Formatter;
4use std::hash::{Hash, Hasher};
5use std::ops::Bound;
6use std::str::FromStr;
7
8use crate::{
9    Operator, OperatorParseError, Version, VersionPattern, VersionPatternParseError, version,
10};
11use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
12#[cfg(feature = "tracing")]
13use tracing::warn;
14
15/// Sorted version specifiers, such as `>=2.1,<3`.
16///
17/// Python requirements can contain multiple version specifier so we need to store them in a list,
18/// such as `>1.2,<2.0` being `[">1.2", "<2.0"]`.
19///
20/// ```rust
21/// # use std::str::FromStr;
22/// # use uv_pep440::{VersionSpecifiers, Version, Operator};
23///
24/// let version = Version::from_str("1.19").unwrap();
25/// let version_specifiers = VersionSpecifiers::from_str(">=1.16, <2.0").unwrap();
26/// assert!(version_specifiers.contains(&version));
27/// // VersionSpecifiers derefs into a list of specifiers
28/// assert_eq!(version_specifiers.iter().position(|specifier| *specifier.operator() == Operator::LessThan), Some(1));
29/// ```
30#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)]
31#[cfg_attr(
32    feature = "rkyv",
33    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
34)]
35#[cfg_attr(feature = "rkyv", rkyv(derive(Debug)))]
36pub struct VersionSpecifiers(Box<[VersionSpecifier]>);
37
38impl std::ops::Deref for VersionSpecifiers {
39    type Target = [VersionSpecifier];
40
41    fn deref(&self) -> &Self::Target {
42        &self.0
43    }
44}
45
46impl VersionSpecifiers {
47    /// Matches all versions.
48    pub fn empty() -> Self {
49        Self(Box::new([]))
50    }
51
52    /// The number of specifiers.
53    pub fn len(&self) -> usize {
54        self.0.len()
55    }
56
57    /// Whether all specifiers match the given version.
58    pub fn contains(&self, version: &Version) -> bool {
59        self.iter().all(|specifier| specifier.contains(version))
60    }
61
62    /// Returns `true` if there are no specifiers.
63    pub fn is_empty(&self) -> bool {
64        self.0.is_empty()
65    }
66
67    /// Sort the specifiers.
68    fn from_unsorted(mut specifiers: Vec<VersionSpecifier>) -> Self {
69        // TODO(konsti): This seems better than sorting on insert and not getting the size hint,
70        // but i haven't measured it.
71        //
72        // Tie-break on the operator so semantically equivalent same-version intervals such as
73        // `>=1.4.4,<=1.4.4` and `<=1.4.4,>=1.4.4` normalize to the same representation.
74        specifiers.sort_by(|a, b| {
75            a.version()
76                .cmp(b.version())
77                .then_with(|| a.operator().cmp(b.operator()))
78        });
79        Self(specifiers.into_boxed_slice())
80    }
81
82    /// Returns the [`VersionSpecifiers`] whose union represents the given range.
83    ///
84    /// This function is not applicable to ranges involving pre-release versions.
85    pub fn from_release_only_bounds<'a>(
86        mut bounds: impl Iterator<Item = (Bound<&'a Version>, Bound<&'a Version>)>,
87    ) -> Self {
88        let mut specifiers = Vec::new();
89
90        let Some((start, mut next)) = bounds.next() else {
91            return Self::empty();
92        };
93
94        // Add specifiers for the holes between the bounds.
95        for (lower, upper) in bounds {
96            let gap_specifiers = match (next, lower) {
97                // Ex) [3.7, 3.8.5), (3.8.5, 3.9] -> >=3.7,!=3.8.5,<=3.9
98                (Bound::Excluded(prev), Bound::Excluded(lower)) if prev == lower => {
99                    vec![VersionSpecifier::not_equals_version(prev.clone())]
100                }
101                // Ex) [3.7, 3.8), (3.8, 3.9] -> >=3.7,!=3.8.*,<=3.9
102                (Bound::Excluded(prev), Bound::Included(lower)) => {
103                    let prev = prev.only_release_trimmed();
104                    let lower = lower.only_release_trimmed();
105                    match (&*prev.release(), &*lower.release()) {
106                        ([major], [lower_major, lower_minor]) if major == lower_major => (0
107                            ..*lower_minor)
108                            .map(|minor| {
109                                VersionSpecifier::not_equals_star_version(Version::new([
110                                    *major, minor,
111                                ]))
112                            })
113                            .collect(),
114                        ([major, minor], [lower_major, lower_minor])
115                            if major == lower_major && minor < lower_minor =>
116                        {
117                            (*minor..*lower_minor)
118                                .map(|minor| {
119                                    VersionSpecifier::not_equals_star_version(Version::new([
120                                        *major, minor,
121                                    ]))
122                                })
123                                .collect()
124                        }
125                        _ => Vec::new(),
126                    }
127                }
128                _ => Vec::new(),
129            };
130            if gap_specifiers.is_empty() {
131                #[cfg(feature = "tracing")]
132                warn!(
133                    "Ignoring unsupported gap in `requires-python` version: {next:?} -> {lower:?}"
134                );
135            } else {
136                specifiers.extend(gap_specifiers);
137            }
138            next = upper;
139        }
140        let end = next;
141
142        // Add the specifiers for the bounding range.
143        specifiers.extend(VersionSpecifier::from_release_only_bounds((start, end)));
144
145        Self::from_unsorted(specifiers)
146    }
147}
148
149impl FromIterator<VersionSpecifier> for VersionSpecifiers {
150    fn from_iter<T: IntoIterator<Item = VersionSpecifier>>(iter: T) -> Self {
151        Self::from_unsorted(iter.into_iter().collect())
152    }
153}
154
155impl IntoIterator for VersionSpecifiers {
156    type Item = VersionSpecifier;
157    type IntoIter = std::vec::IntoIter<VersionSpecifier>;
158
159    fn into_iter(self) -> Self::IntoIter {
160        self.0.into_vec().into_iter()
161    }
162}
163
164impl FromStr for VersionSpecifiers {
165    type Err = VersionSpecifiersParseError;
166
167    fn from_str(s: &str) -> Result<Self, Self::Err> {
168        let separator_count = s.bytes().filter(|byte| *byte == b',').count();
169        if separator_count == 0 {
170            if s.is_empty() {
171                return Ok(Self::empty());
172            }
173            return VersionSpecifier::from_str(s)
174                .map(Self::from)
175                .map_err(|err| VersionSpecifiersParseError {
176                    inner: Box::new(VersionSpecifiersParseErrorInner {
177                        err,
178                        line: s.to_string(),
179                        start: 0,
180                        end: s.len(),
181                    }),
182                });
183        }
184        parse_version_specifiers(s, separator_count + 1).map(Self::from_unsorted)
185    }
186}
187
188impl From<VersionSpecifier> for VersionSpecifiers {
189    fn from(specifier: VersionSpecifier) -> Self {
190        Self(Box::new([specifier]))
191    }
192}
193
194impl std::fmt::Display for VersionSpecifiers {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        for (idx, version_specifier) in self.0.iter().enumerate() {
197            // Separate version specifiers by comma, but we need one comma less than there are
198            // specifiers
199            if idx == 0 {
200                write!(f, "{version_specifier}")?;
201            } else {
202                write!(f, ", {version_specifier}")?;
203            }
204        }
205        Ok(())
206    }
207}
208
209impl Default for VersionSpecifiers {
210    fn default() -> Self {
211        Self::empty()
212    }
213}
214
215impl<'de> Deserialize<'de> for VersionSpecifiers {
216    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
217    where
218        D: Deserializer<'de>,
219    {
220        struct Visitor;
221
222        impl de::Visitor<'_> for Visitor {
223            type Value = VersionSpecifiers;
224
225            fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
226                f.write_str("a string")
227            }
228
229            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
230                VersionSpecifiers::from_str(v).map_err(de::Error::custom)
231            }
232        }
233
234        deserializer.deserialize_str(Visitor)
235    }
236}
237
238impl Serialize for VersionSpecifiers {
239    #[allow(unstable_name_collisions)]
240    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
241    where
242        S: Serializer,
243    {
244        serializer.serialize_str(
245            &self
246                .iter()
247                .map(ToString::to_string)
248                .collect::<Vec<String>>()
249                .join(","),
250        )
251    }
252}
253
254/// Error with span information (unicode width) inside the parsed line
255#[derive(Debug, Eq, PartialEq, Clone)]
256pub struct VersionSpecifiersParseError {
257    // Clippy complains about this error type being too big (at time of
258    // writing, over 150 bytes). That does seem a little big, so we box things.
259    inner: Box<VersionSpecifiersParseErrorInner>,
260}
261
262#[derive(Debug, Eq, PartialEq, Clone)]
263struct VersionSpecifiersParseErrorInner {
264    /// The underlying error that occurred.
265    err: VersionSpecifierParseError,
266    /// The string that failed to parse
267    line: String,
268    /// The starting byte offset into the original string where the error
269    /// occurred.
270    start: usize,
271    /// The ending byte offset into the original string where the error
272    /// occurred.
273    end: usize,
274}
275
276impl std::fmt::Display for VersionSpecifiersParseError {
277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278        use unicode_width::UnicodeWidthStr;
279
280        let VersionSpecifiersParseErrorInner {
281            ref err,
282            ref line,
283            start,
284            end,
285        } = *self.inner;
286        writeln!(f, "Failed to parse version: {err}:")?;
287        writeln!(f, "{line}")?;
288        let indent = line[..start].width();
289        let point = line[start..end].width();
290        writeln!(f, "{}{}", " ".repeat(indent), "^".repeat(point))?;
291        Ok(())
292    }
293}
294
295impl VersionSpecifiersParseError {
296    /// The string that failed to parse
297    pub fn line(&self) -> &String {
298        &self.inner.line
299    }
300}
301
302impl std::error::Error for VersionSpecifiersParseError {}
303
304/// A version range such as `>1.2.3`, `<=4!5.6.7-a8.post9.dev0` or `== 4.1.*`. Parse with
305/// [`VersionSpecifier::from_str`].
306///
307/// ```rust
308/// use std::str::FromStr;
309/// use uv_pep440::{Version, VersionSpecifier};
310///
311/// let version = Version::from_str("1.19").unwrap();
312/// let version_specifier = VersionSpecifier::from_str("== 1.*").unwrap();
313/// assert!(version_specifier.contains(&version));
314/// ```
315///
316/// [`PartialEq`], [`Hash`] and [`Ord`] distinguish `~=` specifiers by their
317/// release segment count, since `~=10.1.0` (`>=10.1.0, <10.2`) and `~=10.1`
318/// (`>=10.1, <11`) match different version sets per PEP 440. For other
319/// operators, trailing zeros are insignificant.
320#[derive(Debug, Clone)]
321#[cfg_attr(
322    feature = "rkyv",
323    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
324)]
325#[cfg_attr(feature = "rkyv", rkyv(derive(Debug)))]
326pub struct VersionSpecifier {
327    /// ~=|==|!=|<=|>=|<|>|===, plus whether the version ended with a star
328    pub(crate) operator: Operator,
329    /// The whole version part behind the operator
330    pub(crate) version: Version,
331}
332
333impl PartialEq for VersionSpecifier {
334    fn eq(&self, other: &Self) -> bool {
335        if self.operator != other.operator {
336            return false;
337        }
338        // `~=` semantics depend on the exact release segment count.
339        if self.operator == Operator::TildeEqual
340            && self.version.release().len() != other.version.release().len()
341        {
342            return false;
343        }
344        self.version == other.version
345    }
346}
347
348impl Eq for VersionSpecifier {}
349
350impl Hash for VersionSpecifier {
351    fn hash<H: Hasher>(&self, state: &mut H) {
352        self.operator.hash(state);
353        // Include the release length for `~=` so that `~=10.1` and `~=10.1.0`
354        // hash differently, matching our `PartialEq`.
355        if self.operator == Operator::TildeEqual {
356            self.version.release().len().hash(state);
357        }
358        self.version.hash(state);
359    }
360}
361
362impl PartialOrd for VersionSpecifier {
363    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
364        Some(self.cmp(other))
365    }
366}
367
368impl Ord for VersionSpecifier {
369    fn cmp(&self, other: &Self) -> Ordering {
370        self.operator
371            .cmp(&other.operator)
372            .then_with(|| self.version.cmp(&other.version))
373            .then_with(|| {
374                // Break `~=` ties on release length to stay consistent with `PartialEq`.
375                if self.operator == Operator::TildeEqual {
376                    self.version
377                        .release()
378                        .len()
379                        .cmp(&other.version.release().len())
380                } else {
381                    Ordering::Equal
382                }
383            })
384    }
385}
386
387impl<'de> Deserialize<'de> for VersionSpecifier {
388    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
389    where
390        D: Deserializer<'de>,
391    {
392        struct Visitor;
393
394        impl de::Visitor<'_> for Visitor {
395            type Value = VersionSpecifier;
396
397            fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
398                f.write_str("a string")
399            }
400
401            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
402                VersionSpecifier::from_str(v).map_err(de::Error::custom)
403            }
404        }
405
406        deserializer.deserialize_str(Visitor)
407    }
408}
409
410/// <https://github.com/serde-rs/serde/issues/1316#issue-332908452>
411impl Serialize for VersionSpecifier {
412    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
413    where
414        S: Serializer,
415    {
416        serializer.collect_str(self)
417    }
418}
419
420impl VersionSpecifier {
421    /// Build from parts, validating that the operator is allowed with that version. The last
422    /// parameter indicates a trailing `.*`, to differentiate between `1.1.*` and `1.1`
423    pub fn from_pattern(
424        operator: Operator,
425        version_pattern: VersionPattern,
426    ) -> Result<Self, VersionSpecifierBuildError> {
427        let star = version_pattern.is_wildcard();
428        let version = version_pattern.into_version();
429
430        // Check if there are star versions and if so, switch operator to star version
431        let operator = if star {
432            match operator.to_star() {
433                Some(starop) => starop,
434                None => {
435                    return Err(BuildErrorKind::OperatorWithStar { operator }.into());
436                }
437            }
438        } else {
439            operator
440        };
441
442        Self::from_version(operator, version)
443    }
444
445    /// Create a new version specifier from an operator and a version.
446    pub fn from_version(
447        operator: Operator,
448        version: Version,
449    ) -> Result<Self, VersionSpecifierBuildError> {
450        // "Local version identifiers are NOT permitted in this version specifier."
451        if version.is_local() && !operator.is_local_compatible() {
452            return Err(BuildErrorKind::OperatorLocalCombo { operator, version }.into());
453        }
454
455        if operator == Operator::TildeEqual && version.release().len() < 2 {
456            return Err(BuildErrorKind::CompatibleRelease.into());
457        }
458
459        Ok(Self { operator, version })
460    }
461
462    /// Remove all non-release parts of the version.
463    ///
464    /// The marker decision diagram relies on the assumption that the negation of a marker tree is
465    /// the complement of the marker space. However, pre-release versions violate this assumption.
466    ///
467    /// For example, the marker `python_full_version > '3.9' or python_full_version <= '3.9'`
468    /// does not match `python_full_version == 3.9.0a0` and so cannot simplify to `true`. However,
469    /// its negation, `python_full_version > '3.9' and python_full_version <= '3.9'`, also does not
470    /// match `3.9.0a0` and simplifies to `false`, which violates the algebra decision diagrams
471    /// rely on. For this reason we ignore pre-release versions entirely when evaluating markers.
472    ///
473    /// Note that `python_version` cannot take on pre-release values as it is truncated to just the
474    /// major and minor version segments. Thus using release-only specifiers is definitely necessary
475    /// for `python_version` to fully simplify any ranges, such as
476    /// `python_version > '3.9' or python_version <= '3.9'`, which is always `true` for
477    /// `python_version`. For `python_full_version` however, this decision is a semantic change.
478    ///
479    /// For Python versions, the major.minor is considered the API version, so unlike the rules
480    /// for package versions in PEP 440, we Python `3.9.0a0` is acceptable for `>= "3.9"`.
481    #[must_use]
482    pub fn only_release(self) -> Self {
483        Self {
484            operator: self.operator,
485            version: self.version.only_release(),
486        }
487    }
488
489    /// Remove all parts of the version beyond the minor segment of the release.
490    #[must_use]
491    pub fn only_minor_release(&self) -> Self {
492        Self {
493            operator: self.operator,
494            version: self.version.only_minor_release(),
495        }
496    }
497
498    /// `==<version>`
499    pub fn equals_version(version: Version) -> Self {
500        Self {
501            operator: Operator::Equal,
502            version,
503        }
504    }
505
506    /// `==<version>.*`
507    pub fn equals_star_version(version: Version) -> Self {
508        Self {
509            operator: Operator::EqualStar,
510            version,
511        }
512    }
513
514    /// `!=<version>.*`
515    pub fn not_equals_star_version(version: Version) -> Self {
516        Self {
517            operator: Operator::NotEqualStar,
518            version,
519        }
520    }
521
522    /// `!=<version>`
523    pub fn not_equals_version(version: Version) -> Self {
524        Self {
525            operator: Operator::NotEqual,
526            version,
527        }
528    }
529
530    /// `>=<version>`
531    pub fn greater_than_equal_version(version: Version) -> Self {
532        Self {
533            operator: Operator::GreaterThanEqual,
534            version,
535        }
536    }
537    /// `><version>`
538    pub fn greater_than_version(version: Version) -> Self {
539        Self {
540            operator: Operator::GreaterThan,
541            version,
542        }
543    }
544
545    /// `<=<version>`
546    pub fn less_than_equal_version(version: Version) -> Self {
547        Self {
548            operator: Operator::LessThanEqual,
549            version,
550        }
551    }
552
553    /// `<<version>`
554    pub fn less_than_version(version: Version) -> Self {
555        Self {
556            operator: Operator::LessThan,
557            version,
558        }
559    }
560
561    /// Get the operator, e.g. `>=` in `>= 2.0.0`
562    pub fn operator(&self) -> &Operator {
563        &self.operator
564    }
565
566    /// Get the version, e.g. `2.0.0` in `<= 2.0.0`
567    pub fn version(&self) -> &Version {
568        &self.version
569    }
570
571    /// Whether the version marker includes a prerelease.
572    pub fn any_prerelease(&self) -> bool {
573        self.version.any_prerelease()
574    }
575
576    /// Returns the version specifiers whose union represents the given range.
577    ///
578    /// This function is not applicable to ranges involving pre-release versions.
579    pub fn from_release_only_bounds(
580        bounds: (Bound<&Version>, Bound<&Version>),
581    ) -> impl Iterator<Item = Self> {
582        let (b1, b2) = match bounds {
583            (Bound::Included(v1), Bound::Included(v2)) if v1 == v2 => {
584                (Some(Self::equals_version(v1.clone())), None)
585            }
586            // `v >= 3.7 && v < 3.8` is equivalent to `v == 3.7.*`
587            (Bound::Included(v1), Bound::Excluded(v2)) => {
588                match *v1.only_release_trimmed().release() {
589                    [major] if *v2.only_release_trimmed().release() == [major, 1] => {
590                        let version = Version::new([major, 0]);
591                        (Some(Self::equals_star_version(version)), None)
592                    }
593                    [major, minor]
594                        if *v2.only_release_trimmed().release() == [major, minor + 1] =>
595                    {
596                        let version = Version::new([major, minor]);
597                        (Some(Self::equals_star_version(version)), None)
598                    }
599                    _ => (
600                        Self::from_lower_bound(Bound::Included(v1)),
601                        Self::from_upper_bound(Bound::Excluded(v2)),
602                    ),
603                }
604            }
605            (lower, upper) => (Self::from_lower_bound(lower), Self::from_upper_bound(upper)),
606        };
607
608        b1.into_iter().chain(b2)
609    }
610
611    /// Returns a version specifier representing the given lower bound.
612    fn from_lower_bound(bound: Bound<&Version>) -> Option<Self> {
613        match bound {
614            Bound::Included(version) => {
615                Some(Self::from_version(Operator::GreaterThanEqual, version.clone()).unwrap())
616            }
617            Bound::Excluded(version) => {
618                Some(Self::from_version(Operator::GreaterThan, version.clone()).unwrap())
619            }
620            Bound::Unbounded => None,
621        }
622    }
623
624    /// Returns a version specifier representing the given upper bound.
625    fn from_upper_bound(bound: Bound<&Version>) -> Option<Self> {
626        match bound {
627            Bound::Included(version) => {
628                Some(Self::from_version(Operator::LessThanEqual, version.clone()).unwrap())
629            }
630            Bound::Excluded(version) => {
631                Some(Self::from_version(Operator::LessThan, version.clone()).unwrap())
632            }
633            Bound::Unbounded => None,
634        }
635    }
636
637    /// Whether the given version satisfies the version range.
638    ///
639    /// For example, `>=1.19,<2.0` contains `1.21`, but not `2.0`.
640    ///
641    /// See:
642    /// - <https://peps.python.org/pep-0440/#version-specifiers>
643    /// - <https://github.com/pypa/packaging/blob/e184feef1a28a5c574ec41f5c263a3a573861f5a/packaging/specifiers.py#L362-L496>
644    pub fn contains(&self, version: &Version) -> bool {
645        // "Except where specifically noted below, local version identifiers MUST NOT be permitted
646        // in version specifiers, and local version labels MUST be ignored entirely when checking
647        // if candidate versions match a given version specifier."
648        let this = self.version();
649        let other = if this.local().is_empty() && !version.local().is_empty() {
650            Cow::Owned(version.clone().without_local())
651        } else {
652            Cow::Borrowed(version)
653        };
654
655        match self.operator {
656            Operator::Equal => other.as_ref() == this,
657            Operator::EqualStar => {
658                this.epoch() == other.epoch()
659                    && self
660                        .version
661                        .release()
662                        .iter()
663                        // Pad the version with zeros if it's shorter than the specifier
664                        // prefix, e.g., version "2" (== "2.0") should NOT match "==2.1.*"
665                        // because 2.0 != 2.1.
666                        .zip(other.release().iter().chain(std::iter::repeat(&0)))
667                        .all(|(this, other)| this == other)
668            }
669            #[allow(deprecated)]
670            Operator::ExactEqual => {
671                #[cfg(feature = "tracing")]
672                {
673                    warn!("Using arbitrary equality (`===`) is discouraged");
674                }
675                self.version.to_string() == version.to_string()
676            }
677            Operator::NotEqual => this != other.as_ref(),
678            Operator::NotEqualStar => {
679                this.epoch() != other.epoch()
680                    || !this
681                        .release()
682                        .iter()
683                        // Pad the version with zeros if it's shorter than the specifier
684                        // prefix, e.g., version "2" (== "2.0") should match "!=2.1.*"
685                        // because 2.0 != 2.1.
686                        .zip(other.release().iter().chain(std::iter::repeat(&0)))
687                        .all(|(this, other)| this == other)
688            }
689            Operator::TildeEqual => {
690                // "For a given release identifier V.N, the compatible release clause is
691                // approximately equivalent to the pair of comparison clauses: `>= V.N, == V.*`"
692                // First, we test that every but the last digit matches.
693                // We know that this must hold true since we checked it in the constructor
694                assert!(this.release().len() > 1);
695                if this.epoch() != other.epoch() {
696                    return false;
697                }
698
699                if !this.release()[..this.release().len() - 1]
700                    .iter()
701                    .zip(&*other.release())
702                    .all(|(this, other)| this == other)
703                {
704                    return false;
705                }
706
707                // According to PEP 440, this ignores the pre-release special rules
708                // pypa/packaging disagrees: https://github.com/pypa/packaging/issues/617
709                other.as_ref() >= this
710            }
711            Operator::GreaterThan => {
712                if other.epoch() > this.epoch() {
713                    return true;
714                }
715
716                if version::compare_release(&this.release(), &other.release()) == Ordering::Equal {
717                    // This special case is here so that, unless the specifier itself
718                    // includes is a post-release version, that we do not accept
719                    // post-release versions for the version mentioned in the specifier
720                    // (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
721                    if !this.is_post() && other.is_post() {
722                        return false;
723                    }
724
725                    // We already checked that self doesn't have a local version
726                    if other.is_local() {
727                        return false;
728                    }
729                }
730
731                other.as_ref() > this
732            }
733            Operator::GreaterThanEqual => other.as_ref() >= this,
734            Operator::LessThan => {
735                if other.epoch() < this.epoch() {
736                    return true;
737                }
738
739                // The exclusive ordered comparison `<V` must not allow a pre-release of the
740                // specified version unless the specified version is itself a pre-release. The
741                // earliest pre-release of a post-release retains its post component, so
742                // `<1.0.post1` excludes `1.0.post1.dev0`, but includes `1.0a1`.
743                if !this.any_prerelease()
744                    && other.any_prerelease()
745                    && other.as_ref() >= &this.clone().with_dev(Some(0))
746                {
747                    return false;
748                }
749
750                other.as_ref() < this
751            }
752            Operator::LessThanEqual => other.as_ref() <= this,
753        }
754    }
755
756    /// Whether this version specifier rejects versions below a lower cutoff.
757    pub fn has_lower_bound(&self) -> bool {
758        match self.operator() {
759            Operator::Equal
760            | Operator::EqualStar
761            | Operator::ExactEqual
762            | Operator::TildeEqual
763            | Operator::GreaterThan
764            | Operator::GreaterThanEqual => true,
765            Operator::LessThanEqual
766            | Operator::LessThan
767            | Operator::NotEqualStar
768            | Operator::NotEqual => false,
769        }
770    }
771}
772
773impl FromStr for VersionSpecifier {
774    type Err = VersionSpecifierParseError;
775
776    /// Parses a version such as `>= 1.19`, `== 1.1.*`,`~=1.0+abc.5` or `<=1!2012.2`
777    fn from_str(spec: &str) -> Result<Self, Self::Err> {
778        let mut s = unscanny::Scanner::new(spec);
779        s.eat_while(|c: char| c.is_whitespace());
780        // operator but we don't know yet if it has a star
781        let operator = s.eat_while(['=', '!', '~', '<', '>']);
782        if operator.is_empty() {
783            // Attempt to parse the version from the rest of the scanner to provide a more useful error message in MissingOperator.
784            // If it is not able to be parsed (i.e. not a valid version), it will just be None and no additional info will be added to the error message.
785            s.eat_while(|c: char| c.is_whitespace());
786            let version = s.eat_while(|c: char| !c.is_whitespace());
787            s.eat_while(|c: char| c.is_whitespace());
788            return Err(ParseErrorKind::MissingOperator(VersionOperatorBuildError {
789                version_pattern: VersionPattern::from_str(version).ok(),
790            })
791            .into());
792        }
793        let operator = Operator::from_str(operator).map_err(ParseErrorKind::InvalidOperator)?;
794        s.eat_while(|c: char| c.is_whitespace());
795        let version = s.eat_while(|c: char| !c.is_whitespace());
796        if version.is_empty() {
797            return Err(ParseErrorKind::MissingVersion.into());
798        }
799        let vpat = version.parse().map_err(ParseErrorKind::InvalidVersion)?;
800        let version_specifier =
801            Self::from_pattern(operator, vpat).map_err(ParseErrorKind::InvalidSpecifier)?;
802        s.eat_while(|c: char| c.is_whitespace());
803        if !s.done() {
804            return Err(ParseErrorKind::InvalidTrailing(s.after().to_string()).into());
805        }
806        Ok(version_specifier)
807    }
808}
809
810impl std::fmt::Display for VersionSpecifier {
811    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
812        if self.operator == Operator::EqualStar || self.operator == Operator::NotEqualStar {
813            return write!(f, "{}{}.*", self.operator, self.version);
814        }
815        write!(f, "{}{}", self.operator, self.version)
816    }
817}
818
819/// An error that can occur when constructing a version specifier.
820#[derive(Clone, Debug, Eq, PartialEq)]
821pub struct VersionSpecifierBuildError {
822    // We box to shrink the error type's size. This in turn keeps Result<T, E>
823    // smaller and should lead to overall better codegen.
824    kind: Box<BuildErrorKind>,
825}
826
827impl std::error::Error for VersionSpecifierBuildError {}
828
829impl std::fmt::Display for VersionSpecifierBuildError {
830    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
831        match *self.kind {
832            BuildErrorKind::OperatorLocalCombo {
833                operator: ref op,
834                ref version,
835            } => {
836                let local = version.local();
837                write!(
838                    f,
839                    "Operator {op} is incompatible with versions \
840                     containing non-empty local segments (`+{local}`)",
841                )
842            }
843            BuildErrorKind::OperatorWithStar { operator: ref op } => {
844                write!(
845                    f,
846                    "Operator {op} cannot be used with a wildcard version specifier",
847                )
848            }
849            BuildErrorKind::CompatibleRelease => {
850                write!(
851                    f,
852                    "The ~= operator requires at least two segments in the release version"
853                )
854            }
855        }
856    }
857}
858
859#[derive(Clone, Debug, Eq, PartialEq)]
860struct VersionOperatorBuildError {
861    version_pattern: Option<VersionPattern>,
862}
863
864impl std::error::Error for VersionOperatorBuildError {}
865
866impl std::fmt::Display for VersionOperatorBuildError {
867    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
868        write!(f, "Unexpected end of version specifier, expected operator")?;
869        if let Some(version_pattern) = &self.version_pattern {
870            let version_specifier =
871                VersionSpecifier::from_pattern(Operator::Equal, version_pattern.clone()).unwrap();
872            write!(f, ". Did you mean `{version_specifier}`?")?;
873        }
874        Ok(())
875    }
876}
877
878/// The specific kind of error that can occur when building a version specifier
879/// from an operator and version pair.
880#[derive(Clone, Debug, Eq, PartialEq)]
881enum BuildErrorKind {
882    /// Occurs when one attempts to build a version specifier with
883    /// a version containing a non-empty local segment with and an
884    /// incompatible operator.
885    OperatorLocalCombo {
886        /// The operator given.
887        operator: Operator,
888        /// The version given.
889        version: Version,
890    },
891    /// Occurs when a version specifier contains a wildcard, but is used with
892    /// an incompatible operator.
893    OperatorWithStar {
894        /// The operator given.
895        operator: Operator,
896    },
897    /// Occurs when the compatible release operator (`~=`) is used with a
898    /// version that has fewer than 2 segments in its release version.
899    CompatibleRelease,
900}
901
902impl From<BuildErrorKind> for VersionSpecifierBuildError {
903    fn from(kind: BuildErrorKind) -> Self {
904        Self {
905            kind: Box::new(kind),
906        }
907    }
908}
909
910/// An error that can occur when parsing or constructing a version specifier.
911#[derive(Clone, Debug, Eq, PartialEq)]
912pub struct VersionSpecifierParseError {
913    // We box to shrink the error type's size. This in turn keeps Result<T, E>
914    // smaller and should lead to overall better codegen.
915    kind: Box<ParseErrorKind>,
916}
917
918impl std::error::Error for VersionSpecifierParseError {}
919
920impl std::fmt::Display for VersionSpecifierParseError {
921    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
922        // Note that even though we have nested error types here, since we
923        // don't expose them through std::error::Error::source, we emit them
924        // as part of the error message here. This makes the error a bit
925        // more self-contained. And it's not clear how useful it is exposing
926        // internal errors.
927        match *self.kind {
928            ParseErrorKind::InvalidOperator(ref err) => err.fmt(f),
929            ParseErrorKind::InvalidVersion(ref err) => err.fmt(f),
930            ParseErrorKind::InvalidSpecifier(ref err) => err.fmt(f),
931            ParseErrorKind::MissingOperator(ref err) => err.fmt(f),
932            ParseErrorKind::MissingVersion => {
933                write!(f, "Unexpected end of version specifier, expected version")
934            }
935            ParseErrorKind::InvalidTrailing(ref trail) => {
936                write!(f, "Trailing `{trail}` is not allowed")
937            }
938        }
939    }
940}
941
942/// The specific kind of error that occurs when parsing a single version
943/// specifier from a string.
944#[derive(Clone, Debug, Eq, PartialEq)]
945enum ParseErrorKind {
946    InvalidOperator(OperatorParseError),
947    InvalidVersion(VersionPatternParseError),
948    InvalidSpecifier(VersionSpecifierBuildError),
949    MissingOperator(VersionOperatorBuildError),
950    MissingVersion,
951    InvalidTrailing(String),
952}
953
954impl From<ParseErrorKind> for VersionSpecifierParseError {
955    fn from(kind: ParseErrorKind) -> Self {
956        Self {
957            kind: Box::new(kind),
958        }
959    }
960}
961
962/// Parse a list of specifiers such as `>= 1.0, != 1.3.*, < 2.0`.
963fn parse_version_specifiers(
964    spec: &str,
965    specifier_count: usize,
966) -> Result<Vec<VersionSpecifier>, VersionSpecifiersParseError> {
967    let mut version_ranges = Vec::with_capacity(specifier_count);
968    let mut start: usize = 0;
969    let separator = ",";
970    for version_range_spec in spec.split(separator) {
971        match VersionSpecifier::from_str(version_range_spec) {
972            Err(err) => {
973                return Err(VersionSpecifiersParseError {
974                    inner: Box::new(VersionSpecifiersParseErrorInner {
975                        err,
976                        line: spec.to_string(),
977                        start,
978                        end: start + version_range_spec.len(),
979                    }),
980                });
981            }
982            Ok(version_range) => {
983                version_ranges.push(version_range);
984            }
985        }
986        start += version_range_spec.len();
987        start += separator.len();
988    }
989    Ok(version_ranges)
990}
991
992/// A simple `~=` version specifier with a major, minor and (optional) patch version, e.g., `~=3.13`
993/// or `~=3.13.0`.
994#[derive(Clone, Debug)]
995pub struct TildeVersionSpecifier<'a> {
996    inner: Cow<'a, VersionSpecifier>,
997}
998
999impl<'a> TildeVersionSpecifier<'a> {
1000    /// Create a new [`TildeVersionSpecifier`] from a [`VersionSpecifier`] value.
1001    ///
1002    /// If a [`Operator::TildeEqual`] is not used, or the version includes more than minor and patch
1003    /// segments, this will return [`None`].
1004    fn from_specifier(specifier: VersionSpecifier) -> Option<Self> {
1005        TildeVersionSpecifier::new(Cow::Owned(specifier))
1006    }
1007
1008    /// Create a new [`TildeVersionSpecifier`] from a [`VersionSpecifier`] reference.
1009    ///
1010    /// See [`TildeVersionSpecifier::from_specifier`].
1011    pub fn from_specifier_ref(specifier: &'a VersionSpecifier) -> Option<Self> {
1012        TildeVersionSpecifier::new(Cow::Borrowed(specifier))
1013    }
1014
1015    fn new(specifier: Cow<'a, VersionSpecifier>) -> Option<Self> {
1016        if specifier.operator != Operator::TildeEqual {
1017            return None;
1018        }
1019        if specifier.version().release().len() < 2 || specifier.version().release().len() > 3 {
1020            return None;
1021        }
1022        if specifier.version().any_prerelease()
1023            || specifier.version().is_local()
1024            || specifier.version().is_post()
1025        {
1026            return None;
1027        }
1028        Some(Self { inner: specifier })
1029    }
1030
1031    /// Whether a patch version is present in this tilde version specifier.
1032    pub fn has_patch(&self) -> bool {
1033        self.inner.version.release().len() == 3
1034    }
1035
1036    /// Construct the lower and upper bounding version specifiers for this tilde version specifier,
1037    /// e.g., for `~=3.13` this would return `>=3.13` and `<4` and for `~=3.13.0` it would
1038    /// return `>=3.13.0` and `<3.14`.
1039    pub fn bounding_specifiers(&self) -> (VersionSpecifier, VersionSpecifier) {
1040        let release = self.inner.version().release();
1041        let lower = self.inner.version.clone();
1042        let upper = if self.has_patch() {
1043            Version::new([release[0], release[1] + 1])
1044        } else {
1045            Version::new([release[0] + 1])
1046        };
1047        (
1048            VersionSpecifier::greater_than_equal_version(lower),
1049            VersionSpecifier::less_than_version(upper),
1050        )
1051    }
1052
1053    /// Construct a new tilde `VersionSpecifier` with the given patch version appended.
1054    pub fn with_patch_version(&self, patch: u64) -> TildeVersionSpecifier<'_> {
1055        let mut release = self.inner.version.release().to_vec();
1056        if self.has_patch() {
1057            release.pop();
1058        }
1059        release.push(patch);
1060        TildeVersionSpecifier::from_specifier(
1061            VersionSpecifier::from_version(Operator::TildeEqual, Version::new(release))
1062                .expect("We should always derive a valid new version specifier"),
1063        )
1064        .expect("We should always derive a new tilde version specifier")
1065    }
1066}
1067
1068impl std::fmt::Display for TildeVersionSpecifier<'_> {
1069    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1070        write!(f, "{}", self.inner)
1071    }
1072}
1073
1074#[cfg(test)]
1075mod tests {
1076    use std::{cmp::Ordering, str::FromStr};
1077
1078    use indoc::indoc;
1079
1080    use crate::LocalSegment;
1081
1082    use super::*;
1083
1084    /// <https://peps.python.org/pep-0440/#version-matching>
1085    #[test]
1086    fn test_equal() {
1087        let version = Version::from_str("1.1.post1").unwrap();
1088
1089        assert!(
1090            !VersionSpecifier::from_str("== 1.1")
1091                .unwrap()
1092                .contains(&version)
1093        );
1094        assert!(
1095            VersionSpecifier::from_str("== 1.1.post1")
1096                .unwrap()
1097                .contains(&version)
1098        );
1099        assert!(
1100            VersionSpecifier::from_str("== 1.1.*")
1101                .unwrap()
1102                .contains(&version)
1103        );
1104    }
1105
1106    /// Test the `<V.postN` cases corrected by `packaging` 26.1.
1107    ///
1108    /// See: <https://github.com/pypa/packaging/pull/1140>
1109    #[test]
1110    fn test_less_than_post_release() {
1111        for (specifier, candidate, expected) in [
1112            ("<1.0.post1", "1.0a1", true),
1113            ("<1.0.post1", "1.0.post0.dev0", true),
1114            ("<1.0.post1", "1.0.post1.dev0", false),
1115            ("<1!1.0.post1", "1!1.0a1", true),
1116        ] {
1117            assert_eq!(
1118                VersionSpecifier::from_str(specifier)
1119                    .unwrap()
1120                    .contains(&Version::from_str(candidate).unwrap()),
1121                expected,
1122                "expected `{specifier}` to contain `{candidate}`: {expected}"
1123            );
1124        }
1125    }
1126
1127    const VERSIONS_ALL: &[&str] = &[
1128        // Implicit epoch of 0
1129        "1.0.dev456",
1130        "1.0a1",
1131        "1.0a2.dev456",
1132        "1.0a12.dev456",
1133        "1.0a12",
1134        "1.0b1.dev456",
1135        "1.0b2",
1136        "1.0b2.post345.dev456",
1137        "1.0b2.post345",
1138        "1.0b2-346",
1139        "1.0c1.dev456",
1140        "1.0c1",
1141        "1.0rc2",
1142        "1.0c3",
1143        "1.0",
1144        "1.0.post456.dev34",
1145        "1.0.post456",
1146        "1.1.dev1",
1147        "1.2+123abc",
1148        "1.2+123abc456",
1149        "1.2+abc",
1150        "1.2+abc123",
1151        "1.2+abc123def",
1152        "1.2+1234.abc",
1153        "1.2+123456",
1154        "1.2.r32+123456",
1155        "1.2.rev33+123456",
1156        // Explicit epoch of 1
1157        "1!1.0.dev456",
1158        "1!1.0a1",
1159        "1!1.0a2.dev456",
1160        "1!1.0a12.dev456",
1161        "1!1.0a12",
1162        "1!1.0b1.dev456",
1163        "1!1.0b2",
1164        "1!1.0b2.post345.dev456",
1165        "1!1.0b2.post345",
1166        "1!1.0b2-346",
1167        "1!1.0c1.dev456",
1168        "1!1.0c1",
1169        "1!1.0rc2",
1170        "1!1.0c3",
1171        "1!1.0",
1172        "1!1.0.post456.dev34",
1173        "1!1.0.post456",
1174        "1!1.1.dev1",
1175        "1!1.2+123abc",
1176        "1!1.2+123abc456",
1177        "1!1.2+abc",
1178        "1!1.2+abc123",
1179        "1!1.2+abc123def",
1180        "1!1.2+1234.abc",
1181        "1!1.2+123456",
1182        "1!1.2.r32+123456",
1183        "1!1.2.rev33+123456",
1184    ];
1185
1186    /// <https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L666-L707>
1187    /// <https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L709-L750>
1188    ///
1189    /// These tests are a lot shorter than the pypa/packaging version since we implement all
1190    /// comparisons through one method
1191    #[test]
1192    fn test_operators_true() {
1193        let versions: Vec<Version> = VERSIONS_ALL
1194            .iter()
1195            .map(|version| Version::from_str(version).unwrap())
1196            .collect();
1197
1198        // Below we'll generate every possible combination of VERSIONS_ALL that
1199        // should be true for the given operator
1200        let operations = [
1201            // Verify that the less than (<) operator works correctly
1202            versions
1203                .iter()
1204                .enumerate()
1205                .flat_map(|(i, x)| {
1206                    versions[i + 1..]
1207                        .iter()
1208                        .map(move |y| (x, y, Ordering::Less))
1209                })
1210                .collect::<Vec<_>>(),
1211            // Verify that the equal (==) operator works correctly
1212            versions
1213                .iter()
1214                .map(move |x| (x, x, Ordering::Equal))
1215                .collect::<Vec<_>>(),
1216            // Verify that the greater than (>) operator works correctly
1217            versions
1218                .iter()
1219                .enumerate()
1220                .flat_map(|(i, x)| versions[..i].iter().map(move |y| (x, y, Ordering::Greater)))
1221                .collect::<Vec<_>>(),
1222        ]
1223        .into_iter()
1224        .flatten();
1225
1226        for (a, b, ordering) in operations {
1227            assert_eq!(a.cmp(b), ordering, "{a} {ordering:?} {b}");
1228        }
1229    }
1230
1231    const VERSIONS_0: &[&str] = &[
1232        "1.0.dev456",
1233        "1.0a1",
1234        "1.0a2.dev456",
1235        "1.0a12.dev456",
1236        "1.0a12",
1237        "1.0b1.dev456",
1238        "1.0b2",
1239        "1.0b2.post345.dev456",
1240        "1.0b2.post345",
1241        "1.0b2-346",
1242        "1.0c1.dev456",
1243        "1.0c1",
1244        "1.0rc2",
1245        "1.0c3",
1246        "1.0",
1247        "1.0.post456.dev34",
1248        "1.0.post456",
1249        "1.1.dev1",
1250        "1.2+123abc",
1251        "1.2+123abc456",
1252        "1.2+abc",
1253        "1.2+abc123",
1254        "1.2+abc123def",
1255        "1.2+1234.abc",
1256        "1.2+123456",
1257        "1.2.r32+123456",
1258        "1.2.rev33+123456",
1259    ];
1260
1261    const SPECIFIERS_OTHER: &[&str] = &[
1262        "== 1.*", "== 1.0.*", "== 1.1.*", "== 1.2.*", "== 2.*", "~= 1.0", "~= 1.0b1", "~= 1.1",
1263        "~= 1.2", "~= 2.0",
1264    ];
1265
1266    const EXPECTED_OTHER: &[[bool; 10]] = &[
1267        [
1268            true, true, false, false, false, false, false, false, false, false,
1269        ],
1270        [
1271            true, true, false, false, false, false, false, false, false, false,
1272        ],
1273        [
1274            true, true, false, false, false, false, false, false, false, false,
1275        ],
1276        [
1277            true, true, false, false, false, false, false, false, false, false,
1278        ],
1279        [
1280            true, true, false, false, false, false, false, false, false, false,
1281        ],
1282        [
1283            true, true, false, false, false, false, false, false, false, false,
1284        ],
1285        [
1286            true, true, false, false, false, false, true, false, false, false,
1287        ],
1288        [
1289            true, true, false, false, false, false, true, false, false, false,
1290        ],
1291        [
1292            true, true, false, false, false, false, true, false, false, false,
1293        ],
1294        [
1295            true, true, false, false, false, false, true, false, false, false,
1296        ],
1297        [
1298            true, true, false, false, false, false, true, false, false, false,
1299        ],
1300        [
1301            true, true, false, false, false, false, true, false, false, false,
1302        ],
1303        [
1304            true, true, false, false, false, false, true, false, false, false,
1305        ],
1306        [
1307            true, true, false, false, false, false, true, false, false, false,
1308        ],
1309        [
1310            true, true, false, false, false, true, true, false, false, false,
1311        ],
1312        [
1313            true, true, false, false, false, true, true, false, false, false,
1314        ],
1315        [
1316            true, true, false, false, false, true, true, false, false, false,
1317        ],
1318        [
1319            true, false, true, false, false, true, true, false, false, false,
1320        ],
1321        [
1322            true, false, false, true, false, true, true, true, true, false,
1323        ],
1324        [
1325            true, false, false, true, false, true, true, true, true, false,
1326        ],
1327        [
1328            true, false, false, true, false, true, true, true, true, false,
1329        ],
1330        [
1331            true, false, false, true, false, true, true, true, true, false,
1332        ],
1333        [
1334            true, false, false, true, false, true, true, true, true, false,
1335        ],
1336        [
1337            true, false, false, true, false, true, true, true, true, false,
1338        ],
1339        [
1340            true, false, false, true, false, true, true, true, true, false,
1341        ],
1342        [
1343            true, false, false, true, false, true, true, true, true, false,
1344        ],
1345        [
1346            true, false, false, true, false, true, true, true, true, false,
1347        ],
1348    ];
1349
1350    /// Test for tilde equal (~=) and star equal (== x.y.*) recorded from pypa/packaging
1351    ///
1352    /// Well, except for <https://github.com/pypa/packaging/issues/617>
1353    #[test]
1354    fn test_operators_other() {
1355        let versions = VERSIONS_0
1356            .iter()
1357            .map(|version| Version::from_str(version).unwrap());
1358        let specifiers: Vec<_> = SPECIFIERS_OTHER
1359            .iter()
1360            .map(|specifier| VersionSpecifier::from_str(specifier).unwrap())
1361            .collect();
1362
1363        for (version, expected) in versions.zip(EXPECTED_OTHER) {
1364            let actual = specifiers
1365                .iter()
1366                .map(|specifier| specifier.contains(&version));
1367            for ((actual, expected), _specifier) in actual.zip(expected).zip(SPECIFIERS_OTHER) {
1368                assert_eq!(actual, *expected);
1369            }
1370        }
1371    }
1372
1373    #[test]
1374    fn test_arbitrary_equality() {
1375        assert!(
1376            VersionSpecifier::from_str("=== 1.2a1")
1377                .unwrap()
1378                .contains(&Version::from_str("1.2a1").unwrap())
1379        );
1380        assert!(
1381            !VersionSpecifier::from_str("=== 1.2a1")
1382                .unwrap()
1383                .contains(&Version::from_str("1.2a1+local").unwrap())
1384        );
1385    }
1386
1387    #[test]
1388    fn test_equal_star_short_version_bug() {
1389        // Version "2" (equivalent to 2.0) should NOT match "==2.1.*"
1390        let specifier = VersionSpecifier::from_str("==2.1.*").unwrap();
1391        let version = Version::from_str("2").unwrap();
1392        assert!(
1393            !specifier.contains(&version),
1394            "Bug: version '2' incorrectly matches '==2.1.*'"
1395        );
1396
1397        // Version "2" (equivalent to 2.0) SHOULD match "!=2.1.*"
1398        let specifier = VersionSpecifier::from_str("!=2.1.*").unwrap();
1399        let version = Version::from_str("2").unwrap();
1400        assert!(
1401            specifier.contains(&version),
1402            "Bug: version '2' should match '!=2.1.*' (2.0 is not in 2.1 family)"
1403        );
1404
1405        // Verify existing behavior still works: "2" matches "==2.0.*"
1406        let specifier = VersionSpecifier::from_str("==2.0.*").unwrap();
1407        let version = Version::from_str("2").unwrap();
1408        assert!(
1409            specifier.contains(&version),
1410            "version '2' should match '==2.0.*'"
1411        );
1412
1413        // And "2" should NOT match "!=2.0.*"
1414        let specifier = VersionSpecifier::from_str("!=2.0.*").unwrap();
1415        let version = Version::from_str("2").unwrap();
1416        assert!(
1417            !specifier.contains(&version),
1418            "version '2' should not match '!=2.0.*'"
1419        );
1420
1421        // Local versions: local segment should be ignored for prefix matching.
1422        // "2+local" (== "2.0") should NOT match "==2.1.*"
1423        let specifier = VersionSpecifier::from_str("==2.1.*").unwrap();
1424        let version = Version::from_str("2+local").unwrap();
1425        assert!(
1426            !specifier.contains(&version),
1427            "version '2+local' should not match '==2.1.*'"
1428        );
1429
1430        // "2+local" (== "2.0") SHOULD match "!=2.1.*"
1431        let specifier = VersionSpecifier::from_str("!=2.1.*").unwrap();
1432        let version = Version::from_str("2+local").unwrap();
1433        assert!(
1434            specifier.contains(&version),
1435            "version '2+local' should match '!=2.1.*'"
1436        );
1437    }
1438
1439    #[test]
1440    fn test_specifiers_true() {
1441        let pairs = [
1442            // Test the equality operation
1443            ("2.0", "==2"),
1444            ("2.0", "==2.0"),
1445            ("2.0", "==2.0.0"),
1446            ("2.0+deadbeef", "==2"),
1447            ("2.0+deadbeef", "==2.0"),
1448            ("2.0+deadbeef", "==2.0.0"),
1449            ("2.0+deadbeef", "==2+deadbeef"),
1450            ("2.0+deadbeef", "==2.0+deadbeef"),
1451            ("2.0+deadbeef", "==2.0.0+deadbeef"),
1452            ("2.0+deadbeef.0", "==2.0.0+deadbeef.00"),
1453            // Test the equality operation with a prefix
1454            ("2.dev1", "==2.*"),
1455            ("2a1", "==2.*"),
1456            ("2a1.post1", "==2.*"),
1457            ("2b1", "==2.*"),
1458            ("2b1.dev1", "==2.*"),
1459            ("2c1", "==2.*"),
1460            ("2c1.post1.dev1", "==2.*"),
1461            ("2c1.post1.dev1", "==2.0.*"),
1462            ("2rc1", "==2.*"),
1463            ("2rc1", "==2.0.*"),
1464            ("2", "==2.*"),
1465            ("2", "==2.0.*"),
1466            ("2", "==0!2.*"),
1467            ("0!2", "==2.*"),
1468            ("2.0", "==2.*"),
1469            ("2.0.0", "==2.*"),
1470            ("2.1+local.version", "==2.1.*"),
1471            // Test the in-equality operation
1472            ("2.1", "!=2"),
1473            ("2.1", "!=2.0"),
1474            ("2.0.1", "!=2"),
1475            ("2.0.1", "!=2.0"),
1476            ("2.0.1", "!=2.0.0"),
1477            ("2.0", "!=2.0+deadbeef"),
1478            // Test the in-equality operation with a prefix
1479            ("2.0", "!=3.*"),
1480            ("2.1", "!=2.0.*"),
1481            // Test the greater than equal operation
1482            ("2.0", ">=2"),
1483            ("2.0", ">=2.0"),
1484            ("2.0", ">=2.0.0"),
1485            ("2.0.post1", ">=2"),
1486            ("2.0.post1.dev1", ">=2"),
1487            ("3", ">=2"),
1488            // Test the less than equal operation
1489            ("2.0", "<=2"),
1490            ("2.0", "<=2.0"),
1491            ("2.0", "<=2.0.0"),
1492            ("2.0.dev1", "<=2"),
1493            ("2.0a1", "<=2"),
1494            ("2.0a1.dev1", "<=2"),
1495            ("2.0b1", "<=2"),
1496            ("2.0b1.post1", "<=2"),
1497            ("2.0c1", "<=2"),
1498            ("2.0c1.post1.dev1", "<=2"),
1499            ("2.0rc1", "<=2"),
1500            ("1", "<=2"),
1501            // Test the greater than operation
1502            ("3", ">2"),
1503            ("2.1", ">2.0"),
1504            ("2.0.1", ">2"),
1505            ("2.1.post1", ">2"),
1506            ("2.1+local.version", ">2"),
1507            ("2.post2", ">2.post1"),
1508            // Test the less than operation
1509            ("1", "<2"),
1510            ("2.0", "<2.1"),
1511            ("2.0.dev0", "<2.1"),
1512            // https://github.com/astral-sh/uv/issues/12834
1513            ("0.1a1", "<0.1a2"),
1514            ("0.1dev1", "<0.1dev2"),
1515            ("0.1dev1", "<0.1a1"),
1516            // Test the compatibility operation
1517            ("1", "~=1.0"),
1518            ("1.0.1", "~=1.0"),
1519            ("1.1", "~=1.0"),
1520            ("1.9999999", "~=1.0"),
1521            ("1.1", "~=1.0a1"),
1522            ("2022.01.01", "~=2022.01.01"),
1523            // Test that epochs are handled sanely
1524            ("2!1.0", "~=2!1.0"),
1525            ("2!1.0", "==2!1.*"),
1526            ("2!1.0", "==2!1.0"),
1527            ("2!1.0", "!=1.0"),
1528            ("1.0", "!=2!1.0"),
1529            ("1.0", "<=2!0.1"),
1530            ("2!1.0", ">=2.0"),
1531            ("1.0", "<2!0.1"),
1532            ("2!1.0", ">2.0"),
1533            // Test some normalization rules
1534            ("2.0.5", ">2.0dev"),
1535        ];
1536
1537        for (s_version, s_spec) in pairs {
1538            let version = s_version.parse::<Version>().unwrap();
1539            let spec = s_spec.parse::<VersionSpecifier>().unwrap();
1540            assert!(
1541                spec.contains(&version),
1542                "{s_version} {s_spec}\nversion repr: {:?}\nspec version repr: {:?}",
1543                version.as_bloated_debug(),
1544                spec.version.as_bloated_debug(),
1545            );
1546        }
1547    }
1548
1549    #[test]
1550    fn test_specifier_false() {
1551        let pairs = [
1552            // Test the equality operation
1553            ("2.1", "==2"),
1554            ("2.1", "==2.0"),
1555            ("2.1", "==2.0.0"),
1556            ("2.0", "==2.0+deadbeef"),
1557            // Test the equality operation with a prefix
1558            ("2.0", "==3.*"),
1559            ("2.1", "==2.0.*"),
1560            // Test the in-equality operation
1561            ("2.0", "!=2"),
1562            ("2.0", "!=2.0"),
1563            ("2.0", "!=2.0.0"),
1564            ("2.0+deadbeef", "!=2"),
1565            ("2.0+deadbeef", "!=2.0"),
1566            ("2.0+deadbeef", "!=2.0.0"),
1567            ("2.0+deadbeef", "!=2+deadbeef"),
1568            ("2.0+deadbeef", "!=2.0+deadbeef"),
1569            ("2.0+deadbeef", "!=2.0.0+deadbeef"),
1570            ("2.0+deadbeef.0", "!=2.0.0+deadbeef.00"),
1571            // Test the in-equality operation with a prefix
1572            ("2.dev1", "!=2.*"),
1573            ("2a1", "!=2.*"),
1574            ("2a1.post1", "!=2.*"),
1575            ("2b1", "!=2.*"),
1576            ("2b1.dev1", "!=2.*"),
1577            ("2c1", "!=2.*"),
1578            ("2c1.post1.dev1", "!=2.*"),
1579            ("2c1.post1.dev1", "!=2.0.*"),
1580            ("2rc1", "!=2.*"),
1581            ("2rc1", "!=2.0.*"),
1582            ("2", "!=2.*"),
1583            ("2", "!=2.0.*"),
1584            ("2.0", "!=2.*"),
1585            ("2.0.0", "!=2.*"),
1586            // Test the greater than equal operation
1587            ("2.0.dev1", ">=2"),
1588            ("2.0a1", ">=2"),
1589            ("2.0a1.dev1", ">=2"),
1590            ("2.0b1", ">=2"),
1591            ("2.0b1.post1", ">=2"),
1592            ("2.0c1", ">=2"),
1593            ("2.0c1.post1.dev1", ">=2"),
1594            ("2.0rc1", ">=2"),
1595            ("1", ">=2"),
1596            // Test the less than equal operation
1597            ("2.0.post1", "<=2"),
1598            ("2.0.post1.dev1", "<=2"),
1599            ("3", "<=2"),
1600            // Test the greater than operation
1601            ("1", ">2"),
1602            ("2.0.dev1", ">2"),
1603            ("2.0a1", ">2"),
1604            ("2.0a1.post1", ">2"),
1605            ("2.0b1", ">2"),
1606            ("2.0b1.dev1", ">2"),
1607            ("2.0c1", ">2"),
1608            ("2.0c1.post1.dev1", ">2"),
1609            ("2.0rc1", ">2"),
1610            ("2.0", ">2"),
1611            ("2.post2", ">2"),
1612            ("2.0.post1", ">2"),
1613            ("2.0.post1.dev1", ">2"),
1614            ("2.0+local.version", ">2"),
1615            // Test the less than operation
1616            ("2.0.dev1", "<2"),
1617            ("2.0a1", "<2"),
1618            ("2.0a1.post1", "<2"),
1619            ("2.0b1", "<2"),
1620            ("2.0b2.dev1", "<2"),
1621            ("2.0c1", "<2"),
1622            ("2.0c1.post1.dev1", "<2"),
1623            ("2.0rc1", "<2"),
1624            ("2.0", "<2"),
1625            ("2.post1", "<2"),
1626            ("2.post1.dev1", "<2"),
1627            ("3", "<2"),
1628            // Test the compatibility operation
1629            ("2.0", "~=1.0"),
1630            ("1.1.0", "~=1.0.0"),
1631            ("1.1.post1", "~=1.0.0"),
1632            // Test that epochs are handled sanely
1633            ("1.0", "~=2!1.0"),
1634            ("2!1.0", "~=1.0"),
1635            ("2!1.0", "==1.0"),
1636            ("1.0", "==2!1.0"),
1637            ("2!1.0", "==1.*"),
1638            ("1.0", "==2!1.*"),
1639            ("2!1.0", "!=2!1.0"),
1640        ];
1641        for (version, specifier) in pairs {
1642            assert!(
1643                !VersionSpecifier::from_str(specifier)
1644                    .unwrap()
1645                    .contains(&Version::from_str(version).unwrap()),
1646                "{version} {specifier}"
1647            );
1648        }
1649    }
1650
1651    #[test]
1652    fn test_parse_version_specifiers() {
1653        let result = VersionSpecifiers::from_str("~= 0.9, >= 1.0, != 1.3.4.*, < 2.0").unwrap();
1654        assert_eq!(
1655            result.0.as_ref(),
1656            [
1657                VersionSpecifier {
1658                    operator: Operator::TildeEqual,
1659                    version: Version::new([0, 9]),
1660                },
1661                VersionSpecifier {
1662                    operator: Operator::GreaterThanEqual,
1663                    version: Version::new([1, 0]),
1664                },
1665                VersionSpecifier {
1666                    operator: Operator::NotEqualStar,
1667                    version: Version::new([1, 3, 4]),
1668                },
1669                VersionSpecifier {
1670                    operator: Operator::LessThan,
1671                    version: Version::new([2, 0]),
1672                }
1673            ]
1674        );
1675    }
1676
1677    #[test]
1678    fn test_parse_error() {
1679        let result = VersionSpecifiers::from_str("~= 0.9, %= 1.0, != 1.3.4.*");
1680        assert_eq!(
1681            result.unwrap_err().to_string(),
1682            indoc! {r"
1683            Failed to parse version: Unexpected end of version specifier, expected operator:
1684            ~= 0.9, %= 1.0, != 1.3.4.*
1685                   ^^^^^^^
1686        "}
1687        );
1688    }
1689
1690    #[test]
1691    fn test_parse_specifier_missing_operator_error() {
1692        let result = VersionSpecifiers::from_str("3.12");
1693        assert_eq!(
1694            result.unwrap_err().to_string(),
1695            indoc! {"
1696            Failed to parse version: Unexpected end of version specifier, expected operator. Did you mean `==3.12`?:
1697            3.12
1698            ^^^^
1699            "}
1700        );
1701    }
1702
1703    #[test]
1704    fn test_parse_specifier_missing_operator_invalid_version_error() {
1705        let result = VersionSpecifiers::from_str("blergh");
1706        assert_eq!(
1707            result.unwrap_err().to_string(),
1708            indoc! {r"
1709            Failed to parse version: Unexpected end of version specifier, expected operator:
1710            blergh
1711            ^^^^^^
1712            "}
1713        );
1714    }
1715
1716    #[test]
1717    fn test_non_star_after_star() {
1718        let result = VersionSpecifiers::from_str("== 0.9.*.1");
1719        assert_eq!(
1720            result.unwrap_err().inner.err,
1721            ParseErrorKind::InvalidVersion(version::PatternErrorKind::WildcardNotTrailing.into())
1722                .into(),
1723        );
1724    }
1725
1726    #[test]
1727    fn test_star_wrong_operator() {
1728        let result = VersionSpecifiers::from_str(">= 0.9.1.*");
1729        assert_eq!(
1730            result.unwrap_err().inner.err,
1731            ParseErrorKind::InvalidSpecifier(
1732                BuildErrorKind::OperatorWithStar {
1733                    operator: Operator::GreaterThanEqual,
1734                }
1735                .into()
1736            )
1737            .into(),
1738        );
1739    }
1740
1741    #[test]
1742    fn test_invalid_word() {
1743        let result = VersionSpecifiers::from_str("blergh");
1744        assert_eq!(
1745            result.unwrap_err().inner.err,
1746            ParseErrorKind::MissingOperator(VersionOperatorBuildError {
1747                version_pattern: None
1748            })
1749            .into(),
1750        );
1751    }
1752
1753    /// <https://github.com/pypa/packaging/blob/e184feef1a28a5c574ec41f5c263a3a573861f5a/tests/test_specifiers.py#L44-L84>
1754    #[test]
1755    fn test_invalid_specifier() {
1756        let specifiers = [
1757            // Operator-less specifier
1758            (
1759                "2.0",
1760                ParseErrorKind::MissingOperator(VersionOperatorBuildError {
1761                    version_pattern: VersionPattern::from_str("2.0").ok(),
1762                })
1763                .into(),
1764            ),
1765            // Invalid operator
1766            (
1767                "=>2.0",
1768                ParseErrorKind::InvalidOperator(OperatorParseError {
1769                    got: "=>".to_string(),
1770                })
1771                .into(),
1772            ),
1773            // Version-less specifier
1774            ("==", ParseErrorKind::MissingVersion.into()),
1775            // Local segment on operators which don't support them
1776            (
1777                "~=1.0+5",
1778                ParseErrorKind::InvalidSpecifier(
1779                    BuildErrorKind::OperatorLocalCombo {
1780                        operator: Operator::TildeEqual,
1781                        version: Version::new([1, 0])
1782                            .with_local_segments(vec![LocalSegment::Number(5)]),
1783                    }
1784                    .into(),
1785                )
1786                .into(),
1787            ),
1788            (
1789                ">=1.0+deadbeef",
1790                ParseErrorKind::InvalidSpecifier(
1791                    BuildErrorKind::OperatorLocalCombo {
1792                        operator: Operator::GreaterThanEqual,
1793                        version: Version::new([1, 0]).with_local_segments(vec![
1794                            LocalSegment::String("deadbeef".to_string()),
1795                        ]),
1796                    }
1797                    .into(),
1798                )
1799                .into(),
1800            ),
1801            (
1802                "<=1.0+abc123",
1803                ParseErrorKind::InvalidSpecifier(
1804                    BuildErrorKind::OperatorLocalCombo {
1805                        operator: Operator::LessThanEqual,
1806                        version: Version::new([1, 0])
1807                            .with_local_segments(vec![LocalSegment::String("abc123".to_string())]),
1808                    }
1809                    .into(),
1810                )
1811                .into(),
1812            ),
1813            (
1814                ">1.0+watwat",
1815                ParseErrorKind::InvalidSpecifier(
1816                    BuildErrorKind::OperatorLocalCombo {
1817                        operator: Operator::GreaterThan,
1818                        version: Version::new([1, 0])
1819                            .with_local_segments(vec![LocalSegment::String("watwat".to_string())]),
1820                    }
1821                    .into(),
1822                )
1823                .into(),
1824            ),
1825            (
1826                "<1.0+1.0",
1827                ParseErrorKind::InvalidSpecifier(
1828                    BuildErrorKind::OperatorLocalCombo {
1829                        operator: Operator::LessThan,
1830                        version: Version::new([1, 0]).with_local_segments(vec![
1831                            LocalSegment::Number(1),
1832                            LocalSegment::Number(0),
1833                        ]),
1834                    }
1835                    .into(),
1836                )
1837                .into(),
1838            ),
1839            // Prefix matching on operators which don't support them
1840            (
1841                "~=1.0.*",
1842                ParseErrorKind::InvalidSpecifier(
1843                    BuildErrorKind::OperatorWithStar {
1844                        operator: Operator::TildeEqual,
1845                    }
1846                    .into(),
1847                )
1848                .into(),
1849            ),
1850            (
1851                ">=1.0.*",
1852                ParseErrorKind::InvalidSpecifier(
1853                    BuildErrorKind::OperatorWithStar {
1854                        operator: Operator::GreaterThanEqual,
1855                    }
1856                    .into(),
1857                )
1858                .into(),
1859            ),
1860            (
1861                "<=1.0.*",
1862                ParseErrorKind::InvalidSpecifier(
1863                    BuildErrorKind::OperatorWithStar {
1864                        operator: Operator::LessThanEqual,
1865                    }
1866                    .into(),
1867                )
1868                .into(),
1869            ),
1870            (
1871                ">1.0.*",
1872                ParseErrorKind::InvalidSpecifier(
1873                    BuildErrorKind::OperatorWithStar {
1874                        operator: Operator::GreaterThan,
1875                    }
1876                    .into(),
1877                )
1878                .into(),
1879            ),
1880            (
1881                "<1.0.*",
1882                ParseErrorKind::InvalidSpecifier(
1883                    BuildErrorKind::OperatorWithStar {
1884                        operator: Operator::LessThan,
1885                    }
1886                    .into(),
1887                )
1888                .into(),
1889            ),
1890            // Combination of local and prefix matching on operators which do
1891            // support one or the other
1892            (
1893                "==1.0.*+5",
1894                ParseErrorKind::InvalidVersion(
1895                    version::PatternErrorKind::WildcardNotTrailing.into(),
1896                )
1897                .into(),
1898            ),
1899            (
1900                "!=1.0.*+deadbeef",
1901                ParseErrorKind::InvalidVersion(
1902                    version::PatternErrorKind::WildcardNotTrailing.into(),
1903                )
1904                .into(),
1905            ),
1906            // Prefix matching cannot be used with a pre-release, post-release,
1907            // dev or local version
1908            (
1909                "==2.0a1.*",
1910                ParseErrorKind::InvalidVersion(
1911                    version::ErrorKind::UnexpectedEnd {
1912                        version: "2.0a1".to_string(),
1913                        remaining: ".*".to_string(),
1914                    }
1915                    .into(),
1916                )
1917                .into(),
1918            ),
1919            (
1920                "!=2.0a1.*",
1921                ParseErrorKind::InvalidVersion(
1922                    version::ErrorKind::UnexpectedEnd {
1923                        version: "2.0a1".to_string(),
1924                        remaining: ".*".to_string(),
1925                    }
1926                    .into(),
1927                )
1928                .into(),
1929            ),
1930            (
1931                "==2.0.post1.*",
1932                ParseErrorKind::InvalidVersion(
1933                    version::ErrorKind::UnexpectedEnd {
1934                        version: "2.0.post1".to_string(),
1935                        remaining: ".*".to_string(),
1936                    }
1937                    .into(),
1938                )
1939                .into(),
1940            ),
1941            (
1942                "!=2.0.post1.*",
1943                ParseErrorKind::InvalidVersion(
1944                    version::ErrorKind::UnexpectedEnd {
1945                        version: "2.0.post1".to_string(),
1946                        remaining: ".*".to_string(),
1947                    }
1948                    .into(),
1949                )
1950                .into(),
1951            ),
1952            (
1953                "==2.0.dev1.*",
1954                ParseErrorKind::InvalidVersion(
1955                    version::ErrorKind::UnexpectedEnd {
1956                        version: "2.0.dev1".to_string(),
1957                        remaining: ".*".to_string(),
1958                    }
1959                    .into(),
1960                )
1961                .into(),
1962            ),
1963            (
1964                "!=2.0.dev1.*",
1965                ParseErrorKind::InvalidVersion(
1966                    version::ErrorKind::UnexpectedEnd {
1967                        version: "2.0.dev1".to_string(),
1968                        remaining: ".*".to_string(),
1969                    }
1970                    .into(),
1971                )
1972                .into(),
1973            ),
1974            (
1975                "==1.0+5.*",
1976                ParseErrorKind::InvalidVersion(
1977                    version::ErrorKind::LocalEmpty { precursor: '.' }.into(),
1978                )
1979                .into(),
1980            ),
1981            (
1982                "!=1.0+deadbeef.*",
1983                ParseErrorKind::InvalidVersion(
1984                    version::ErrorKind::LocalEmpty { precursor: '.' }.into(),
1985                )
1986                .into(),
1987            ),
1988            // Prefix matching must appear at the end
1989            (
1990                "==1.0.*.5",
1991                ParseErrorKind::InvalidVersion(
1992                    version::PatternErrorKind::WildcardNotTrailing.into(),
1993                )
1994                .into(),
1995            ),
1996            // Compatible operator requires 2 digits in the release operator
1997            (
1998                "~=1",
1999                ParseErrorKind::InvalidSpecifier(BuildErrorKind::CompatibleRelease.into()).into(),
2000            ),
2001            // Cannot use a prefix matching after a .devN version
2002            (
2003                "==1.0.dev1.*",
2004                ParseErrorKind::InvalidVersion(
2005                    version::ErrorKind::UnexpectedEnd {
2006                        version: "1.0.dev1".to_string(),
2007                        remaining: ".*".to_string(),
2008                    }
2009                    .into(),
2010                )
2011                .into(),
2012            ),
2013            (
2014                "!=1.0.dev1.*",
2015                ParseErrorKind::InvalidVersion(
2016                    version::ErrorKind::UnexpectedEnd {
2017                        version: "1.0.dev1".to_string(),
2018                        remaining: ".*".to_string(),
2019                    }
2020                    .into(),
2021                )
2022                .into(),
2023            ),
2024        ];
2025        for (specifier, error) in specifiers {
2026            assert_eq!(VersionSpecifier::from_str(specifier).unwrap_err(), error);
2027        }
2028    }
2029
2030    #[test]
2031    fn test_display_start() {
2032        assert_eq!(
2033            VersionSpecifier::from_str("==     1.1.*")
2034                .unwrap()
2035                .to_string(),
2036            "==1.1.*"
2037        );
2038        assert_eq!(
2039            VersionSpecifier::from_str("!=     1.1.*")
2040                .unwrap()
2041                .to_string(),
2042            "!=1.1.*"
2043        );
2044    }
2045
2046    #[test]
2047    fn test_version_specifiers_str() {
2048        assert_eq!(
2049            VersionSpecifiers::from_str(">= 3.7").unwrap().to_string(),
2050            ">=3.7"
2051        );
2052        assert_eq!(
2053            VersionSpecifiers::from_str(">=3.7, <      4.0, != 3.9.0")
2054                .unwrap()
2055                .to_string(),
2056            ">=3.7, !=3.9.0, <4.0"
2057        );
2058    }
2059
2060    #[test]
2061    fn test_version_specifiers_singular_interval() {
2062        let lower_then_upper = VersionSpecifiers::from_str(">=1.4.4, <=1.4.4").unwrap();
2063        let upper_then_lower = VersionSpecifiers::from_str("<=1.4.4, >=1.4.4").unwrap();
2064
2065        assert_eq!(lower_then_upper, upper_then_lower);
2066        assert_eq!(lower_then_upper.to_string(), "<=1.4.4, >=1.4.4");
2067    }
2068
2069    /// These occur in the simple api, e.g.
2070    /// <https://pypi.org/simple/geopandas/?format=application/vnd.pypi.simple.v1+json>
2071    #[test]
2072    fn test_version_specifiers_empty() {
2073        assert_eq!(VersionSpecifiers::from_str("").unwrap().to_string(), "");
2074    }
2075
2076    /// All non-ASCII version specifiers are invalid, but the user can still
2077    /// attempt to parse a non-ASCII string as a version specifier. This
2078    /// ensures no panics occur and that the error reported has correct info.
2079    #[test]
2080    fn non_ascii_version_specifier() {
2081        let s = "💩";
2082        let err = s.parse::<VersionSpecifiers>().unwrap_err();
2083        assert_eq!(err.inner.start, 0);
2084        assert_eq!(err.inner.end, 4);
2085
2086        // The first test here is plain ASCII and it gives the
2087        // expected result: the error starts at codepoint 12,
2088        // which is the start of `>5.%`.
2089        let s = ">=3.7, <4.0,>5.%";
2090        let err = s.parse::<VersionSpecifiers>().unwrap_err();
2091        assert_eq!(err.inner.start, 12);
2092        assert_eq!(err.inner.end, 16);
2093        // In this case, we replace a single ASCII codepoint
2094        // with U+3000 IDEOGRAPHIC SPACE. Its *visual* width is
2095        // 2 despite it being a single codepoint. This causes
2096        // the offsets in the error reporting logic to become
2097        // incorrect.
2098        //
2099        // ... it did. This bug was fixed by switching to byte
2100        // offsets.
2101        let s = ">=3.7,\u{3000}<4.0,>5.%";
2102        let err = s.parse::<VersionSpecifiers>().unwrap_err();
2103        assert_eq!(err.inner.start, 14);
2104        assert_eq!(err.inner.end, 18);
2105    }
2106
2107    /// Tests the human readable error messages generated from an invalid
2108    /// sequence of version specifiers.
2109    #[test]
2110    fn error_message_version_specifiers_parse_error() {
2111        let specs = ">=1.2.3, 5.4.3, >=3.4.5";
2112        let err = VersionSpecifierParseError {
2113            kind: Box::new(ParseErrorKind::MissingOperator(VersionOperatorBuildError {
2114                version_pattern: VersionPattern::from_str("5.4.3").ok(),
2115            })),
2116        };
2117        let inner = Box::new(VersionSpecifiersParseErrorInner {
2118            err,
2119            line: specs.to_string(),
2120            start: 8,
2121            end: 14,
2122        });
2123        let err = VersionSpecifiersParseError { inner };
2124        assert_eq!(err, VersionSpecifiers::from_str(specs).unwrap_err());
2125        assert_eq!(
2126            err.to_string(),
2127            "\
2128Failed to parse version: Unexpected end of version specifier, expected operator. Did you mean `==5.4.3`?:
2129>=1.2.3, 5.4.3, >=3.4.5
2130        ^^^^^^
2131"
2132        );
2133    }
2134
2135    /// Tests the human readable error messages generated when building an
2136    /// invalid version specifier.
2137    #[test]
2138    fn error_message_version_specifier_build_error() {
2139        let err = VersionSpecifierBuildError {
2140            kind: Box::new(BuildErrorKind::CompatibleRelease),
2141        };
2142        let op = Operator::TildeEqual;
2143        let v = Version::new([5]);
2144        let vpat = VersionPattern::verbatim(v);
2145        assert_eq!(err, VersionSpecifier::from_pattern(op, vpat).unwrap_err());
2146        assert_eq!(
2147            err.to_string(),
2148            "The ~= operator requires at least two segments in the release version"
2149        );
2150    }
2151
2152    /// Tests the human readable error messages generated from parsing invalid
2153    /// version specifier.
2154    #[test]
2155    fn error_message_version_specifier_parse_error() {
2156        let err = VersionSpecifierParseError {
2157            kind: Box::new(ParseErrorKind::InvalidSpecifier(
2158                VersionSpecifierBuildError {
2159                    kind: Box::new(BuildErrorKind::CompatibleRelease),
2160                },
2161            )),
2162        };
2163        assert_eq!(err, VersionSpecifier::from_str("~=5").unwrap_err());
2164        assert_eq!(
2165            err.to_string(),
2166            "The ~= operator requires at least two segments in the release version"
2167        );
2168    }
2169
2170    /// PEP 440 states that trailing zeros in `~=` specifiers control forward
2171    /// compatibility, so `~=2.2` ≠ `~=2.2.0`. Non-`~=` specifiers are unaffected.
2172    #[test]
2173    fn trailing_zero_equality() {
2174        let equal = [
2175            // Non-`~=` operators: trailing zeros are insignificant.
2176            (">=3.3", ">=3.3.0"),
2177            ("<2", "<2.0.0"),
2178            ("==1.2", "==1.2.0"),
2179            // Identical `~=` specifiers.
2180            ("~=2.2.0", "~=2.2.0"),
2181        ];
2182        for (a, b) in equal {
2183            let a = VersionSpecifier::from_str(a).unwrap();
2184            let b = VersionSpecifier::from_str(b).unwrap();
2185            assert_eq!(a, b);
2186        }
2187
2188        let not_equal = [
2189            // PEP 440 forward-compat examples.
2190            ("~=2.2", "~=2.2.0"),
2191            ("~=1.4.5", "~=1.4.5.0"),
2192            // Same release, different suffix.
2193            ("~=2.2.post3", "~=2.2.post5"),
2194            // Different release length with matching suffix.
2195            ("~=2.2.post3", "~=2.2.0.post3"),
2196        ];
2197        for (a, b) in not_equal {
2198            let a = VersionSpecifier::from_str(a).unwrap();
2199            let b = VersionSpecifier::from_str(b).unwrap();
2200            assert_ne!(a, b);
2201        }
2202    }
2203
2204    /// Do not panic with `u64::MAX` causing an `u64::MAX + 1` overflow.
2205    #[test]
2206    fn bounding_specifiers_u64_max_rejected_at_parse_time() {
2207        assert!(VersionSpecifier::from_str("~=3.18446744073709551615.0").is_err());
2208        assert!(VersionSpecifier::from_str("~=18446744073709551615.0").is_err());
2209
2210        // u64::MAX - 1 is accepted and bounding_specifiers does not overflow.
2211        let specifier = VersionSpecifier::from_str("~=3.18446744073709551614.0").unwrap();
2212        let tilde = TildeVersionSpecifier::from_specifier(specifier).unwrap();
2213        let (_lower, _upper) = tilde.bounding_specifiers();
2214    }
2215}