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