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