Skip to main content

uv_distribution_types/
lib.rs

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