Skip to main content

uv_distribution_types/
lib.rs

1//! ## Type hierarchy
2//!
3//! When we receive the requirements from `pip sync`, we check which requirements already fulfilled
4//! in the users environment ([`InstalledDist`]), whether the matching package is in our wheel cache
5//! ([`CachedDist`]) or whether we need to download, (potentially build) and install it ([`Dist`]).
6//! These three variants make up [`BuiltDist`].
7//!
8//! ## `Dist`
9//! A [`Dist`] is either a built distribution (a wheel), or a source distribution that exists at
10//! some location. We translate every PEP 508 requirement e.g. from `requirements.txt` or from
11//! `pyproject.toml`'s `[project] dependencies` into a [`Dist`] by checking each index.
12//! * [`BuiltDist`]: A wheel, with its three possible origins:
13//!   * [`RegistryBuiltDist`]
14//!   * [`DirectUrlBuiltDist`]
15//!   * [`PathBuiltDist`]
16//! * [`SourceDist`]: A source distribution, with its four possible origins:
17//!   * [`RegistrySourceDist`]
18//!   * [`DirectUrlSourceDist`]
19//!   * [`GitSourceDist`]
20//!   * [`PathSourceDist`]
21//!
22//! ## `CachedDist`
23//! A [`CachedDist`] is a built distribution (wheel) that exists in the local cache, with the two
24//! possible origins we currently track:
25//! * [`CachedRegistryDist`]
26//! * [`CachedDirectUrlDist`]
27//!
28//! ## `InstalledDist`
29//! An [`InstalledDist`] is built distribution (wheel) that is installed in a virtual environment,
30//! with the two possible origins we currently track:
31//! * [`InstalledRegistryDist`]
32//! * [`InstalledDirectUrlDist`]
33//!
34//! Since we read this information from [`direct_url.json`](https://packaging.python.org/en/latest/specifications/direct-url-data-structure/), it doesn't match the information [`Dist`] exactly.
35use std::borrow::Cow;
36use std::path;
37use std::path::Path;
38use std::str::FromStr;
39
40use url::Url;
41
42use uv_distribution_filename::{
43    DistExtension, SourceDistExtension, SourceDistFilename, WheelFilename,
44};
45use uv_fs::normalize_absolute_path;
46use uv_git_types::GitUrl;
47use uv_normalize::PackageName;
48use uv_pep440::Version;
49use uv_pep508::{Pep508Url, VerbatimUrl};
50use uv_pypi_types::{
51    ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, VerbatimParsedUrl,
52};
53use uv_redacted::DisplaySafeUrl;
54
55pub use crate::annotation::*;
56pub use crate::any::*;
57pub use crate::build_info::*;
58pub use crate::build_requires::*;
59pub use crate::buildable::*;
60pub use crate::cached::*;
61pub use crate::config_settings::*;
62pub use crate::dependency_metadata::*;
63pub use crate::diagnostic::*;
64pub use crate::dist_error::*;
65pub use crate::error::*;
66pub use crate::file::*;
67pub use crate::hash::*;
68pub use crate::id::*;
69pub use crate::index::*;
70pub use crate::index_name::*;
71pub use crate::index_url::*;
72pub use crate::installed::*;
73pub use crate::known_platform::*;
74pub use crate::origin::*;
75pub use crate::pip_index::*;
76pub use crate::prioritized_distribution::*;
77pub use crate::requested::*;
78pub use crate::requirement::*;
79pub use crate::requires_python::*;
80pub use crate::resolution::*;
81pub use crate::resolved::*;
82pub use crate::specified_requirement::*;
83pub use crate::status_code_strategy::*;
84pub use crate::traits::*;
85
86mod annotation;
87mod any;
88mod build_info;
89mod build_requires;
90mod buildable;
91mod cached;
92mod config_settings;
93mod dependency_metadata;
94mod diagnostic;
95mod dist_error;
96mod error;
97mod file;
98mod hash;
99mod id;
100mod index;
101mod index_name;
102mod index_url;
103mod installed;
104mod known_platform;
105mod origin;
106mod pip_index;
107mod prioritized_distribution;
108mod requested;
109mod requirement;
110mod requires_python;
111mod resolution;
112mod resolved;
113mod specified_requirement;
114mod status_code_strategy;
115mod traits;
116
117#[derive(Debug, Clone)]
118pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
119    /// A PEP 440 version specifier, used to identify a distribution in a registry.
120    Version(&'a Version),
121    /// A URL, used to identify a distribution at an arbitrary location.
122    Url(&'a T),
123}
124
125impl<'a, T: Pep508Url> VersionOrUrlRef<'a, T> {
126    /// If it is a URL, return its value.
127    pub fn url(&self) -> Option<&'a T> {
128        match self {
129            Self::Version(_) => None,
130            Self::Url(url) => Some(url),
131        }
132    }
133}
134
135impl Verbatim for VersionOrUrlRef<'_> {
136    fn verbatim(&self) -> Cow<'_, str> {
137        match self {
138            Self::Version(version) => Cow::Owned(format!("=={version}")),
139            Self::Url(url) => Cow::Owned(format!(" @ {}", url.verbatim())),
140        }
141    }
142}
143
144impl std::fmt::Display for VersionOrUrlRef<'_> {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        match self {
147            Self::Version(version) => write!(f, "=={version}"),
148            Self::Url(url) => write!(f, " @ {url}"),
149        }
150    }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
154pub enum InstalledVersion<'a> {
155    /// A PEP 440 version specifier, used to identify a distribution in a registry.
156    Version(&'a Version),
157    /// A URL, used to identify a distribution at an arbitrary location, along with the version
158    /// specifier to which it resolved.
159    Url(&'a DisplaySafeUrl, &'a Version),
160}
161
162impl<'a> InstalledVersion<'a> {
163    /// If it is a URL, return its value.
164    pub fn url(&self) -> Option<&'a DisplaySafeUrl> {
165        match self {
166            Self::Version(_) => None,
167            Self::Url(url, _) => Some(url),
168        }
169    }
170
171    /// If it is a version, return its value.
172    pub fn version(&self) -> &'a Version {
173        match self {
174            Self::Version(version) => version,
175            Self::Url(_, version) => version,
176        }
177    }
178}
179
180impl std::fmt::Display for InstalledVersion<'_> {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        match self {
183            Self::Version(version) => write!(f, "=={version}"),
184            Self::Url(url, version) => write!(f, "=={version} (from {url})"),
185        }
186    }
187}
188
189/// Either a built distribution, a wheel, or a source distribution that exists at some location.
190///
191/// The location can be an index, URL or path (wheel), or index, URL, path or Git repository (source distribution).
192#[derive(Debug, Clone, Hash, PartialEq, Eq)]
193pub enum Dist {
194    Built(BuiltDist),
195    Source(SourceDist),
196}
197
198/// A reference to a built or source distribution.
199#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
200pub enum DistRef<'a> {
201    Built(&'a BuiltDist),
202    Source(&'a SourceDist),
203}
204
205/// A wheel, with its three possible origins (index, url, path)
206#[derive(Debug, Clone, Hash, PartialEq, Eq)]
207pub enum BuiltDist {
208    Registry(RegistryBuiltDist),
209    DirectUrl(DirectUrlBuiltDist),
210    Path(PathBuiltDist),
211}
212
213/// A source distribution, with its possible origins (index, url, path, git)
214#[derive(Debug, Clone, Hash, PartialEq, Eq)]
215pub enum SourceDist {
216    Registry(RegistrySourceDist),
217    DirectUrl(DirectUrlSourceDist),
218    Git(GitSourceDist),
219    Path(PathSourceDist),
220    Directory(DirectorySourceDist),
221}
222
223/// A built distribution (wheel) that exists in a registry, like `PyPI`.
224#[derive(Debug, Clone, Hash, PartialEq, Eq)]
225pub struct RegistryBuiltWheel {
226    pub filename: WheelFilename,
227    pub file: Box<File>,
228    pub index: IndexUrl,
229}
230
231/// A built distribution (wheel) that exists in a registry, like `PyPI`.
232#[derive(Debug, Clone, Hash, PartialEq, Eq)]
233pub struct RegistryBuiltDist {
234    /// All wheels associated with this distribution. It is guaranteed
235    /// that there is at least one wheel.
236    pub wheels: Vec<RegistryBuiltWheel>,
237    /// The "best" wheel selected based on the current wheel tag
238    /// environment.
239    ///
240    /// This is guaranteed to point into a valid entry in `wheels`.
241    pub best_wheel_index: usize,
242    /// A source distribution if one exists for this distribution.
243    ///
244    /// It is possible for this to be `None`. For example, when a distribution
245    /// has no source distribution, or if it does have one but isn't compatible
246    /// with the user configuration. (e.g., If `Requires-Python` isn't
247    /// compatible with the installed/target Python versions, or if something
248    /// like `--exclude-newer` was used.)
249    pub sdist: Option<RegistrySourceDist>,
250    // Ideally, this type would have an index URL on it, and the
251    // `RegistryBuiltDist` and `RegistrySourceDist` types would *not* have an
252    // index URL on them. Alas, the --find-links feature makes it technically
253    // possible for the indexes to diverge across wheels/sdists in the same
254    // distribution.
255    //
256    // Note though that at time of writing, when generating a universal lock
257    // file, we require that all index URLs across wheels/sdists for a single
258    // distribution are equivalent.
259}
260
261/// A built distribution (wheel) that exists at an arbitrary URL.
262#[derive(Debug, Clone, Hash, PartialEq, Eq)]
263pub struct DirectUrlBuiltDist {
264    /// We require that wheel urls end in the full wheel filename, e.g.
265    /// `https://example.org/packages/flask-3.0.0-py3-none-any.whl`
266    pub filename: WheelFilename,
267    /// The URL without the subdirectory fragment.
268    pub location: Box<DisplaySafeUrl>,
269    /// The URL as it was provided by the user.
270    pub url: VerbatimUrl,
271}
272
273/// A built distribution (wheel) that exists in a local directory.
274#[derive(Debug, Clone, Hash, PartialEq, Eq)]
275pub struct PathBuiltDist {
276    pub filename: WheelFilename,
277    /// The absolute path to the wheel which we use for installing.
278    pub install_path: Box<Path>,
279    /// The URL as it was provided by the user.
280    pub url: VerbatimUrl,
281}
282
283/// A source distribution that exists in a registry, like `PyPI`.
284#[derive(Debug, Clone, Hash, PartialEq, Eq)]
285pub struct RegistrySourceDist {
286    pub name: PackageName,
287    pub version: Version,
288    pub file: Box<File>,
289    /// The file extension, e.g. `tar.gz`, `zip`, etc.
290    pub ext: SourceDistExtension,
291    pub index: IndexUrl,
292    /// When an sdist is selected, it may be the case that there were
293    /// available wheels too. There are many reasons why a wheel might not
294    /// have been chosen (maybe none available are compatible with the
295    /// current environment), but we still want to track that they exist. In
296    /// particular, for generating a universal lockfile, we do not want to
297    /// skip emitting wheels to the lockfile just because the host generating
298    /// the lockfile didn't have any compatible wheels available.
299    pub wheels: Vec<RegistryBuiltWheel>,
300}
301
302/// A source distribution that exists at an arbitrary URL.
303#[derive(Debug, Clone, Hash, PartialEq, Eq)]
304pub struct DirectUrlSourceDist {
305    /// Unlike [`DirectUrlBuiltDist`], we can't require a full filename with a version here, people
306    /// like using e.g. `foo @ https://github.com/org/repo/archive/master.zip`
307    pub name: PackageName,
308    /// The URL without the subdirectory fragment.
309    pub location: Box<DisplaySafeUrl>,
310    /// The subdirectory within the archive in which the source distribution is located.
311    pub subdirectory: Option<Box<Path>>,
312    /// The file extension, e.g. `tar.gz`, `zip`, etc.
313    pub ext: SourceDistExtension,
314    /// The URL as it was provided by the user, including the subdirectory fragment.
315    pub url: VerbatimUrl,
316}
317
318/// A source distribution that exists in a Git repository.
319#[derive(Debug, Clone, Hash, PartialEq, Eq)]
320pub struct GitSourceDist {
321    pub name: PackageName,
322    /// The URL without the revision and subdirectory fragment.
323    pub git: Box<GitUrl>,
324    /// The subdirectory within the Git repository in which the source distribution is located.
325    pub subdirectory: Option<Box<Path>>,
326    /// The URL as it was provided by the user, including the revision and subdirectory fragment.
327    pub url: VerbatimUrl,
328}
329
330/// A source distribution that exists in a local archive (e.g., a `.tar.gz` file).
331#[derive(Debug, Clone, Hash, PartialEq, Eq)]
332pub struct PathSourceDist {
333    pub name: PackageName,
334    pub version: Option<Version>,
335    /// The absolute path to the distribution which we use for installing.
336    pub install_path: Box<Path>,
337    /// The file extension, e.g. `tar.gz`, `zip`, etc.
338    pub ext: SourceDistExtension,
339    /// The URL as it was provided by the user.
340    pub url: VerbatimUrl,
341}
342
343/// A source distribution that exists in a local directory.
344#[derive(Debug, Clone, Hash, PartialEq, Eq)]
345pub struct DirectorySourceDist {
346    pub name: PackageName,
347    /// The absolute path to the distribution which we use for installing.
348    pub install_path: Box<Path>,
349    /// Whether the package should be installed in editable mode.
350    pub editable: Option<bool>,
351    /// Whether the package should be built and installed.
352    pub r#virtual: Option<bool>,
353    /// The URL as it was provided by the user.
354    pub url: VerbatimUrl,
355}
356
357impl Dist {
358    /// A remote built distribution (`.whl`) or source distribution from a `http://` or `https://`
359    /// URL.
360    pub fn from_http_url(
361        name: PackageName,
362        url: VerbatimUrl,
363        location: DisplaySafeUrl,
364        subdirectory: Option<Box<Path>>,
365        ext: DistExtension,
366    ) -> Result<Self, Error> {
367        match ext {
368            DistExtension::Wheel => {
369                // Validate that the name in the wheel matches that of the requirement.
370                let filename = WheelFilename::from_str(&url.filename()?)?;
371                if filename.name != name {
372                    return Err(Error::PackageNameMismatch(
373                        name,
374                        filename.name,
375                        url.verbatim().to_string(),
376                    ));
377                }
378
379                Ok(Self::Built(BuiltDist::DirectUrl(DirectUrlBuiltDist {
380                    filename,
381                    location: Box::new(location),
382                    url,
383                })))
384            }
385            DistExtension::Source(ext) => {
386                Ok(Self::Source(SourceDist::DirectUrl(DirectUrlSourceDist {
387                    name,
388                    location: Box::new(location),
389                    subdirectory,
390                    ext,
391                    url,
392                })))
393            }
394        }
395    }
396
397    /// A local built or source distribution from a `file://` URL.
398    pub fn from_file_url(
399        name: PackageName,
400        url: VerbatimUrl,
401        install_path: &Path,
402        ext: DistExtension,
403    ) -> Result<Self, Error> {
404        // Convert to an absolute path.
405        let install_path = path::absolute(install_path)?;
406
407        // Normalize the path.
408        let install_path = normalize_absolute_path(&install_path)?;
409
410        // Validate that the path exists.
411        if !install_path.exists() {
412            return Err(Error::NotFound(url.to_url()));
413        }
414
415        // Determine whether the path represents a built or source distribution.
416        match ext {
417            DistExtension::Wheel => {
418                // Validate that the name in the wheel matches that of the requirement.
419                let filename = WheelFilename::from_str(&url.filename()?)?;
420                if filename.name != name {
421                    return Err(Error::PackageNameMismatch(
422                        name,
423                        filename.name,
424                        url.verbatim().to_string(),
425                    ));
426                }
427                Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
428                    filename,
429                    install_path: install_path.into_boxed_path(),
430                    url,
431                })))
432            }
433            DistExtension::Source(ext) => {
434                // If there is a version in the filename, record it.
435                let version = url
436                    .filename()
437                    .ok()
438                    .and_then(|filename| {
439                        SourceDistFilename::parse(filename.as_ref(), ext, &name).ok()
440                    })
441                    .map(|filename| filename.version);
442
443                Ok(Self::Source(SourceDist::Path(PathSourceDist {
444                    name,
445                    version,
446                    install_path: install_path.into_boxed_path(),
447                    ext,
448                    url,
449                })))
450            }
451        }
452    }
453
454    /// A local source tree from a `file://` URL.
455    pub fn from_directory_url(
456        name: PackageName,
457        url: VerbatimUrl,
458        install_path: &Path,
459        editable: Option<bool>,
460        r#virtual: Option<bool>,
461    ) -> Result<Self, Error> {
462        // Convert to an absolute path.
463        let install_path = path::absolute(install_path)?;
464
465        // Normalize the path.
466        let install_path = normalize_absolute_path(&install_path)?;
467
468        // Validate that the path exists.
469        if !install_path.exists() {
470            return Err(Error::NotFound(url.to_url()));
471        }
472
473        // Determine whether the path represents an archive or a directory.
474        Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
475            name,
476            install_path: install_path.into_boxed_path(),
477            editable,
478            r#virtual,
479            url,
480        })))
481    }
482
483    /// A remote source distribution from a `git+https://` or `git+ssh://` url.
484    pub fn from_git_url(
485        name: PackageName,
486        url: VerbatimUrl,
487        git: GitUrl,
488        subdirectory: Option<Box<Path>>,
489    ) -> Result<Self, Error> {
490        Ok(Self::Source(SourceDist::Git(GitSourceDist {
491            name,
492            git: Box::new(git),
493            subdirectory,
494            url,
495        })))
496    }
497
498    /// Create a [`Dist`] for a URL-based distribution.
499    pub fn from_url(name: PackageName, url: VerbatimParsedUrl) -> Result<Self, Error> {
500        match url.parsed_url {
501            ParsedUrl::Archive(archive) => Self::from_http_url(
502                name,
503                url.verbatim,
504                archive.url,
505                archive.subdirectory,
506                archive.ext,
507            ),
508            ParsedUrl::Path(file) => {
509                Self::from_file_url(name, url.verbatim, &file.install_path, file.ext)
510            }
511            ParsedUrl::Directory(directory) => Self::from_directory_url(
512                name,
513                url.verbatim,
514                &directory.install_path,
515                directory.editable,
516                directory.r#virtual,
517            ),
518            ParsedUrl::Git(git) => {
519                Self::from_git_url(name, url.verbatim, git.url, git.subdirectory)
520            }
521        }
522    }
523
524    /// Return true if the distribution is editable.
525    pub fn is_editable(&self) -> bool {
526        match self {
527            Self::Source(dist) => dist.is_editable(),
528            Self::Built(_) => false,
529        }
530    }
531
532    /// Return true if the distribution refers to a local file or directory.
533    pub fn is_local(&self) -> bool {
534        match self {
535            Self::Source(dist) => dist.is_local(),
536            Self::Built(dist) => dist.is_local(),
537        }
538    }
539
540    /// Returns the [`IndexUrl`], if the distribution is from a registry.
541    pub fn index(&self) -> Option<&IndexUrl> {
542        match self {
543            Self::Built(dist) => dist.index(),
544            Self::Source(dist) => dist.index(),
545        }
546    }
547
548    /// Returns the [`File`] instance, if this dist is from a registry with simple json api support
549    pub fn file(&self) -> Option<&File> {
550        match self {
551            Self::Built(built) => built.file(),
552            Self::Source(source) => source.file(),
553        }
554    }
555
556    /// Return the source tree of the distribution, if available.
557    pub fn source_tree(&self) -> Option<&Path> {
558        match self {
559            Self::Built { .. } => None,
560            Self::Source(source) => source.source_tree(),
561        }
562    }
563
564    /// Returns the version of the distribution, if it is known.
565    pub fn version(&self) -> Option<&Version> {
566        match self {
567            Self::Built(wheel) => Some(wheel.version()),
568            Self::Source(source_dist) => source_dist.version(),
569        }
570    }
571
572    /// Convert this distribution into a reference.
573    pub fn as_ref(&self) -> DistRef<'_> {
574        match self {
575            Self::Built(dist) => DistRef::Built(dist),
576            Self::Source(dist) => DistRef::Source(dist),
577        }
578    }
579}
580
581impl<'a> From<&'a Dist> for DistRef<'a> {
582    fn from(dist: &'a Dist) -> Self {
583        match dist {
584            Dist::Built(built) => DistRef::Built(built),
585            Dist::Source(source) => DistRef::Source(source),
586        }
587    }
588}
589
590impl<'a> From<&'a SourceDist> for DistRef<'a> {
591    fn from(dist: &'a SourceDist) -> Self {
592        DistRef::Source(dist)
593    }
594}
595
596impl<'a> From<&'a BuiltDist> for DistRef<'a> {
597    fn from(dist: &'a BuiltDist) -> Self {
598        DistRef::Built(dist)
599    }
600}
601
602impl BuiltDist {
603    /// Return true if the distribution refers to a local file or directory.
604    pub fn is_local(&self) -> bool {
605        matches!(self, Self::Path(_))
606    }
607
608    /// Returns the [`IndexUrl`], if the distribution is from a registry.
609    pub fn index(&self) -> Option<&IndexUrl> {
610        match self {
611            Self::Registry(registry) => Some(&registry.best_wheel().index),
612            Self::DirectUrl(_) => None,
613            Self::Path(_) => None,
614        }
615    }
616
617    /// Returns the [`File`] instance, if this distribution is from a registry.
618    pub fn file(&self) -> Option<&File> {
619        match self {
620            Self::Registry(registry) => Some(&registry.best_wheel().file),
621            Self::DirectUrl(_) | Self::Path(_) => None,
622        }
623    }
624
625    pub fn version(&self) -> &Version {
626        match self {
627            Self::Registry(wheels) => &wheels.best_wheel().filename.version,
628            Self::DirectUrl(wheel) => &wheel.filename.version,
629            Self::Path(wheel) => &wheel.filename.version,
630        }
631    }
632}
633
634impl SourceDist {
635    /// Returns the [`IndexUrl`], if the distribution is from a registry.
636    pub fn index(&self) -> Option<&IndexUrl> {
637        match self {
638            Self::Registry(registry) => Some(&registry.index),
639            Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
640        }
641    }
642
643    /// Returns the [`File`] instance, if this dist is from a registry with simple json api support
644    pub fn file(&self) -> Option<&File> {
645        match self {
646            Self::Registry(registry) => Some(&registry.file),
647            Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
648        }
649    }
650
651    /// Returns the [`Version`] of the distribution, if it is known.
652    pub fn version(&self) -> Option<&Version> {
653        match self {
654            Self::Registry(source_dist) => Some(&source_dist.version),
655            Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
656        }
657    }
658
659    /// Returns `true` if the distribution is editable.
660    pub fn is_editable(&self) -> bool {
661        match self {
662            Self::Directory(DirectorySourceDist { editable, .. }) => editable.unwrap_or(false),
663            _ => false,
664        }
665    }
666
667    /// Returns `true` if the distribution is virtual.
668    pub fn is_virtual(&self) -> bool {
669        match self {
670            Self::Directory(DirectorySourceDist { r#virtual, .. }) => r#virtual.unwrap_or(false),
671            _ => false,
672        }
673    }
674
675    /// Returns `true` if the distribution refers to a local file or directory.
676    pub fn is_local(&self) -> bool {
677        matches!(self, Self::Directory(_) | Self::Path(_))
678    }
679
680    /// Returns the path to the source distribution, if it's a local distribution.
681    pub fn as_path(&self) -> Option<&Path> {
682        match self {
683            Self::Path(dist) => Some(&dist.install_path),
684            Self::Directory(dist) => Some(&dist.install_path),
685            _ => None,
686        }
687    }
688
689    /// Returns the source tree of the distribution, if available.
690    pub fn source_tree(&self) -> Option<&Path> {
691        match self {
692            Self::Directory(dist) => Some(&dist.install_path),
693            _ => None,
694        }
695    }
696}
697
698impl RegistryBuiltDist {
699    /// Returns the best or "most compatible" wheel in this distribution.
700    pub fn best_wheel(&self) -> &RegistryBuiltWheel {
701        &self.wheels[self.best_wheel_index]
702    }
703}
704
705impl DirectUrlBuiltDist {
706    /// Return the [`ParsedUrl`] for the distribution.
707    pub fn parsed_url(&self) -> ParsedUrl {
708        ParsedUrl::Archive(ParsedArchiveUrl::from_source(
709            (*self.location).clone(),
710            None,
711            DistExtension::Wheel,
712        ))
713    }
714}
715
716impl PathBuiltDist {
717    /// Return the [`ParsedUrl`] for the distribution.
718    pub fn parsed_url(&self) -> ParsedUrl {
719        ParsedUrl::Path(ParsedPathUrl::from_source(
720            self.install_path.clone(),
721            DistExtension::Wheel,
722            self.url.to_url(),
723        ))
724    }
725}
726
727impl PathSourceDist {
728    /// Return the [`ParsedUrl`] for the distribution.
729    pub fn parsed_url(&self) -> ParsedUrl {
730        ParsedUrl::Path(ParsedPathUrl::from_source(
731            self.install_path.clone(),
732            DistExtension::Source(self.ext),
733            self.url.to_url(),
734        ))
735    }
736}
737
738impl DirectUrlSourceDist {
739    /// Return the [`ParsedUrl`] for the distribution.
740    pub fn parsed_url(&self) -> ParsedUrl {
741        ParsedUrl::Archive(ParsedArchiveUrl::from_source(
742            (*self.location).clone(),
743            self.subdirectory.clone(),
744            DistExtension::Source(self.ext),
745        ))
746    }
747}
748
749impl GitSourceDist {
750    /// Return the [`ParsedUrl`] for the distribution.
751    pub fn parsed_url(&self) -> ParsedUrl {
752        ParsedUrl::Git(ParsedGitUrl::from_source(
753            (*self.git).clone(),
754            self.subdirectory.clone(),
755        ))
756    }
757}
758
759impl DirectorySourceDist {
760    /// Return the [`ParsedUrl`] for the distribution.
761    pub fn parsed_url(&self) -> ParsedUrl {
762        ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
763            self.install_path.clone(),
764            self.editable,
765            self.r#virtual,
766            self.url.to_url(),
767        ))
768    }
769}
770
771impl Name for RegistryBuiltWheel {
772    fn name(&self) -> &PackageName {
773        &self.filename.name
774    }
775}
776
777impl Name for RegistryBuiltDist {
778    fn name(&self) -> &PackageName {
779        self.best_wheel().name()
780    }
781}
782
783impl Name for DirectUrlBuiltDist {
784    fn name(&self) -> &PackageName {
785        &self.filename.name
786    }
787}
788
789impl Name for PathBuiltDist {
790    fn name(&self) -> &PackageName {
791        &self.filename.name
792    }
793}
794
795impl Name for RegistrySourceDist {
796    fn name(&self) -> &PackageName {
797        &self.name
798    }
799}
800
801impl Name for DirectUrlSourceDist {
802    fn name(&self) -> &PackageName {
803        &self.name
804    }
805}
806
807impl Name for GitSourceDist {
808    fn name(&self) -> &PackageName {
809        &self.name
810    }
811}
812
813impl Name for PathSourceDist {
814    fn name(&self) -> &PackageName {
815        &self.name
816    }
817}
818
819impl Name for DirectorySourceDist {
820    fn name(&self) -> &PackageName {
821        &self.name
822    }
823}
824
825impl Name for SourceDist {
826    fn name(&self) -> &PackageName {
827        match self {
828            Self::Registry(dist) => dist.name(),
829            Self::DirectUrl(dist) => dist.name(),
830            Self::Git(dist) => dist.name(),
831            Self::Path(dist) => dist.name(),
832            Self::Directory(dist) => dist.name(),
833        }
834    }
835}
836
837impl Name for BuiltDist {
838    fn name(&self) -> &PackageName {
839        match self {
840            Self::Registry(dist) => dist.name(),
841            Self::DirectUrl(dist) => dist.name(),
842            Self::Path(dist) => dist.name(),
843        }
844    }
845}
846
847impl Name for Dist {
848    fn name(&self) -> &PackageName {
849        match self {
850            Self::Built(dist) => dist.name(),
851            Self::Source(dist) => dist.name(),
852        }
853    }
854}
855
856impl Name for CompatibleDist<'_> {
857    fn name(&self) -> &PackageName {
858        match self {
859            Self::InstalledDist(dist) => dist.name(),
860            Self::SourceDist {
861                sdist,
862                prioritized: _,
863            } => sdist.name(),
864            Self::CompatibleWheel {
865                wheel,
866                priority: _,
867                prioritized: _,
868            } => wheel.name(),
869            Self::IncompatibleWheel {
870                sdist,
871                wheel: _,
872                prioritized: _,
873            } => sdist.name(),
874        }
875    }
876}
877
878impl DistributionMetadata for RegistryBuiltWheel {
879    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
880        VersionOrUrlRef::Version(&self.filename.version)
881    }
882}
883
884impl DistributionMetadata for RegistryBuiltDist {
885    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
886        self.best_wheel().version_or_url()
887    }
888}
889
890impl DistributionMetadata for DirectUrlBuiltDist {
891    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
892        VersionOrUrlRef::Url(&self.url)
893    }
894}
895
896impl DistributionMetadata for PathBuiltDist {
897    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
898        VersionOrUrlRef::Url(&self.url)
899    }
900}
901
902impl DistributionMetadata for RegistrySourceDist {
903    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
904        VersionOrUrlRef::Version(&self.version)
905    }
906}
907
908impl DistributionMetadata for DirectUrlSourceDist {
909    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
910        VersionOrUrlRef::Url(&self.url)
911    }
912}
913
914impl DistributionMetadata for GitSourceDist {
915    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
916        VersionOrUrlRef::Url(&self.url)
917    }
918}
919
920impl DistributionMetadata for PathSourceDist {
921    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
922        VersionOrUrlRef::Url(&self.url)
923    }
924}
925
926impl DistributionMetadata for DirectorySourceDist {
927    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
928        VersionOrUrlRef::Url(&self.url)
929    }
930}
931
932impl DistributionMetadata for SourceDist {
933    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
934        match self {
935            Self::Registry(dist) => dist.version_or_url(),
936            Self::DirectUrl(dist) => dist.version_or_url(),
937            Self::Git(dist) => dist.version_or_url(),
938            Self::Path(dist) => dist.version_or_url(),
939            Self::Directory(dist) => dist.version_or_url(),
940        }
941    }
942}
943
944impl DistributionMetadata for BuiltDist {
945    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
946        match self {
947            Self::Registry(dist) => dist.version_or_url(),
948            Self::DirectUrl(dist) => dist.version_or_url(),
949            Self::Path(dist) => dist.version_or_url(),
950        }
951    }
952}
953
954impl DistributionMetadata for Dist {
955    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
956        match self {
957            Self::Built(dist) => dist.version_or_url(),
958            Self::Source(dist) => dist.version_or_url(),
959        }
960    }
961}
962
963impl RemoteSource for File {
964    fn filename(&self) -> Result<Cow<'_, str>, Error> {
965        Ok(Cow::Borrowed(&self.filename))
966    }
967
968    fn size(&self) -> Option<u64> {
969        self.size
970    }
971}
972
973impl RemoteSource for Url {
974    fn filename(&self) -> Result<Cow<'_, str>, Error> {
975        // Identify the last segment of the URL as the filename.
976        let mut path_segments = self
977            .path_segments()
978            .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
979
980        // This is guaranteed by the contract of `Url::path_segments`.
981        let last = path_segments
982            .next_back()
983            .expect("path segments is non-empty");
984
985        // Decode the filename, which may be percent-encoded.
986        let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
987
988        Ok(filename)
989    }
990
991    fn size(&self) -> Option<u64> {
992        None
993    }
994}
995
996impl RemoteSource for UrlString {
997    fn filename(&self) -> Result<Cow<'_, str>, Error> {
998        // Take the last segment, stripping any query or fragment.
999        let last = self
1000            .base_str()
1001            .split('/')
1002            .next_back()
1003            .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1004
1005        // Decode the filename, which may be percent-encoded.
1006        let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1007
1008        Ok(filename)
1009    }
1010
1011    fn size(&self) -> Option<u64> {
1012        None
1013    }
1014}
1015
1016impl RemoteSource for RegistryBuiltWheel {
1017    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1018        self.file.filename()
1019    }
1020
1021    fn size(&self) -> Option<u64> {
1022        self.file.size()
1023    }
1024}
1025
1026impl RemoteSource for RegistryBuiltDist {
1027    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1028        self.best_wheel().filename()
1029    }
1030
1031    fn size(&self) -> Option<u64> {
1032        self.best_wheel().size()
1033    }
1034}
1035
1036impl RemoteSource for RegistrySourceDist {
1037    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1038        self.file.filename()
1039    }
1040
1041    fn size(&self) -> Option<u64> {
1042        self.file.size()
1043    }
1044}
1045
1046impl RemoteSource for DirectUrlBuiltDist {
1047    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1048        self.url.filename()
1049    }
1050
1051    fn size(&self) -> Option<u64> {
1052        self.url.size()
1053    }
1054}
1055
1056impl RemoteSource for DirectUrlSourceDist {
1057    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1058        self.url.filename()
1059    }
1060
1061    fn size(&self) -> Option<u64> {
1062        self.url.size()
1063    }
1064}
1065
1066impl RemoteSource for GitSourceDist {
1067    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1068        // The filename is the last segment of the URL, before any `@`.
1069        match self.url.filename()? {
1070            Cow::Borrowed(filename) => {
1071                if let Some((_, filename)) = filename.rsplit_once('@') {
1072                    Ok(Cow::Borrowed(filename))
1073                } else {
1074                    Ok(Cow::Borrowed(filename))
1075                }
1076            }
1077            Cow::Owned(filename) => {
1078                if let Some((_, filename)) = filename.rsplit_once('@') {
1079                    Ok(Cow::Owned(filename.to_owned()))
1080                } else {
1081                    Ok(Cow::Owned(filename))
1082                }
1083            }
1084        }
1085    }
1086
1087    fn size(&self) -> Option<u64> {
1088        self.url.size()
1089    }
1090}
1091
1092impl RemoteSource for PathBuiltDist {
1093    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1094        self.url.filename()
1095    }
1096
1097    fn size(&self) -> Option<u64> {
1098        self.url.size()
1099    }
1100}
1101
1102impl RemoteSource for PathSourceDist {
1103    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1104        self.url.filename()
1105    }
1106
1107    fn size(&self) -> Option<u64> {
1108        self.url.size()
1109    }
1110}
1111
1112impl RemoteSource for DirectorySourceDist {
1113    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1114        self.url.filename()
1115    }
1116
1117    fn size(&self) -> Option<u64> {
1118        self.url.size()
1119    }
1120}
1121
1122impl RemoteSource for SourceDist {
1123    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1124        match self {
1125            Self::Registry(dist) => dist.filename(),
1126            Self::DirectUrl(dist) => dist.filename(),
1127            Self::Git(dist) => dist.filename(),
1128            Self::Path(dist) => dist.filename(),
1129            Self::Directory(dist) => dist.filename(),
1130        }
1131    }
1132
1133    fn size(&self) -> Option<u64> {
1134        match self {
1135            Self::Registry(dist) => dist.size(),
1136            Self::DirectUrl(dist) => dist.size(),
1137            Self::Git(dist) => dist.size(),
1138            Self::Path(dist) => dist.size(),
1139            Self::Directory(dist) => dist.size(),
1140        }
1141    }
1142}
1143
1144impl RemoteSource for BuiltDist {
1145    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1146        match self {
1147            Self::Registry(dist) => dist.filename(),
1148            Self::DirectUrl(dist) => dist.filename(),
1149            Self::Path(dist) => dist.filename(),
1150        }
1151    }
1152
1153    fn size(&self) -> Option<u64> {
1154        match self {
1155            Self::Registry(dist) => dist.size(),
1156            Self::DirectUrl(dist) => dist.size(),
1157            Self::Path(dist) => dist.size(),
1158        }
1159    }
1160}
1161
1162impl RemoteSource for Dist {
1163    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1164        match self {
1165            Self::Built(dist) => dist.filename(),
1166            Self::Source(dist) => dist.filename(),
1167        }
1168    }
1169
1170    fn size(&self) -> Option<u64> {
1171        match self {
1172            Self::Built(dist) => dist.size(),
1173            Self::Source(dist) => dist.size(),
1174        }
1175    }
1176}
1177
1178impl Identifier for DisplaySafeUrl {
1179    fn distribution_id(&self) -> DistributionId {
1180        DistributionId::Url(uv_cache_key::CanonicalUrl::new(self))
1181    }
1182
1183    fn resource_id(&self) -> ResourceId {
1184        ResourceId::Url(uv_cache_key::RepositoryUrl::new(self))
1185    }
1186}
1187
1188impl Identifier for File {
1189    fn distribution_id(&self) -> DistributionId {
1190        self.hashes
1191            .first()
1192            .cloned()
1193            .map(DistributionId::Digest)
1194            .unwrap_or_else(|| self.url.distribution_id())
1195    }
1196
1197    fn resource_id(&self) -> ResourceId {
1198        self.hashes
1199            .first()
1200            .cloned()
1201            .map(ResourceId::Digest)
1202            .unwrap_or_else(|| self.url.resource_id())
1203    }
1204}
1205
1206impl Identifier for Path {
1207    fn distribution_id(&self) -> DistributionId {
1208        DistributionId::PathBuf(self.to_path_buf())
1209    }
1210
1211    fn resource_id(&self) -> ResourceId {
1212        ResourceId::PathBuf(self.to_path_buf())
1213    }
1214}
1215
1216impl Identifier for FileLocation {
1217    fn distribution_id(&self) -> DistributionId {
1218        match self {
1219            Self::RelativeUrl(base, url) => {
1220                DistributionId::RelativeUrl(base.to_string(), url.to_string())
1221            }
1222            Self::AbsoluteUrl(url) => DistributionId::AbsoluteUrl(url.to_string()),
1223        }
1224    }
1225
1226    fn resource_id(&self) -> ResourceId {
1227        match self {
1228            Self::RelativeUrl(base, url) => {
1229                ResourceId::RelativeUrl(base.to_string(), url.to_string())
1230            }
1231            Self::AbsoluteUrl(url) => ResourceId::AbsoluteUrl(url.to_string()),
1232        }
1233    }
1234}
1235
1236impl Identifier for RegistryBuiltWheel {
1237    fn distribution_id(&self) -> DistributionId {
1238        self.file.distribution_id()
1239    }
1240
1241    fn resource_id(&self) -> ResourceId {
1242        self.file.resource_id()
1243    }
1244}
1245
1246impl Identifier for RegistryBuiltDist {
1247    fn distribution_id(&self) -> DistributionId {
1248        self.best_wheel().distribution_id()
1249    }
1250
1251    fn resource_id(&self) -> ResourceId {
1252        self.best_wheel().resource_id()
1253    }
1254}
1255
1256impl Identifier for RegistrySourceDist {
1257    fn distribution_id(&self) -> DistributionId {
1258        self.file.distribution_id()
1259    }
1260
1261    fn resource_id(&self) -> ResourceId {
1262        self.file.resource_id()
1263    }
1264}
1265
1266impl Identifier for DirectUrlBuiltDist {
1267    fn distribution_id(&self) -> DistributionId {
1268        self.url.distribution_id()
1269    }
1270
1271    fn resource_id(&self) -> ResourceId {
1272        self.url.resource_id()
1273    }
1274}
1275
1276impl Identifier for DirectUrlSourceDist {
1277    fn distribution_id(&self) -> DistributionId {
1278        self.url.distribution_id()
1279    }
1280
1281    fn resource_id(&self) -> ResourceId {
1282        self.url.resource_id()
1283    }
1284}
1285
1286impl Identifier for PathBuiltDist {
1287    fn distribution_id(&self) -> DistributionId {
1288        self.url.distribution_id()
1289    }
1290
1291    fn resource_id(&self) -> ResourceId {
1292        self.url.resource_id()
1293    }
1294}
1295
1296impl Identifier for PathSourceDist {
1297    fn distribution_id(&self) -> DistributionId {
1298        self.url.distribution_id()
1299    }
1300
1301    fn resource_id(&self) -> ResourceId {
1302        self.url.resource_id()
1303    }
1304}
1305
1306impl Identifier for DirectorySourceDist {
1307    fn distribution_id(&self) -> DistributionId {
1308        self.url.distribution_id()
1309    }
1310
1311    fn resource_id(&self) -> ResourceId {
1312        self.url.resource_id()
1313    }
1314}
1315
1316impl Identifier for GitSourceDist {
1317    fn distribution_id(&self) -> DistributionId {
1318        self.url.distribution_id()
1319    }
1320
1321    fn resource_id(&self) -> ResourceId {
1322        self.url.resource_id()
1323    }
1324}
1325
1326impl Identifier for SourceDist {
1327    fn distribution_id(&self) -> DistributionId {
1328        match self {
1329            Self::Registry(dist) => dist.distribution_id(),
1330            Self::DirectUrl(dist) => dist.distribution_id(),
1331            Self::Git(dist) => dist.distribution_id(),
1332            Self::Path(dist) => dist.distribution_id(),
1333            Self::Directory(dist) => dist.distribution_id(),
1334        }
1335    }
1336
1337    fn resource_id(&self) -> ResourceId {
1338        match self {
1339            Self::Registry(dist) => dist.resource_id(),
1340            Self::DirectUrl(dist) => dist.resource_id(),
1341            Self::Git(dist) => dist.resource_id(),
1342            Self::Path(dist) => dist.resource_id(),
1343            Self::Directory(dist) => dist.resource_id(),
1344        }
1345    }
1346}
1347
1348impl Identifier for BuiltDist {
1349    fn distribution_id(&self) -> DistributionId {
1350        match self {
1351            Self::Registry(dist) => dist.distribution_id(),
1352            Self::DirectUrl(dist) => dist.distribution_id(),
1353            Self::Path(dist) => dist.distribution_id(),
1354        }
1355    }
1356
1357    fn resource_id(&self) -> ResourceId {
1358        match self {
1359            Self::Registry(dist) => dist.resource_id(),
1360            Self::DirectUrl(dist) => dist.resource_id(),
1361            Self::Path(dist) => dist.resource_id(),
1362        }
1363    }
1364}
1365
1366impl Identifier for InstalledDist {
1367    fn distribution_id(&self) -> DistributionId {
1368        self.install_path().distribution_id()
1369    }
1370
1371    fn resource_id(&self) -> ResourceId {
1372        self.install_path().resource_id()
1373    }
1374}
1375
1376impl Identifier for Dist {
1377    fn distribution_id(&self) -> DistributionId {
1378        match self {
1379            Self::Built(dist) => dist.distribution_id(),
1380            Self::Source(dist) => dist.distribution_id(),
1381        }
1382    }
1383
1384    fn resource_id(&self) -> ResourceId {
1385        match self {
1386            Self::Built(dist) => dist.resource_id(),
1387            Self::Source(dist) => dist.resource_id(),
1388        }
1389    }
1390}
1391
1392impl Identifier for DirectSourceUrl<'_> {
1393    fn distribution_id(&self) -> DistributionId {
1394        self.url.distribution_id()
1395    }
1396
1397    fn resource_id(&self) -> ResourceId {
1398        self.url.resource_id()
1399    }
1400}
1401
1402impl Identifier for GitSourceUrl<'_> {
1403    fn distribution_id(&self) -> DistributionId {
1404        self.url.distribution_id()
1405    }
1406
1407    fn resource_id(&self) -> ResourceId {
1408        self.url.resource_id()
1409    }
1410}
1411
1412impl Identifier for PathSourceUrl<'_> {
1413    fn distribution_id(&self) -> DistributionId {
1414        self.url.distribution_id()
1415    }
1416
1417    fn resource_id(&self) -> ResourceId {
1418        self.url.resource_id()
1419    }
1420}
1421
1422impl Identifier for DirectorySourceUrl<'_> {
1423    fn distribution_id(&self) -> DistributionId {
1424        self.url.distribution_id()
1425    }
1426
1427    fn resource_id(&self) -> ResourceId {
1428        self.url.resource_id()
1429    }
1430}
1431
1432impl Identifier for SourceUrl<'_> {
1433    fn distribution_id(&self) -> DistributionId {
1434        match self {
1435            Self::Direct(url) => url.distribution_id(),
1436            Self::Git(url) => url.distribution_id(),
1437            Self::Path(url) => url.distribution_id(),
1438            Self::Directory(url) => url.distribution_id(),
1439        }
1440    }
1441
1442    fn resource_id(&self) -> ResourceId {
1443        match self {
1444            Self::Direct(url) => url.resource_id(),
1445            Self::Git(url) => url.resource_id(),
1446            Self::Path(url) => url.resource_id(),
1447            Self::Directory(url) => url.resource_id(),
1448        }
1449    }
1450}
1451
1452impl Identifier for BuildableSource<'_> {
1453    fn distribution_id(&self) -> DistributionId {
1454        match self {
1455            Self::Dist(source) => source.distribution_id(),
1456            Self::Url(source) => source.distribution_id(),
1457        }
1458    }
1459
1460    fn resource_id(&self) -> ResourceId {
1461        match self {
1462            Self::Dist(source) => source.resource_id(),
1463            Self::Url(source) => source.resource_id(),
1464        }
1465    }
1466}
1467
1468#[cfg(test)]
1469mod test {
1470    use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
1471    use uv_redacted::DisplaySafeUrl;
1472
1473    /// Ensure that we don't accidentally grow the `Dist` sizes.
1474    #[test]
1475    fn dist_size() {
1476        assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>());
1477        assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>());
1478        assert!(
1479            size_of::<SourceDist>() <= 176,
1480            "{}",
1481            size_of::<SourceDist>()
1482        );
1483    }
1484
1485    #[test]
1486    fn remote_source() {
1487        for url in [
1488            "https://example.com/foo-0.1.0.tar.gz",
1489            "https://example.com/foo-0.1.0.tar.gz#fragment",
1490            "https://example.com/foo-0.1.0.tar.gz?query",
1491            "https://example.com/foo-0.1.0.tar.gz?query#fragment",
1492            "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment",
1493            "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment/3",
1494        ] {
1495            let url = DisplaySafeUrl::parse(url).unwrap();
1496            assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1497            let url = UrlString::from(url.clone());
1498            assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1499        }
1500    }
1501}