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