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/// A source distribution that exists in a local directory.
387#[derive(Debug, Clone, Hash, PartialEq, Eq)]
388pub struct DirectorySourceDist {
389    pub name: PackageName,
390    /// The absolute path to the distribution which we use for installing.
391    pub install_path: Box<Path>,
392    /// Whether the package should be installed in editable mode.
393    pub editable: Option<bool>,
394    /// Whether the package should be built and installed.
395    pub r#virtual: Option<bool>,
396    /// The URL as it was provided by the user.
397    pub url: VerbatimUrl,
398}
399
400impl Dist {
401    /// A remote built distribution (`.whl`) or source distribution from a `http://` or `https://`
402    /// URL.
403    pub fn from_http_url(
404        name: PackageName,
405        url: VerbatimUrl,
406        location: DisplaySafeUrl,
407        subdirectory: Option<Box<Path>>,
408        ext: DistExtension,
409    ) -> Result<Self, Error> {
410        match ext {
411            DistExtension::Wheel => {
412                // Validate that the name in the wheel matches that of the requirement.
413                let filename = WheelFilename::from_str(&url.filename()?)?;
414                if filename.name != name {
415                    return Err(Error::PackageNameMismatch(
416                        name,
417                        filename.name,
418                        url.verbatim().to_string(),
419                    ));
420                }
421
422                Ok(Self::Built(BuiltDist::DirectUrl(DirectUrlBuiltDist {
423                    filename,
424                    location: Box::new(location),
425                    url,
426                    size: None,
427                })))
428            }
429            DistExtension::Source(ext) => {
430                if !ext.is_pep625_compliant() {
431                    return Err(Error::NotPep625Filename(url.verbatim().to_string()));
432                }
433                Ok(Self::Source(SourceDist::DirectUrl(DirectUrlSourceDist {
434                    name,
435                    location: Box::new(location),
436                    subdirectory,
437                    ext,
438                    url,
439                    size: None,
440                })))
441            }
442        }
443    }
444
445    /// A local built or source distribution from a `file://` URL.
446    pub fn from_file_url(
447        name: PackageName,
448        url: VerbatimUrl,
449        install_path: &Path,
450        ext: DistExtension,
451    ) -> Result<Self, Error> {
452        // Convert to an absolute path.
453        let install_path = path::absolute(install_path)?;
454
455        // Normalize the path.
456        let install_path = normalize_absolute_path(&install_path)?;
457
458        // Validate that the path exists.
459        if !install_path.exists() {
460            return Err(Error::NotFound(url.to_url()));
461        }
462
463        // Determine whether the path represents a built or source distribution.
464        match ext {
465            DistExtension::Wheel => {
466                // Validate that the name in the wheel matches that of the requirement.
467                let filename = install_path
468                    .file_name()
469                    .and_then(OsStr::to_str)
470                    .ok_or_else(|| Error::MissingWheelFilename(install_path.clone()))?;
471                let filename = WheelFilename::from_str(filename)?;
472                if filename.name != name {
473                    return Err(Error::PackageNameMismatch(
474                        name,
475                        filename.name,
476                        url.verbatim().to_string(),
477                    ));
478                }
479                Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
480                    filename,
481                    install_path: install_path.into_boxed_path(),
482                    url,
483                })))
484            }
485            DistExtension::Source(ext) => {
486                if !ext.is_pep625_compliant() {
487                    return Err(Error::NotPep625Filename(url.verbatim().to_string()));
488                }
489
490                // If there is a version in the filename, record it.
491                let version = url
492                    .filename()
493                    .ok()
494                    .and_then(|filename| {
495                        SourceDistFilename::parse(filename.as_ref(), ext, &name).ok()
496                    })
497                    .map(|filename| filename.version);
498
499                Ok(Self::Source(SourceDist::Path(PathSourceDist {
500                    name,
501                    version,
502                    install_path: install_path.into_boxed_path(),
503                    ext,
504                    url,
505                })))
506            }
507        }
508    }
509
510    /// A local source tree from a `file://` URL.
511    pub fn from_directory_url(
512        name: PackageName,
513        url: VerbatimUrl,
514        install_path: &Path,
515        editable: Option<bool>,
516        r#virtual: Option<bool>,
517    ) -> Result<Self, Error> {
518        // Convert to an absolute path.
519        let install_path = path::absolute(install_path)?;
520
521        // Normalize the path.
522        let install_path = normalize_absolute_path(&install_path)?;
523
524        // Validate that the path exists.
525        if !install_path.exists() {
526            return Err(Error::NotFound(url.to_url()));
527        }
528
529        // Determine whether the path represents an archive or a directory.
530        Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
531            name,
532            install_path: install_path.into_boxed_path(),
533            editable,
534            r#virtual,
535            url,
536        })))
537    }
538
539    /// Create a [`Dist`] for a source tree within a Git repository (i.e., a `git+https://` or `git+ssh://` URL).
540    pub fn from_git_directory_url(
541        name: PackageName,
542        url: VerbatimUrl,
543        git: GitUrl,
544        subdirectory: Option<Box<Path>>,
545    ) -> Result<Self, Error> {
546        Ok(Self::Source(SourceDist::GitDirectory(
547            GitDirectorySourceDist {
548                name,
549                git: Box::new(git),
550                subdirectory,
551                url,
552            },
553        )))
554    }
555
556    /// Create a [`Dist`] for a source archive within a Git repository (i.e., a `git+https://` or `git+ssh://` URL).
557    pub fn from_git_path_url(
558        name: PackageName,
559        url: VerbatimUrl,
560        git: GitUrl,
561        install_path: PathBuf,
562        ext: DistExtension,
563    ) -> Result<Self, Error> {
564        match ext {
565            DistExtension::Wheel => {
566                // Validate that the name in the wheel matches that of the requirement.
567                let filename = install_path
568                    .file_name()
569                    .and_then(OsStr::to_str)
570                    .ok_or_else(|| Error::MissingWheelFilename(install_path.clone()))?;
571                let filename = WheelFilename::from_str(filename)?;
572                if filename.name != name {
573                    return Err(Error::PackageNameMismatch(
574                        name,
575                        filename.name,
576                        url.verbatim().to_string(),
577                    ));
578                }
579
580                Ok(Self::Built(BuiltDist::GitPath(GitPathBuiltDist {
581                    filename,
582                    git: Box::new(git),
583                    install_path,
584                    url,
585                })))
586            }
587            DistExtension::Source(ext) => {
588                Ok(Self::Source(SourceDist::GitPath(GitPathSourceDist {
589                    name,
590                    git: Box::new(git),
591                    install_path,
592                    ext,
593                    url,
594                })))
595            }
596        }
597    }
598
599    /// Create a [`Dist`] for a URL-based distribution.
600    pub fn from_url(name: PackageName, url: VerbatimParsedUrl) -> Result<Self, Error> {
601        match url.parsed_url {
602            ParsedUrl::Archive(archive) => Self::from_http_url(
603                name,
604                url.verbatim,
605                archive.url,
606                archive.subdirectory,
607                archive.ext,
608            ),
609            ParsedUrl::Path(file) => {
610                Self::from_file_url(name, url.verbatim, &file.install_path, file.ext)
611            }
612            ParsedUrl::Directory(directory) => Self::from_directory_url(
613                name,
614                url.verbatim,
615                &directory.install_path,
616                directory.editable,
617                directory.r#virtual,
618            ),
619            ParsedUrl::GitDirectory(git) => {
620                Self::from_git_directory_url(name, url.verbatim, git.url, git.subdirectory)
621            }
622            ParsedUrl::GitPath(git) => {
623                Self::from_git_path_url(name, url.verbatim, git.url, git.install_path, git.ext)
624            }
625        }
626    }
627
628    /// Return true if the distribution is editable.
629    fn is_editable(&self) -> bool {
630        match self {
631            Self::Source(dist) => dist.is_editable(),
632            Self::Built(_) => false,
633        }
634    }
635
636    /// Return true if the distribution refers to a local file or directory.
637    fn is_local(&self) -> bool {
638        match self {
639            Self::Source(dist) => dist.is_local(),
640            Self::Built(dist) => dist.is_local(),
641        }
642    }
643
644    /// Returns the [`IndexUrl`], if the distribution is from a registry.
645    pub fn index(&self) -> Option<&IndexUrl> {
646        match self {
647            Self::Built(dist) => dist.index(),
648            Self::Source(dist) => dist.index(),
649        }
650    }
651
652    /// Returns the [`File`] instance, if this dist is from a registry with simple json api support
653    pub fn file(&self) -> Option<&File> {
654        match self {
655            Self::Built(built) => built.file(),
656            Self::Source(source) => source.file(),
657        }
658    }
659
660    /// Return the source tree of the distribution, if available.
661    pub fn source_tree(&self) -> Option<&Path> {
662        match self {
663            Self::Built { .. } => None,
664            Self::Source(source) => source.source_tree(),
665        }
666    }
667
668    /// Returns the version of the distribution, if it is known.
669    pub fn version(&self) -> Option<&Version> {
670        match self {
671            Self::Built(wheel) => Some(wheel.version()),
672            Self::Source(source_dist) => source_dist.version(),
673        }
674    }
675}
676
677impl<'a> From<&'a Dist> for DistRef<'a> {
678    fn from(dist: &'a Dist) -> Self {
679        match dist {
680            Dist::Built(built) => DistRef::Built(built),
681            Dist::Source(source) => DistRef::Source(source),
682        }
683    }
684}
685
686impl<'a> From<&'a SourceDist> for DistRef<'a> {
687    fn from(dist: &'a SourceDist) -> Self {
688        DistRef::Source(dist)
689    }
690}
691
692impl<'a> From<&'a BuiltDist> for DistRef<'a> {
693    fn from(dist: &'a BuiltDist) -> Self {
694        DistRef::Built(dist)
695    }
696}
697
698impl BuiltDist {
699    /// Return true if the distribution refers to a local file or directory.
700    fn is_local(&self) -> bool {
701        matches!(self, Self::Path(_))
702    }
703
704    /// Returns the [`IndexUrl`], if the distribution is from a registry.
705    pub fn index(&self) -> Option<&IndexUrl> {
706        match self {
707            Self::Registry(registry) => Some(&registry.best_wheel().index),
708            Self::DirectUrl(_) => None,
709            Self::Path(_) => None,
710            Self::GitPath(_) => None,
711        }
712    }
713
714    /// Returns the [`File`] instance, if this distribution is from a registry.
715    fn file(&self) -> Option<&File> {
716        match self {
717            Self::Registry(registry) => Some(&registry.best_wheel().file),
718            Self::DirectUrl(_) | Self::Path(_) | Self::GitPath(_) => None,
719        }
720    }
721
722    pub fn version(&self) -> &Version {
723        match self {
724            Self::Registry(wheels) => &wheels.best_wheel().filename.version,
725            Self::DirectUrl(wheel) => &wheel.filename.version,
726            Self::Path(wheel) => &wheel.filename.version,
727            Self::GitPath(wheel) => &wheel.filename.version,
728        }
729    }
730}
731
732impl SourceDist {
733    /// Returns the [`IndexUrl`], if the distribution is from a registry.
734    fn index(&self) -> Option<&IndexUrl> {
735        match self {
736            Self::Registry(registry) => Some(&registry.index),
737            Self::DirectUrl(_)
738            | Self::GitPath(_)
739            | Self::GitDirectory(_)
740            | Self::Path(_)
741            | Self::Directory(_) => None,
742        }
743    }
744
745    /// Returns the [`File`] instance, if this dist is from a registry with simple json api support
746    fn file(&self) -> Option<&File> {
747        match self {
748            Self::Registry(registry) => Some(&registry.file),
749            Self::DirectUrl(_)
750            | Self::GitPath(_)
751            | Self::GitDirectory(_)
752            | Self::Path(_)
753            | Self::Directory(_) => None,
754        }
755    }
756
757    /// Returns the [`Version`] of the distribution, if it is known.
758    pub fn version(&self) -> Option<&Version> {
759        match self {
760            Self::Registry(source_dist) => Some(&source_dist.version),
761            Self::DirectUrl(_)
762            | Self::GitPath(_)
763            | Self::GitDirectory(_)
764            | Self::Path(_)
765            | Self::Directory(_) => None,
766        }
767    }
768
769    /// Returns `true` if the distribution is editable.
770    pub fn is_editable(&self) -> bool {
771        match self {
772            Self::Directory(DirectorySourceDist { editable, .. }) => editable.unwrap_or(false),
773            _ => false,
774        }
775    }
776
777    /// Returns `true` if the distribution is virtual.
778    pub fn is_virtual(&self) -> bool {
779        match self {
780            Self::Directory(DirectorySourceDist { r#virtual, .. }) => r#virtual.unwrap_or(false),
781            _ => false,
782        }
783    }
784
785    /// Returns `true` if the distribution refers to a local file or directory.
786    fn is_local(&self) -> bool {
787        matches!(self, Self::Directory(_) | Self::Path(_))
788    }
789
790    /// Returns the path to the source distribution, if it's a local distribution.
791    pub fn as_path(&self) -> Option<&Path> {
792        match self {
793            Self::Path(dist) => Some(&dist.install_path),
794            Self::Directory(dist) => Some(&dist.install_path),
795            _ => None,
796        }
797    }
798
799    /// Returns the source tree of the distribution, if available.
800    fn source_tree(&self) -> Option<&Path> {
801        match self {
802            Self::Directory(dist) => Some(&dist.install_path),
803            _ => None,
804        }
805    }
806}
807
808impl RegistryBuiltDist {
809    /// Returns the best or "most compatible" wheel in this distribution.
810    pub fn best_wheel(&self) -> &RegistryBuiltWheel {
811        &self.wheels[self.best_wheel_index]
812    }
813}
814
815impl DirectUrlBuiltDist {
816    /// Return the [`ParsedUrl`] for the distribution.
817    pub fn to_parsed_url(&self) -> ParsedUrl {
818        ParsedUrl::Archive(ParsedArchiveUrl::from_source(
819            (*self.location).clone(),
820            None,
821            DistExtension::Wheel,
822        ))
823    }
824}
825
826impl PathBuiltDist {
827    /// Return the [`ParsedUrl`] for the distribution.
828    pub fn to_parsed_url(&self) -> ParsedUrl {
829        ParsedUrl::Path(ParsedPathUrl::from_source(
830            self.install_path.clone(),
831            DistExtension::Wheel,
832            self.url.to_url(),
833        ))
834    }
835}
836
837impl PathSourceDist {
838    /// Return the [`ParsedUrl`] for the distribution.
839    pub fn to_parsed_url(&self) -> ParsedUrl {
840        ParsedUrl::Path(ParsedPathUrl::from_source(
841            self.install_path.clone(),
842            DistExtension::Source(self.ext),
843            self.url.to_url(),
844        ))
845    }
846}
847
848impl DirectUrlSourceDist {
849    /// Return the [`ParsedUrl`] for the distribution.
850    pub fn to_parsed_url(&self) -> ParsedUrl {
851        ParsedUrl::Archive(ParsedArchiveUrl::from_source(
852            (*self.location).clone(),
853            self.subdirectory.clone(),
854            DistExtension::Source(self.ext),
855        ))
856    }
857}
858
859impl GitDirectorySourceDist {
860    /// Return the [`ParsedUrl`] for the distribution.
861    pub fn to_parsed_url(&self) -> ParsedUrl {
862        ParsedUrl::GitDirectory(ParsedGitDirectoryUrl::from_source(
863            (*self.git).clone(),
864            self.subdirectory.clone(),
865        ))
866    }
867}
868
869impl GitPathBuiltDist {
870    /// Return the [`ParsedUrl`] for the distribution.
871    pub fn to_parsed_url(&self) -> ParsedUrl {
872        ParsedUrl::GitPath(ParsedGitPathUrl::from_source(
873            (*self.git).clone(),
874            self.install_path.clone(),
875            DistExtension::Wheel,
876        ))
877    }
878}
879
880impl GitPathSourceDist {
881    /// Return the [`ParsedUrl`] for the distribution.
882    pub fn to_parsed_url(&self) -> ParsedUrl {
883        ParsedUrl::GitPath(ParsedGitPathUrl::from_source(
884            (*self.git).clone(),
885            self.install_path.clone(),
886            DistExtension::Source(self.ext),
887        ))
888    }
889}
890
891impl DirectorySourceDist {
892    /// Return the [`ParsedUrl`] for the distribution.
893    pub fn to_parsed_url(&self) -> ParsedUrl {
894        ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
895            self.install_path.clone(),
896            self.editable,
897            self.r#virtual,
898            self.url.to_url(),
899        ))
900    }
901}
902
903impl Name for RegistryBuiltWheel {
904    fn name(&self) -> &PackageName {
905        &self.filename.name
906    }
907}
908
909impl Name for RegistryBuiltDist {
910    fn name(&self) -> &PackageName {
911        self.best_wheel().name()
912    }
913}
914
915impl Name for DirectUrlBuiltDist {
916    fn name(&self) -> &PackageName {
917        &self.filename.name
918    }
919}
920
921impl Name for PathBuiltDist {
922    fn name(&self) -> &PackageName {
923        &self.filename.name
924    }
925}
926
927impl Name for GitPathBuiltDist {
928    fn name(&self) -> &PackageName {
929        &self.filename.name
930    }
931}
932
933impl Name for RegistrySourceDist {
934    fn name(&self) -> &PackageName {
935        &self.name
936    }
937}
938
939impl Name for DirectUrlSourceDist {
940    fn name(&self) -> &PackageName {
941        &self.name
942    }
943}
944
945impl Name for GitPathSourceDist {
946    fn name(&self) -> &PackageName {
947        &self.name
948    }
949}
950
951impl Name for GitDirectorySourceDist {
952    fn name(&self) -> &PackageName {
953        &self.name
954    }
955}
956
957impl Name for PathSourceDist {
958    fn name(&self) -> &PackageName {
959        &self.name
960    }
961}
962
963impl Name for DirectorySourceDist {
964    fn name(&self) -> &PackageName {
965        &self.name
966    }
967}
968
969impl Name for SourceDist {
970    fn name(&self) -> &PackageName {
971        match self {
972            Self::Registry(dist) => dist.name(),
973            Self::DirectUrl(dist) => dist.name(),
974            Self::GitPath(dist) => dist.name(),
975            Self::GitDirectory(dist) => dist.name(),
976            Self::Path(dist) => dist.name(),
977            Self::Directory(dist) => dist.name(),
978        }
979    }
980}
981
982impl Name for BuiltDist {
983    fn name(&self) -> &PackageName {
984        match self {
985            Self::Registry(dist) => dist.name(),
986            Self::DirectUrl(dist) => dist.name(),
987            Self::Path(dist) => dist.name(),
988            Self::GitPath(dist) => dist.name(),
989        }
990    }
991}
992
993impl Name for Dist {
994    fn name(&self) -> &PackageName {
995        match self {
996            Self::Built(dist) => dist.name(),
997            Self::Source(dist) => dist.name(),
998        }
999    }
1000}
1001
1002impl Name for CompatibleDist<'_> {
1003    fn name(&self) -> &PackageName {
1004        match self {
1005            Self::InstalledDist(dist) => dist.name(),
1006            Self::SourceDist {
1007                sdist,
1008                prioritized: _,
1009            } => sdist.name(),
1010            Self::CompatibleWheel {
1011                wheel,
1012                priority: _,
1013                prioritized: _,
1014            } => wheel.name(),
1015            Self::IncompatibleWheel {
1016                sdist,
1017                wheel: _,
1018                prioritized: _,
1019            } => sdist.name(),
1020        }
1021    }
1022}
1023
1024impl DistributionMetadata for RegistryBuiltWheel {
1025    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1026        VersionOrUrlRef::Version(&self.filename.version)
1027    }
1028}
1029
1030impl DistributionMetadata for RegistryBuiltDist {
1031    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1032        self.best_wheel().version_or_url()
1033    }
1034}
1035
1036impl DistributionMetadata for DirectUrlBuiltDist {
1037    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1038        VersionOrUrlRef::Url(&self.url)
1039    }
1040
1041    fn version_id(&self) -> VersionId {
1042        VersionId::from_archive(self.location.as_ref().clone(), None)
1043    }
1044}
1045
1046impl DistributionMetadata for PathBuiltDist {
1047    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1048        VersionOrUrlRef::Url(&self.url)
1049    }
1050
1051    fn version_id(&self) -> VersionId {
1052        VersionId::from_path(self.install_path.as_ref())
1053    }
1054}
1055
1056impl DistributionMetadata for GitPathBuiltDist {
1057    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1058        VersionOrUrlRef::Url(&self.url)
1059    }
1060}
1061
1062impl DistributionMetadata for RegistrySourceDist {
1063    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1064        VersionOrUrlRef::Version(&self.version)
1065    }
1066}
1067
1068impl DistributionMetadata for DirectUrlSourceDist {
1069    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1070        VersionOrUrlRef::Url(&self.url)
1071    }
1072
1073    fn version_id(&self) -> VersionId {
1074        VersionId::from_archive(
1075            self.location.as_ref().clone(),
1076            self.subdirectory.clone().map(Path::into_path_buf),
1077        )
1078    }
1079}
1080
1081impl DistributionMetadata for GitPathSourceDist {
1082    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1083        VersionOrUrlRef::Url(&self.url)
1084    }
1085
1086    fn version_id(&self) -> VersionId {
1087        VersionId::from_git(self.git.as_ref(), Some(&self.install_path))
1088    }
1089}
1090
1091impl DistributionMetadata for GitDirectorySourceDist {
1092    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1093        VersionOrUrlRef::Url(&self.url)
1094    }
1095
1096    fn version_id(&self) -> VersionId {
1097        VersionId::from_git(self.git.as_ref(), self.subdirectory.as_deref())
1098    }
1099}
1100
1101impl DistributionMetadata for PathSourceDist {
1102    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1103        VersionOrUrlRef::Url(&self.url)
1104    }
1105
1106    fn version_id(&self) -> VersionId {
1107        VersionId::from_path(self.install_path.as_ref())
1108    }
1109}
1110
1111impl DistributionMetadata for DirectorySourceDist {
1112    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1113        VersionOrUrlRef::Url(&self.url)
1114    }
1115
1116    fn version_id(&self) -> VersionId {
1117        VersionId::from_directory(self.install_path.as_ref())
1118    }
1119}
1120
1121impl DistributionMetadata for SourceDist {
1122    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1123        match self {
1124            Self::Registry(dist) => dist.version_or_url(),
1125            Self::DirectUrl(dist) => dist.version_or_url(),
1126            Self::GitPath(dist) => dist.version_or_url(),
1127            Self::GitDirectory(dist) => dist.version_or_url(),
1128            Self::Path(dist) => dist.version_or_url(),
1129            Self::Directory(dist) => dist.version_or_url(),
1130        }
1131    }
1132
1133    fn version_id(&self) -> VersionId {
1134        match self {
1135            Self::Registry(dist) => dist.version_id(),
1136            Self::DirectUrl(dist) => dist.version_id(),
1137            Self::GitPath(dist) => dist.version_id(),
1138            Self::GitDirectory(dist) => dist.version_id(),
1139            Self::Path(dist) => dist.version_id(),
1140            Self::Directory(dist) => dist.version_id(),
1141        }
1142    }
1143}
1144
1145impl DistributionMetadata for BuiltDist {
1146    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1147        match self {
1148            Self::Registry(dist) => dist.version_or_url(),
1149            Self::DirectUrl(dist) => dist.version_or_url(),
1150            Self::Path(dist) => dist.version_or_url(),
1151            Self::GitPath(dist) => dist.version_or_url(),
1152        }
1153    }
1154
1155    fn version_id(&self) -> VersionId {
1156        match self {
1157            Self::Registry(dist) => dist.version_id(),
1158            Self::DirectUrl(dist) => dist.version_id(),
1159            Self::Path(dist) => dist.version_id(),
1160            Self::GitPath(dist) => dist.version_id(),
1161        }
1162    }
1163}
1164
1165impl DistributionMetadata for Dist {
1166    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1167        match self {
1168            Self::Built(dist) => dist.version_or_url(),
1169            Self::Source(dist) => dist.version_or_url(),
1170        }
1171    }
1172
1173    fn version_id(&self) -> VersionId {
1174        match self {
1175            Self::Built(dist) => dist.version_id(),
1176            Self::Source(dist) => dist.version_id(),
1177        }
1178    }
1179}
1180
1181impl RemoteSource for File {
1182    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1183        Ok(Cow::Borrowed(&self.filename))
1184    }
1185
1186    fn size(&self) -> Option<u64> {
1187        self.size
1188    }
1189}
1190
1191impl RemoteSource for Url {
1192    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1193        // Identify the last segment of the URL as the filename.
1194        let mut path_segments = self
1195            .path_segments()
1196            .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1197
1198        // This is guaranteed by the contract of `Url::path_segments`.
1199        let last = path_segments
1200            .next_back()
1201            .expect("path segments is non-empty");
1202
1203        // Decode the filename, which may be percent-encoded.
1204        let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1205
1206        Ok(filename)
1207    }
1208
1209    fn size(&self) -> Option<u64> {
1210        None
1211    }
1212}
1213
1214impl RemoteSource for UrlString {
1215    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1216        let url = self.as_ref();
1217        if memchr3(b'?', b'#', b'%', url.as_bytes()).is_none()
1218            && let Some((_, filename)) = url.rsplit_once('/')
1219        {
1220            return Ok(Cow::Borrowed(filename));
1221        }
1222
1223        // Take the last segment, stripping any query or fragment.
1224        let last = self
1225            .base_str()
1226            .split('/')
1227            .next_back()
1228            .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1229
1230        // Decode the filename, which may be percent-encoded.
1231        let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1232
1233        Ok(filename)
1234    }
1235
1236    fn size(&self) -> Option<u64> {
1237        None
1238    }
1239}
1240
1241impl RemoteSource for RegistryBuiltWheel {
1242    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1243        self.file.filename()
1244    }
1245
1246    fn size(&self) -> Option<u64> {
1247        self.file.size()
1248    }
1249}
1250
1251impl RemoteSource for RegistryBuiltDist {
1252    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1253        self.best_wheel().filename()
1254    }
1255
1256    fn size(&self) -> Option<u64> {
1257        self.best_wheel().size()
1258    }
1259}
1260
1261impl RemoteSource for RegistrySourceDist {
1262    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1263        self.file.filename()
1264    }
1265
1266    fn size(&self) -> Option<u64> {
1267        self.file.size()
1268    }
1269}
1270
1271impl RemoteSource for DirectUrlBuiltDist {
1272    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1273        self.url.filename()
1274    }
1275
1276    fn size(&self) -> Option<u64> {
1277        self.size
1278    }
1279}
1280
1281impl RemoteSource for DirectUrlSourceDist {
1282    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1283        self.url.filename()
1284    }
1285
1286    fn size(&self) -> Option<u64> {
1287        self.size
1288    }
1289}
1290
1291impl RemoteSource for GitPathSourceDist {
1292    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1293        // The filename is the last segment of the URL, before any `@`.
1294        match self.url.filename()? {
1295            Cow::Borrowed(filename) => {
1296                if let Some((_, filename)) = filename.rsplit_once('@') {
1297                    Ok(Cow::Borrowed(filename))
1298                } else {
1299                    Ok(Cow::Borrowed(filename))
1300                }
1301            }
1302            Cow::Owned(filename) => {
1303                if let Some((_, filename)) = filename.rsplit_once('@') {
1304                    Ok(Cow::Owned(filename.to_owned()))
1305                } else {
1306                    Ok(Cow::Owned(filename))
1307                }
1308            }
1309        }
1310    }
1311
1312    fn size(&self) -> Option<u64> {
1313        self.url.size()
1314    }
1315}
1316
1317impl RemoteSource for GitDirectorySourceDist {
1318    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1319        // The filename is the last segment of the URL, before any `@`.
1320        match self.url.filename()? {
1321            Cow::Borrowed(filename) => {
1322                if let Some((_, filename)) = filename.rsplit_once('@') {
1323                    Ok(Cow::Borrowed(filename))
1324                } else {
1325                    Ok(Cow::Borrowed(filename))
1326                }
1327            }
1328            Cow::Owned(filename) => {
1329                if let Some((_, filename)) = filename.rsplit_once('@') {
1330                    Ok(Cow::Owned(filename.to_owned()))
1331                } else {
1332                    Ok(Cow::Owned(filename))
1333                }
1334            }
1335        }
1336    }
1337
1338    fn size(&self) -> Option<u64> {
1339        self.url.size()
1340    }
1341}
1342
1343impl RemoteSource for PathBuiltDist {
1344    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1345        self.url.filename()
1346    }
1347
1348    fn size(&self) -> Option<u64> {
1349        self.url.size()
1350    }
1351}
1352
1353impl RemoteSource for GitPathBuiltDist {
1354    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1355        self.url.filename()
1356    }
1357
1358    fn size(&self) -> Option<u64> {
1359        self.url.size()
1360    }
1361}
1362
1363impl RemoteSource for PathSourceDist {
1364    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1365        self.url.filename()
1366    }
1367
1368    fn size(&self) -> Option<u64> {
1369        self.url.size()
1370    }
1371}
1372
1373impl RemoteSource for DirectorySourceDist {
1374    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1375        self.url.filename()
1376    }
1377
1378    fn size(&self) -> Option<u64> {
1379        self.url.size()
1380    }
1381}
1382
1383impl RemoteSource for SourceDist {
1384    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1385        match self {
1386            Self::Registry(dist) => dist.filename(),
1387            Self::DirectUrl(dist) => dist.filename(),
1388            Self::GitPath(dist) => dist.filename(),
1389            Self::GitDirectory(dist) => dist.filename(),
1390            Self::Path(dist) => dist.filename(),
1391            Self::Directory(dist) => dist.filename(),
1392        }
1393    }
1394
1395    fn size(&self) -> Option<u64> {
1396        match self {
1397            Self::Registry(dist) => dist.size(),
1398            Self::DirectUrl(dist) => dist.size(),
1399            Self::GitPath(dist) => dist.size(),
1400            Self::GitDirectory(dist) => dist.size(),
1401            Self::Path(dist) => dist.size(),
1402            Self::Directory(dist) => dist.size(),
1403        }
1404    }
1405}
1406
1407impl RemoteSource for BuiltDist {
1408    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1409        match self {
1410            Self::Registry(dist) => dist.filename(),
1411            Self::DirectUrl(dist) => dist.filename(),
1412            Self::Path(dist) => dist.filename(),
1413            Self::GitPath(dist) => dist.filename(),
1414        }
1415    }
1416
1417    fn size(&self) -> Option<u64> {
1418        match self {
1419            Self::Registry(dist) => dist.size(),
1420            Self::DirectUrl(dist) => dist.size(),
1421            Self::Path(dist) => dist.size(),
1422            Self::GitPath(dist) => dist.size(),
1423        }
1424    }
1425}
1426
1427impl RemoteSource for Dist {
1428    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1429        match self {
1430            Self::Built(dist) => dist.filename(),
1431            Self::Source(dist) => dist.filename(),
1432        }
1433    }
1434
1435    fn size(&self) -> Option<u64> {
1436        match self {
1437            Self::Built(dist) => dist.size(),
1438            Self::Source(dist) => dist.size(),
1439        }
1440    }
1441}
1442
1443impl Identifier for DisplaySafeUrl {
1444    fn distribution_id(&self) -> DistributionId {
1445        DistributionId::Url(uv_cache_key::CanonicalUrl::new(self.clone()))
1446    }
1447
1448    fn resource_id(&self) -> ResourceId {
1449        ResourceId::Url(uv_cache_key::RepositoryUrl::new(self.clone()))
1450    }
1451}
1452
1453impl Identifier for File {
1454    fn distribution_id(&self) -> DistributionId {
1455        self.hashes
1456            .first()
1457            .cloned()
1458            .map(DistributionId::Digest)
1459            .unwrap_or_else(|| self.url.distribution_id())
1460    }
1461
1462    fn resource_id(&self) -> ResourceId {
1463        self.hashes
1464            .first()
1465            .cloned()
1466            .map(ResourceId::Digest)
1467            .unwrap_or_else(|| self.url.resource_id())
1468    }
1469}
1470
1471impl Identifier for Path {
1472    fn distribution_id(&self) -> DistributionId {
1473        DistributionId::PathBuf(self.to_path_buf())
1474    }
1475
1476    fn resource_id(&self) -> ResourceId {
1477        ResourceId::PathBuf(self.to_path_buf())
1478    }
1479}
1480
1481impl Identifier for FileLocation {
1482    fn distribution_id(&self) -> DistributionId {
1483        match self {
1484            Self::RelativeUrl(base, url) => {
1485                DistributionId::RelativeUrl(base.to_string(), url.to_string())
1486            }
1487            Self::AbsoluteUrl(url) => DistributionId::AbsoluteUrl(url.to_string()),
1488        }
1489    }
1490
1491    fn resource_id(&self) -> ResourceId {
1492        match self {
1493            Self::RelativeUrl(base, url) => {
1494                ResourceId::RelativeUrl(base.to_string(), url.to_string())
1495            }
1496            Self::AbsoluteUrl(url) => ResourceId::AbsoluteUrl(url.to_string()),
1497        }
1498    }
1499}
1500
1501impl Identifier for RegistryBuiltWheel {
1502    fn distribution_id(&self) -> DistributionId {
1503        self.file.distribution_id()
1504    }
1505
1506    fn resource_id(&self) -> ResourceId {
1507        self.file.resource_id()
1508    }
1509}
1510
1511impl Identifier for RegistryBuiltDist {
1512    fn distribution_id(&self) -> DistributionId {
1513        self.best_wheel().distribution_id()
1514    }
1515
1516    fn resource_id(&self) -> ResourceId {
1517        self.best_wheel().resource_id()
1518    }
1519}
1520
1521impl Identifier for RegistrySourceDist {
1522    fn distribution_id(&self) -> DistributionId {
1523        self.file.distribution_id()
1524    }
1525
1526    fn resource_id(&self) -> ResourceId {
1527        self.file.resource_id()
1528    }
1529}
1530
1531impl Identifier for DirectUrlBuiltDist {
1532    fn distribution_id(&self) -> DistributionId {
1533        self.url.distribution_id()
1534    }
1535
1536    fn resource_id(&self) -> ResourceId {
1537        self.url.resource_id()
1538    }
1539}
1540
1541impl Identifier for DirectUrlSourceDist {
1542    fn distribution_id(&self) -> DistributionId {
1543        self.url.distribution_id()
1544    }
1545
1546    fn resource_id(&self) -> ResourceId {
1547        self.url.resource_id()
1548    }
1549}
1550
1551impl Identifier for PathBuiltDist {
1552    fn distribution_id(&self) -> DistributionId {
1553        self.url.distribution_id()
1554    }
1555
1556    fn resource_id(&self) -> ResourceId {
1557        self.url.resource_id()
1558    }
1559}
1560
1561impl Identifier for GitPathBuiltDist {
1562    fn distribution_id(&self) -> DistributionId {
1563        self.url.distribution_id()
1564    }
1565
1566    fn resource_id(&self) -> ResourceId {
1567        self.url.resource_id()
1568    }
1569}
1570
1571impl Identifier for PathSourceDist {
1572    fn distribution_id(&self) -> DistributionId {
1573        self.url.distribution_id()
1574    }
1575
1576    fn resource_id(&self) -> ResourceId {
1577        self.url.resource_id()
1578    }
1579}
1580
1581impl Identifier for DirectorySourceDist {
1582    fn distribution_id(&self) -> DistributionId {
1583        self.url.distribution_id()
1584    }
1585
1586    fn resource_id(&self) -> ResourceId {
1587        self.url.resource_id()
1588    }
1589}
1590
1591impl Identifier for GitPathSourceDist {
1592    fn distribution_id(&self) -> DistributionId {
1593        self.url.distribution_id()
1594    }
1595
1596    fn resource_id(&self) -> ResourceId {
1597        self.url.resource_id()
1598    }
1599}
1600
1601impl Identifier for GitDirectorySourceDist {
1602    fn distribution_id(&self) -> DistributionId {
1603        self.url.distribution_id()
1604    }
1605
1606    fn resource_id(&self) -> ResourceId {
1607        self.url.resource_id()
1608    }
1609}
1610
1611impl Identifier for SourceDist {
1612    fn distribution_id(&self) -> DistributionId {
1613        match self {
1614            Self::Registry(dist) => dist.distribution_id(),
1615            Self::DirectUrl(dist) => dist.distribution_id(),
1616            Self::GitPath(dist) => dist.distribution_id(),
1617            Self::GitDirectory(dist) => dist.distribution_id(),
1618            Self::Path(dist) => dist.distribution_id(),
1619            Self::Directory(dist) => dist.distribution_id(),
1620        }
1621    }
1622
1623    fn resource_id(&self) -> ResourceId {
1624        match self {
1625            Self::Registry(dist) => dist.resource_id(),
1626            Self::DirectUrl(dist) => dist.resource_id(),
1627            Self::GitPath(dist) => dist.resource_id(),
1628            Self::GitDirectory(dist) => dist.resource_id(),
1629            Self::Path(dist) => dist.resource_id(),
1630            Self::Directory(dist) => dist.resource_id(),
1631        }
1632    }
1633}
1634
1635impl Identifier for BuiltDist {
1636    fn distribution_id(&self) -> DistributionId {
1637        match self {
1638            Self::Registry(dist) => dist.distribution_id(),
1639            Self::DirectUrl(dist) => dist.distribution_id(),
1640            Self::Path(dist) => dist.distribution_id(),
1641            Self::GitPath(dist) => dist.distribution_id(),
1642        }
1643    }
1644
1645    fn resource_id(&self) -> ResourceId {
1646        match self {
1647            Self::Registry(dist) => dist.resource_id(),
1648            Self::DirectUrl(dist) => dist.resource_id(),
1649            Self::Path(dist) => dist.resource_id(),
1650            Self::GitPath(dist) => dist.resource_id(),
1651        }
1652    }
1653}
1654
1655impl Identifier for InstalledDist {
1656    fn distribution_id(&self) -> DistributionId {
1657        self.install_path().distribution_id()
1658    }
1659
1660    fn resource_id(&self) -> ResourceId {
1661        self.install_path().resource_id()
1662    }
1663}
1664
1665impl Identifier for Dist {
1666    fn distribution_id(&self) -> DistributionId {
1667        match self {
1668            Self::Built(dist) => dist.distribution_id(),
1669            Self::Source(dist) => dist.distribution_id(),
1670        }
1671    }
1672
1673    fn resource_id(&self) -> ResourceId {
1674        match self {
1675            Self::Built(dist) => dist.resource_id(),
1676            Self::Source(dist) => dist.resource_id(),
1677        }
1678    }
1679}
1680
1681impl Identifier for DirectSourceUrl<'_> {
1682    fn distribution_id(&self) -> DistributionId {
1683        self.url.distribution_id()
1684    }
1685
1686    fn resource_id(&self) -> ResourceId {
1687        self.url.resource_id()
1688    }
1689}
1690
1691impl Identifier for GitDirectorySourceUrl<'_> {
1692    fn distribution_id(&self) -> DistributionId {
1693        self.url.distribution_id()
1694    }
1695
1696    fn resource_id(&self) -> ResourceId {
1697        self.url.resource_id()
1698    }
1699}
1700
1701impl Identifier for GitPathSourceUrl<'_> {
1702    fn distribution_id(&self) -> DistributionId {
1703        self.url.distribution_id()
1704    }
1705
1706    fn resource_id(&self) -> ResourceId {
1707        self.url.resource_id()
1708    }
1709}
1710
1711impl Identifier for PathSourceUrl<'_> {
1712    fn distribution_id(&self) -> DistributionId {
1713        self.url.distribution_id()
1714    }
1715
1716    fn resource_id(&self) -> ResourceId {
1717        self.url.resource_id()
1718    }
1719}
1720
1721impl Identifier for DirectorySourceUrl<'_> {
1722    fn distribution_id(&self) -> DistributionId {
1723        self.url.distribution_id()
1724    }
1725
1726    fn resource_id(&self) -> ResourceId {
1727        self.url.resource_id()
1728    }
1729}
1730
1731impl Identifier for SourceUrl<'_> {
1732    fn distribution_id(&self) -> DistributionId {
1733        match self {
1734            Self::Direct(url) => url.distribution_id(),
1735            Self::GitDirectory(url) => url.distribution_id(),
1736            Self::GitPath(url) => url.distribution_id(),
1737            Self::Path(url) => url.distribution_id(),
1738            Self::Directory(url) => url.distribution_id(),
1739        }
1740    }
1741
1742    fn resource_id(&self) -> ResourceId {
1743        match self {
1744            Self::Direct(url) => url.resource_id(),
1745            Self::GitDirectory(url) => url.resource_id(),
1746            Self::GitPath(url) => url.resource_id(),
1747            Self::Path(url) => url.resource_id(),
1748            Self::Directory(url) => url.resource_id(),
1749        }
1750    }
1751}
1752
1753impl Identifier for BuildableSource<'_> {
1754    fn distribution_id(&self) -> DistributionId {
1755        match self {
1756            Self::Dist(source) => source.distribution_id(),
1757            Self::Url(source) => source.distribution_id(),
1758        }
1759    }
1760
1761    fn resource_id(&self) -> ResourceId {
1762        match self {
1763            Self::Dist(source) => source.resource_id(),
1764            Self::Url(source) => source.resource_id(),
1765        }
1766    }
1767}
1768
1769#[cfg(test)]
1770mod test {
1771    use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
1772    use uv_redacted::DisplaySafeUrl;
1773
1774    /// Ensure that we don't accidentally grow the `Dist` sizes.
1775    #[test]
1776    fn dist_size() {
1777        assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>());
1778        assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>());
1779        assert!(
1780            size_of::<SourceDist>() <= 176,
1781            "{}",
1782            size_of::<SourceDist>()
1783        );
1784    }
1785
1786    #[test]
1787    fn remote_source() {
1788        for url in [
1789            "https://example.com/foo-0.1.0.tar.gz",
1790            "https://example.com/foo%2D0.1.0.tar.gz",
1791            "https://example.com/foo-0.1.0.tar.gz#fragment",
1792            "https://example.com/foo-0.1.0.tar.gz?query",
1793            "https://example.com/foo-0.1.0.tar.gz?query#fragment",
1794            "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment",
1795            "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment/3",
1796            "https://example.com/foo%2D0.1.0.tar.gz?query=1/2#fragment/3",
1797        ] {
1798            let url = DisplaySafeUrl::parse(url).unwrap();
1799            assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1800            let url = UrlString::from(url.clone());
1801            assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1802        }
1803    }
1804}