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