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