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