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