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(), 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(self.location.as_ref(), self.subdirectory.as_deref())
1060    }
1061}
1062
1063impl DistributionMetadata for GitPathSourceDist {
1064    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1065        VersionOrUrlRef::Url(&self.url)
1066    }
1067
1068    fn version_id(&self) -> VersionId {
1069        VersionId::from_git(self.git.as_ref(), Some(&self.install_path))
1070    }
1071}
1072
1073impl DistributionMetadata for GitDirectorySourceDist {
1074    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1075        VersionOrUrlRef::Url(&self.url)
1076    }
1077
1078    fn version_id(&self) -> VersionId {
1079        VersionId::from_git(self.git.as_ref(), self.subdirectory.as_deref())
1080    }
1081}
1082
1083impl DistributionMetadata for PathSourceDist {
1084    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1085        VersionOrUrlRef::Url(&self.url)
1086    }
1087
1088    fn version_id(&self) -> VersionId {
1089        VersionId::from_path(self.install_path.as_ref())
1090    }
1091}
1092
1093impl DistributionMetadata for DirectorySourceDist {
1094    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1095        VersionOrUrlRef::Url(&self.url)
1096    }
1097
1098    fn version_id(&self) -> VersionId {
1099        VersionId::from_directory(self.install_path.as_ref())
1100    }
1101}
1102
1103impl DistributionMetadata for SourceDist {
1104    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1105        match self {
1106            Self::Registry(dist) => dist.version_or_url(),
1107            Self::DirectUrl(dist) => dist.version_or_url(),
1108            Self::GitPath(dist) => dist.version_or_url(),
1109            Self::GitDirectory(dist) => dist.version_or_url(),
1110            Self::Path(dist) => dist.version_or_url(),
1111            Self::Directory(dist) => dist.version_or_url(),
1112        }
1113    }
1114
1115    fn version_id(&self) -> VersionId {
1116        match self {
1117            Self::Registry(dist) => dist.version_id(),
1118            Self::DirectUrl(dist) => dist.version_id(),
1119            Self::GitPath(dist) => dist.version_id(),
1120            Self::GitDirectory(dist) => dist.version_id(),
1121            Self::Path(dist) => dist.version_id(),
1122            Self::Directory(dist) => dist.version_id(),
1123        }
1124    }
1125}
1126
1127impl DistributionMetadata for BuiltDist {
1128    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1129        match self {
1130            Self::Registry(dist) => dist.version_or_url(),
1131            Self::DirectUrl(dist) => dist.version_or_url(),
1132            Self::Path(dist) => dist.version_or_url(),
1133            Self::GitPath(dist) => dist.version_or_url(),
1134        }
1135    }
1136
1137    fn version_id(&self) -> VersionId {
1138        match self {
1139            Self::Registry(dist) => dist.version_id(),
1140            Self::DirectUrl(dist) => dist.version_id(),
1141            Self::Path(dist) => dist.version_id(),
1142            Self::GitPath(dist) => dist.version_id(),
1143        }
1144    }
1145}
1146
1147impl DistributionMetadata for Dist {
1148    fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1149        match self {
1150            Self::Built(dist) => dist.version_or_url(),
1151            Self::Source(dist) => dist.version_or_url(),
1152        }
1153    }
1154
1155    fn version_id(&self) -> VersionId {
1156        match self {
1157            Self::Built(dist) => dist.version_id(),
1158            Self::Source(dist) => dist.version_id(),
1159        }
1160    }
1161}
1162
1163impl RemoteSource for File {
1164    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1165        Ok(Cow::Borrowed(&self.filename))
1166    }
1167
1168    fn size(&self) -> Option<u64> {
1169        self.size
1170    }
1171}
1172
1173impl RemoteSource for Url {
1174    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1175        // Identify the last segment of the URL as the filename.
1176        let mut path_segments = self
1177            .path_segments()
1178            .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1179
1180        // This is guaranteed by the contract of `Url::path_segments`.
1181        let last = path_segments
1182            .next_back()
1183            .expect("path segments is non-empty");
1184
1185        // Decode the filename, which may be percent-encoded.
1186        let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1187
1188        Ok(filename)
1189    }
1190
1191    fn size(&self) -> Option<u64> {
1192        None
1193    }
1194}
1195
1196impl RemoteSource for UrlString {
1197    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1198        // Take the last segment, stripping any query or fragment.
1199        let last = self
1200            .base_str()
1201            .split('/')
1202            .next_back()
1203            .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1204
1205        // Decode the filename, which may be percent-encoded.
1206        let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1207
1208        Ok(filename)
1209    }
1210
1211    fn size(&self) -> Option<u64> {
1212        None
1213    }
1214}
1215
1216impl RemoteSource for RegistryBuiltWheel {
1217    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1218        self.file.filename()
1219    }
1220
1221    fn size(&self) -> Option<u64> {
1222        self.file.size()
1223    }
1224}
1225
1226impl RemoteSource for RegistryBuiltDist {
1227    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1228        self.best_wheel().filename()
1229    }
1230
1231    fn size(&self) -> Option<u64> {
1232        self.best_wheel().size()
1233    }
1234}
1235
1236impl RemoteSource for RegistrySourceDist {
1237    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1238        self.file.filename()
1239    }
1240
1241    fn size(&self) -> Option<u64> {
1242        self.file.size()
1243    }
1244}
1245
1246impl RemoteSource for DirectUrlBuiltDist {
1247    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1248        self.url.filename()
1249    }
1250
1251    fn size(&self) -> Option<u64> {
1252        self.url.size()
1253    }
1254}
1255
1256impl RemoteSource for DirectUrlSourceDist {
1257    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1258        self.url.filename()
1259    }
1260
1261    fn size(&self) -> Option<u64> {
1262        self.url.size()
1263    }
1264}
1265
1266impl RemoteSource for GitPathSourceDist {
1267    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1268        // The filename is the last segment of the URL, before any `@`.
1269        match self.url.filename()? {
1270            Cow::Borrowed(filename) => {
1271                if let Some((_, filename)) = filename.rsplit_once('@') {
1272                    Ok(Cow::Borrowed(filename))
1273                } else {
1274                    Ok(Cow::Borrowed(filename))
1275                }
1276            }
1277            Cow::Owned(filename) => {
1278                if let Some((_, filename)) = filename.rsplit_once('@') {
1279                    Ok(Cow::Owned(filename.to_owned()))
1280                } else {
1281                    Ok(Cow::Owned(filename))
1282                }
1283            }
1284        }
1285    }
1286
1287    fn size(&self) -> Option<u64> {
1288        self.url.size()
1289    }
1290}
1291
1292impl RemoteSource for GitDirectorySourceDist {
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 PathBuiltDist {
1319    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1320        self.url.filename()
1321    }
1322
1323    fn size(&self) -> Option<u64> {
1324        self.url.size()
1325    }
1326}
1327
1328impl RemoteSource for GitPathBuiltDist {
1329    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1330        self.url.filename()
1331    }
1332
1333    fn size(&self) -> Option<u64> {
1334        self.url.size()
1335    }
1336}
1337
1338impl RemoteSource for PathSourceDist {
1339    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1340        self.url.filename()
1341    }
1342
1343    fn size(&self) -> Option<u64> {
1344        self.url.size()
1345    }
1346}
1347
1348impl RemoteSource for DirectorySourceDist {
1349    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1350        self.url.filename()
1351    }
1352
1353    fn size(&self) -> Option<u64> {
1354        self.url.size()
1355    }
1356}
1357
1358impl RemoteSource for SourceDist {
1359    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1360        match self {
1361            Self::Registry(dist) => dist.filename(),
1362            Self::DirectUrl(dist) => dist.filename(),
1363            Self::GitPath(dist) => dist.filename(),
1364            Self::GitDirectory(dist) => dist.filename(),
1365            Self::Path(dist) => dist.filename(),
1366            Self::Directory(dist) => dist.filename(),
1367        }
1368    }
1369
1370    fn size(&self) -> Option<u64> {
1371        match self {
1372            Self::Registry(dist) => dist.size(),
1373            Self::DirectUrl(dist) => dist.size(),
1374            Self::GitPath(dist) => dist.size(),
1375            Self::GitDirectory(dist) => dist.size(),
1376            Self::Path(dist) => dist.size(),
1377            Self::Directory(dist) => dist.size(),
1378        }
1379    }
1380}
1381
1382impl RemoteSource for BuiltDist {
1383    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1384        match self {
1385            Self::Registry(dist) => dist.filename(),
1386            Self::DirectUrl(dist) => dist.filename(),
1387            Self::Path(dist) => dist.filename(),
1388            Self::GitPath(dist) => dist.filename(),
1389        }
1390    }
1391
1392    fn size(&self) -> Option<u64> {
1393        match self {
1394            Self::Registry(dist) => dist.size(),
1395            Self::DirectUrl(dist) => dist.size(),
1396            Self::Path(dist) => dist.size(),
1397            Self::GitPath(dist) => dist.size(),
1398        }
1399    }
1400}
1401
1402impl RemoteSource for Dist {
1403    fn filename(&self) -> Result<Cow<'_, str>, Error> {
1404        match self {
1405            Self::Built(dist) => dist.filename(),
1406            Self::Source(dist) => dist.filename(),
1407        }
1408    }
1409
1410    fn size(&self) -> Option<u64> {
1411        match self {
1412            Self::Built(dist) => dist.size(),
1413            Self::Source(dist) => dist.size(),
1414        }
1415    }
1416}
1417
1418impl Identifier for DisplaySafeUrl {
1419    fn distribution_id(&self) -> DistributionId {
1420        DistributionId::Url(uv_cache_key::CanonicalUrl::new(self))
1421    }
1422
1423    fn resource_id(&self) -> ResourceId {
1424        ResourceId::Url(uv_cache_key::RepositoryUrl::new(self))
1425    }
1426}
1427
1428impl Identifier for File {
1429    fn distribution_id(&self) -> DistributionId {
1430        self.hashes
1431            .first()
1432            .cloned()
1433            .map(DistributionId::Digest)
1434            .unwrap_or_else(|| self.url.distribution_id())
1435    }
1436
1437    fn resource_id(&self) -> ResourceId {
1438        self.hashes
1439            .first()
1440            .cloned()
1441            .map(ResourceId::Digest)
1442            .unwrap_or_else(|| self.url.resource_id())
1443    }
1444}
1445
1446impl Identifier for Path {
1447    fn distribution_id(&self) -> DistributionId {
1448        DistributionId::PathBuf(self.to_path_buf())
1449    }
1450
1451    fn resource_id(&self) -> ResourceId {
1452        ResourceId::PathBuf(self.to_path_buf())
1453    }
1454}
1455
1456impl Identifier for FileLocation {
1457    fn distribution_id(&self) -> DistributionId {
1458        match self {
1459            Self::RelativeUrl(base, url) => {
1460                DistributionId::RelativeUrl(base.to_string(), url.to_string())
1461            }
1462            Self::AbsoluteUrl(url) => DistributionId::AbsoluteUrl(url.to_string()),
1463        }
1464    }
1465
1466    fn resource_id(&self) -> ResourceId {
1467        match self {
1468            Self::RelativeUrl(base, url) => {
1469                ResourceId::RelativeUrl(base.to_string(), url.to_string())
1470            }
1471            Self::AbsoluteUrl(url) => ResourceId::AbsoluteUrl(url.to_string()),
1472        }
1473    }
1474}
1475
1476impl Identifier for RegistryBuiltWheel {
1477    fn distribution_id(&self) -> DistributionId {
1478        self.file.distribution_id()
1479    }
1480
1481    fn resource_id(&self) -> ResourceId {
1482        self.file.resource_id()
1483    }
1484}
1485
1486impl Identifier for RegistryBuiltDist {
1487    fn distribution_id(&self) -> DistributionId {
1488        self.best_wheel().distribution_id()
1489    }
1490
1491    fn resource_id(&self) -> ResourceId {
1492        self.best_wheel().resource_id()
1493    }
1494}
1495
1496impl Identifier for RegistrySourceDist {
1497    fn distribution_id(&self) -> DistributionId {
1498        self.file.distribution_id()
1499    }
1500
1501    fn resource_id(&self) -> ResourceId {
1502        self.file.resource_id()
1503    }
1504}
1505
1506impl Identifier for DirectUrlBuiltDist {
1507    fn distribution_id(&self) -> DistributionId {
1508        self.url.distribution_id()
1509    }
1510
1511    fn resource_id(&self) -> ResourceId {
1512        self.url.resource_id()
1513    }
1514}
1515
1516impl Identifier for DirectUrlSourceDist {
1517    fn distribution_id(&self) -> DistributionId {
1518        self.url.distribution_id()
1519    }
1520
1521    fn resource_id(&self) -> ResourceId {
1522        self.url.resource_id()
1523    }
1524}
1525
1526impl Identifier for PathBuiltDist {
1527    fn distribution_id(&self) -> DistributionId {
1528        self.url.distribution_id()
1529    }
1530
1531    fn resource_id(&self) -> ResourceId {
1532        self.url.resource_id()
1533    }
1534}
1535
1536impl Identifier for GitPathBuiltDist {
1537    fn distribution_id(&self) -> DistributionId {
1538        self.url.distribution_id()
1539    }
1540
1541    fn resource_id(&self) -> ResourceId {
1542        self.url.resource_id()
1543    }
1544}
1545
1546impl Identifier for PathSourceDist {
1547    fn distribution_id(&self) -> DistributionId {
1548        self.url.distribution_id()
1549    }
1550
1551    fn resource_id(&self) -> ResourceId {
1552        self.url.resource_id()
1553    }
1554}
1555
1556impl Identifier for DirectorySourceDist {
1557    fn distribution_id(&self) -> DistributionId {
1558        self.url.distribution_id()
1559    }
1560
1561    fn resource_id(&self) -> ResourceId {
1562        self.url.resource_id()
1563    }
1564}
1565
1566impl Identifier for GitPathSourceDist {
1567    fn distribution_id(&self) -> DistributionId {
1568        self.url.distribution_id()
1569    }
1570
1571    fn resource_id(&self) -> ResourceId {
1572        self.url.resource_id()
1573    }
1574}
1575
1576impl Identifier for GitDirectorySourceDist {
1577    fn distribution_id(&self) -> DistributionId {
1578        self.url.distribution_id()
1579    }
1580
1581    fn resource_id(&self) -> ResourceId {
1582        self.url.resource_id()
1583    }
1584}
1585
1586impl Identifier for SourceDist {
1587    fn distribution_id(&self) -> DistributionId {
1588        match self {
1589            Self::Registry(dist) => dist.distribution_id(),
1590            Self::DirectUrl(dist) => dist.distribution_id(),
1591            Self::GitPath(dist) => dist.distribution_id(),
1592            Self::GitDirectory(dist) => dist.distribution_id(),
1593            Self::Path(dist) => dist.distribution_id(),
1594            Self::Directory(dist) => dist.distribution_id(),
1595        }
1596    }
1597
1598    fn resource_id(&self) -> ResourceId {
1599        match self {
1600            Self::Registry(dist) => dist.resource_id(),
1601            Self::DirectUrl(dist) => dist.resource_id(),
1602            Self::GitPath(dist) => dist.resource_id(),
1603            Self::GitDirectory(dist) => dist.resource_id(),
1604            Self::Path(dist) => dist.resource_id(),
1605            Self::Directory(dist) => dist.resource_id(),
1606        }
1607    }
1608}
1609
1610impl Identifier for BuiltDist {
1611    fn distribution_id(&self) -> DistributionId {
1612        match self {
1613            Self::Registry(dist) => dist.distribution_id(),
1614            Self::DirectUrl(dist) => dist.distribution_id(),
1615            Self::Path(dist) => dist.distribution_id(),
1616            Self::GitPath(dist) => dist.distribution_id(),
1617        }
1618    }
1619
1620    fn resource_id(&self) -> ResourceId {
1621        match self {
1622            Self::Registry(dist) => dist.resource_id(),
1623            Self::DirectUrl(dist) => dist.resource_id(),
1624            Self::Path(dist) => dist.resource_id(),
1625            Self::GitPath(dist) => dist.resource_id(),
1626        }
1627    }
1628}
1629
1630impl Identifier for InstalledDist {
1631    fn distribution_id(&self) -> DistributionId {
1632        self.install_path().distribution_id()
1633    }
1634
1635    fn resource_id(&self) -> ResourceId {
1636        self.install_path().resource_id()
1637    }
1638}
1639
1640impl Identifier for Dist {
1641    fn distribution_id(&self) -> DistributionId {
1642        match self {
1643            Self::Built(dist) => dist.distribution_id(),
1644            Self::Source(dist) => dist.distribution_id(),
1645        }
1646    }
1647
1648    fn resource_id(&self) -> ResourceId {
1649        match self {
1650            Self::Built(dist) => dist.resource_id(),
1651            Self::Source(dist) => dist.resource_id(),
1652        }
1653    }
1654}
1655
1656impl Identifier for DirectSourceUrl<'_> {
1657    fn distribution_id(&self) -> DistributionId {
1658        self.url.distribution_id()
1659    }
1660
1661    fn resource_id(&self) -> ResourceId {
1662        self.url.resource_id()
1663    }
1664}
1665
1666impl Identifier for GitDirectorySourceUrl<'_> {
1667    fn distribution_id(&self) -> DistributionId {
1668        self.url.distribution_id()
1669    }
1670
1671    fn resource_id(&self) -> ResourceId {
1672        self.url.resource_id()
1673    }
1674}
1675
1676impl Identifier for GitPathSourceUrl<'_> {
1677    fn distribution_id(&self) -> DistributionId {
1678        self.url.distribution_id()
1679    }
1680
1681    fn resource_id(&self) -> ResourceId {
1682        self.url.resource_id()
1683    }
1684}
1685
1686impl Identifier for PathSourceUrl<'_> {
1687    fn distribution_id(&self) -> DistributionId {
1688        self.url.distribution_id()
1689    }
1690
1691    fn resource_id(&self) -> ResourceId {
1692        self.url.resource_id()
1693    }
1694}
1695
1696impl Identifier for DirectorySourceUrl<'_> {
1697    fn distribution_id(&self) -> DistributionId {
1698        self.url.distribution_id()
1699    }
1700
1701    fn resource_id(&self) -> ResourceId {
1702        self.url.resource_id()
1703    }
1704}
1705
1706impl Identifier for SourceUrl<'_> {
1707    fn distribution_id(&self) -> DistributionId {
1708        match self {
1709            Self::Direct(url) => url.distribution_id(),
1710            Self::GitDirectory(url) => url.distribution_id(),
1711            Self::GitPath(url) => url.distribution_id(),
1712            Self::Path(url) => url.distribution_id(),
1713            Self::Directory(url) => url.distribution_id(),
1714        }
1715    }
1716
1717    fn resource_id(&self) -> ResourceId {
1718        match self {
1719            Self::Direct(url) => url.resource_id(),
1720            Self::GitDirectory(url) => url.resource_id(),
1721            Self::GitPath(url) => url.resource_id(),
1722            Self::Path(url) => url.resource_id(),
1723            Self::Directory(url) => url.resource_id(),
1724        }
1725    }
1726}
1727
1728impl Identifier for BuildableSource<'_> {
1729    fn distribution_id(&self) -> DistributionId {
1730        match self {
1731            Self::Dist(source) => source.distribution_id(),
1732            Self::Url(source) => source.distribution_id(),
1733        }
1734    }
1735
1736    fn resource_id(&self) -> ResourceId {
1737        match self {
1738            Self::Dist(source) => source.resource_id(),
1739            Self::Url(source) => source.resource_id(),
1740        }
1741    }
1742}
1743
1744#[cfg(test)]
1745mod test {
1746    use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
1747    use uv_redacted::DisplaySafeUrl;
1748
1749    /// Ensure that we don't accidentally grow the `Dist` sizes.
1750    #[test]
1751    fn dist_size() {
1752        assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>());
1753        assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>());
1754        assert!(
1755            size_of::<SourceDist>() <= 176,
1756            "{}",
1757            size_of::<SourceDist>()
1758        );
1759    }
1760
1761    #[test]
1762    fn remote_source() {
1763        for url in [
1764            "https://example.com/foo-0.1.0.tar.gz",
1765            "https://example.com/foo-0.1.0.tar.gz#fragment",
1766            "https://example.com/foo-0.1.0.tar.gz?query",
1767            "https://example.com/foo-0.1.0.tar.gz?query#fragment",
1768            "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment",
1769            "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment/3",
1770        ] {
1771            let url = DisplaySafeUrl::parse(url).unwrap();
1772            assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1773            let url = UrlString::from(url.clone());
1774            assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1775        }
1776    }
1777}