1use std::borrow::Cow;
43use std::ffi::OsStr;
44use std::fmt::Display;
45use std::path;
46use std::path::{Path, PathBuf};
47use std::str::FromStr;
48
49use memchr::memchr3;
50use url::Url;
51
52use uv_distribution_filename::{
53 DistExtension, SourceDistExtension, SourceDistFilename, WheelFilename,
54};
55use uv_fs::normalize_absolute_path;
56use uv_git_types::GitUrl;
57use uv_normalize::PackageName;
58use uv_pep440::Version;
59use uv_pep508::{Pep508Url, VerbatimUrl};
60use uv_pypi_types::{
61 ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitDirectoryUrl, ParsedGitPathUrl, ParsedPathUrl,
62 ParsedUrl, VerbatimParsedUrl,
63};
64use uv_redacted::DisplaySafeUrl;
65
66pub use crate::annotation::*;
67pub use crate::any::*;
68pub use crate::build_info::*;
69pub use crate::build_requires::*;
70pub use crate::buildable::*;
71pub use crate::cached::*;
72pub use crate::config_settings::*;
73pub use crate::dependency_metadata::*;
74pub use crate::diagnostic::*;
75pub use crate::dist_error::*;
76pub use crate::error::*;
77pub use crate::exclude_newer::*;
78pub use crate::file::*;
79pub use crate::hash::*;
80pub use crate::id::*;
81pub use crate::index::*;
82pub use crate::index_name::*;
83pub use crate::index_url::*;
84pub use crate::installed::*;
85pub use crate::known_platform::*;
86pub use crate::minimum_libc_version::MinimumLibcVersion;
87pub use crate::origin::*;
88pub use crate::pip_index::*;
89pub use crate::prioritized_distribution::*;
90pub use crate::requested::*;
91pub use crate::requirement::*;
92pub use crate::requires_python::*;
93pub use crate::resolution::*;
94pub use crate::resolved::*;
95pub use crate::specified_requirement::*;
96pub use crate::status_code_strategy::*;
97pub use crate::traits::*;
98
99mod annotation;
100mod any;
101mod build_info;
102mod build_requires;
103mod buildable;
104mod cached;
105mod config_settings;
106mod dependency_metadata;
107mod diagnostic;
108mod dist_error;
109mod error;
110mod exclude_newer;
111mod file;
112mod hash;
113mod id;
114mod index;
115mod index_name;
116mod index_url;
117mod installed;
118mod installed_modules;
119mod known_platform;
120mod minimum_libc_version;
121mod origin;
122mod pip_index;
123mod prioritized_distribution;
124mod requested;
125mod requirement;
126mod requires_python;
127mod resolution;
128mod resolved;
129mod specified_requirement;
130mod status_code_strategy;
131mod traits;
132
133#[derive(Debug, Clone)]
134pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
135 Version(&'a Version),
137 Url(&'a T),
139}
140
141impl Verbatim for VersionOrUrlRef<'_> {
142 fn verbatim(&self) -> Cow<'_, str> {
143 match self {
144 Self::Version(version) => Cow::Owned(format!("=={version}")),
145 Self::Url(url) => Cow::Owned(format!(" @ {}", url.verbatim())),
146 }
147 }
148}
149
150impl std::fmt::Display for VersionOrUrlRef<'_> {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 match self {
153 Self::Version(version) => write!(f, "=={version}"),
154 Self::Url(url) => write!(f, " @ {url}"),
155 }
156 }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
160pub enum InstalledVersion<'a> {
161 Version(&'a Version),
163 Url(&'a DisplaySafeUrl, &'a Version),
166}
167
168impl<'a> InstalledVersion<'a> {
169 pub fn version(&self) -> &'a Version {
171 match self {
172 Self::Version(version) => version,
173 Self::Url(_, version) => version,
174 }
175 }
176}
177
178impl std::fmt::Display for InstalledVersion<'_> {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 match self {
181 Self::Version(version) => write!(f, "=={version}"),
182 Self::Url(url, version) => write!(f, "=={version} (from {url})"),
183 }
184 }
185}
186
187#[derive(Debug, Clone, Hash, PartialEq, Eq)]
191pub enum Dist {
192 Built(BuiltDist),
193 Source(SourceDist),
194}
195
196#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
198pub enum DistRef<'a> {
199 Built(&'a BuiltDist),
200 Source(&'a SourceDist),
201}
202
203impl Display for DistRef<'_> {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 match self {
206 Self::Built(built_dist) => Display::fmt(&built_dist, f),
207 Self::Source(source_dist) => Display::fmt(&source_dist, f),
208 }
209 }
210}
211
212#[derive(Debug, Clone, Hash, PartialEq, Eq)]
214pub enum BuiltDist {
215 Registry(RegistryBuiltDist),
216 DirectUrl(DirectUrlBuiltDist),
217 Path(PathBuiltDist),
218 GitPath(GitPathBuiltDist),
219}
220
221#[derive(Debug, Clone, Hash, PartialEq, Eq)]
224pub enum SourceDist {
225 Registry(RegistrySourceDist),
226 DirectUrl(DirectUrlSourceDist),
227 GitDirectory(GitDirectorySourceDist),
228 GitPath(GitPathSourceDist),
229 Path(PathSourceDist),
230 Directory(DirectorySourceDist),
231}
232
233#[derive(Debug, Clone, Hash, PartialEq, Eq)]
235pub struct RegistryBuiltWheel {
236 pub filename: WheelFilename,
237 pub file: Box<File>,
238 pub index: IndexUrl,
239 pub size_is_authoritative: bool,
241}
242
243#[derive(Debug, Clone, Hash, PartialEq, Eq)]
245pub struct RegistryBuiltDist {
246 pub wheels: Vec<RegistryBuiltWheel>,
249 pub best_wheel_index: usize,
254 pub sdist: Option<RegistrySourceDist>,
262 }
272
273#[derive(Debug, Clone, Hash, PartialEq, Eq)]
275pub struct DirectUrlBuiltDist {
276 pub filename: WheelFilename,
279 pub location: Box<DisplaySafeUrl>,
281 pub url: VerbatimUrl,
283 pub size: Option<u64>,
285}
286
287#[derive(Debug, Clone, Hash, PartialEq, Eq)]
289pub struct PathBuiltDist {
290 pub filename: WheelFilename,
291 pub install_path: Box<Path>,
293 pub url: VerbatimUrl,
295}
296
297#[derive(Debug, Clone, Hash, PartialEq, Eq)]
299pub struct GitPathBuiltDist {
300 pub filename: WheelFilename,
301 pub git: Box<GitUrl>,
303 pub install_path: PathBuf,
305 pub url: VerbatimUrl,
307}
308
309#[derive(Debug, Clone, Hash, PartialEq, Eq)]
311pub struct RegistrySourceDist {
312 pub name: PackageName,
313 pub version: Version,
314 pub file: Box<File>,
315 pub ext: SourceDistExtension,
317 pub index: IndexUrl,
318 pub wheels: Vec<RegistryBuiltWheel>,
326 pub size_is_authoritative: bool,
328}
329
330#[derive(Debug, Clone, Hash, PartialEq, Eq)]
332pub struct DirectUrlSourceDist {
333 pub name: PackageName,
336 pub location: Box<DisplaySafeUrl>,
338 pub subdirectory: Option<Box<Path>>,
340 pub ext: SourceDistExtension,
342 pub url: VerbatimUrl,
344 pub size: Option<u64>,
346}
347
348#[derive(Debug, Clone, Hash, PartialEq, Eq)]
350pub struct GitDirectorySourceDist {
351 pub name: PackageName,
352 pub git: Box<GitUrl>,
354 pub subdirectory: Option<Box<Path>>,
356 pub url: VerbatimUrl,
358}
359
360#[derive(Debug, Clone, Hash, PartialEq, Eq)]
363pub struct GitPathSourceDist {
364 pub name: PackageName,
365 pub git: Box<GitUrl>,
367 pub install_path: PathBuf,
369 pub ext: SourceDistExtension,
371 pub url: VerbatimUrl,
373}
374
375#[derive(Debug, Clone, Hash, PartialEq, Eq)]
377pub struct PathSourceDist {
378 pub name: PackageName,
379 pub version: Option<Version>,
380 pub install_path: Box<Path>,
382 pub ext: SourceDistExtension,
384 pub url: VerbatimUrl,
386}
387
388#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
390pub enum FirstParty {
391 Yes,
392 No,
393}
394
395#[derive(Debug, Clone, Hash, PartialEq, Eq)]
397pub struct DirectorySourceDist {
398 pub name: PackageName,
399 pub install_path: Box<Path>,
401 pub editable: Option<bool>,
403 pub r#virtual: Option<bool>,
405 pub first_party: FirstParty,
407 pub url: VerbatimUrl,
409}
410
411impl Dist {
412 pub fn from_http_url(
415 name: PackageName,
416 url: VerbatimUrl,
417 location: DisplaySafeUrl,
418 subdirectory: Option<Box<Path>>,
419 ext: DistExtension,
420 ) -> Result<Self, Error> {
421 match ext {
422 DistExtension::Wheel => {
423 let filename = WheelFilename::from_str(&url.filename()?)?;
425 if filename.name != name {
426 return Err(Error::PackageNameMismatch(
427 name,
428 filename.name,
429 url.verbatim().to_string(),
430 ));
431 }
432
433 Ok(Self::Built(BuiltDist::DirectUrl(DirectUrlBuiltDist {
434 filename,
435 location: Box::new(location),
436 url,
437 size: None,
438 })))
439 }
440 DistExtension::Source(ext) => {
441 if !ext.is_pep625_compliant() {
442 return Err(Error::NotPep625Filename(url.verbatim().to_string()));
443 }
444 Ok(Self::Source(SourceDist::DirectUrl(DirectUrlSourceDist {
445 name,
446 location: Box::new(location),
447 subdirectory,
448 ext,
449 url,
450 size: None,
451 })))
452 }
453 }
454 }
455
456 pub fn from_file_url(
458 name: PackageName,
459 url: VerbatimUrl,
460 install_path: &Path,
461 ext: DistExtension,
462 ) -> Result<Self, Error> {
463 let install_path = path::absolute(install_path)?;
465
466 let install_path = normalize_absolute_path(&install_path)?;
468
469 if !install_path.exists() {
471 return Err(Error::NotFound(url.to_url()));
472 }
473
474 match ext {
476 DistExtension::Wheel => {
477 let filename = install_path
479 .file_name()
480 .and_then(OsStr::to_str)
481 .ok_or_else(|| Error::MissingWheelFilename(install_path.clone()))?;
482 let filename = WheelFilename::from_str(filename)?;
483 if filename.name != name {
484 return Err(Error::PackageNameMismatch(
485 name,
486 filename.name,
487 url.verbatim().to_string(),
488 ));
489 }
490 Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
491 filename,
492 install_path: install_path.into_boxed_path(),
493 url,
494 })))
495 }
496 DistExtension::Source(ext) => {
497 if !ext.is_pep625_compliant() {
498 return Err(Error::NotPep625Filename(url.verbatim().to_string()));
499 }
500
501 let version = url
503 .filename()
504 .ok()
505 .and_then(|filename| {
506 SourceDistFilename::parse(filename.as_ref(), ext, &name).ok()
507 })
508 .map(|filename| filename.version);
509
510 Ok(Self::Source(SourceDist::Path(PathSourceDist {
511 name,
512 version,
513 install_path: install_path.into_boxed_path(),
514 ext,
515 url,
516 })))
517 }
518 }
519 }
520
521 pub fn from_directory_url(
523 name: PackageName,
524 url: VerbatimUrl,
525 install_path: &Path,
526 editable: Option<bool>,
527 r#virtual: Option<bool>,
528 ) -> Result<Self, Error> {
529 let install_path = path::absolute(install_path)?;
531
532 let install_path = normalize_absolute_path(&install_path)?;
534
535 if !install_path.exists() {
537 return Err(Error::NotFound(url.to_url()));
538 }
539
540 Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
542 name,
543 install_path: install_path.into_boxed_path(),
544 editable,
545 r#virtual,
546 first_party: FirstParty::No,
547 url,
548 })))
549 }
550
551 pub fn from_git_directory_url(
553 name: PackageName,
554 url: VerbatimUrl,
555 git: GitUrl,
556 subdirectory: Option<Box<Path>>,
557 ) -> Result<Self, Error> {
558 Ok(Self::Source(SourceDist::GitDirectory(
559 GitDirectorySourceDist {
560 name,
561 git: Box::new(git),
562 subdirectory,
563 url,
564 },
565 )))
566 }
567
568 pub fn from_git_path_url(
570 name: PackageName,
571 url: VerbatimUrl,
572 git: GitUrl,
573 install_path: PathBuf,
574 ext: DistExtension,
575 ) -> Result<Self, Error> {
576 match ext {
577 DistExtension::Wheel => {
578 let filename = install_path
580 .file_name()
581 .and_then(OsStr::to_str)
582 .ok_or_else(|| Error::MissingWheelFilename(install_path.clone()))?;
583 let filename = WheelFilename::from_str(filename)?;
584 if filename.name != name {
585 return Err(Error::PackageNameMismatch(
586 name,
587 filename.name,
588 url.verbatim().to_string(),
589 ));
590 }
591
592 Ok(Self::Built(BuiltDist::GitPath(GitPathBuiltDist {
593 filename,
594 git: Box::new(git),
595 install_path,
596 url,
597 })))
598 }
599 DistExtension::Source(ext) => {
600 Ok(Self::Source(SourceDist::GitPath(GitPathSourceDist {
601 name,
602 git: Box::new(git),
603 install_path,
604 ext,
605 url,
606 })))
607 }
608 }
609 }
610
611 pub fn from_url(name: PackageName, url: VerbatimParsedUrl) -> Result<Self, Error> {
613 match url.parsed_url {
614 ParsedUrl::Archive(archive) => Self::from_http_url(
615 name,
616 url.verbatim,
617 archive.url,
618 archive.subdirectory,
619 archive.ext,
620 ),
621 ParsedUrl::Path(file) => {
622 Self::from_file_url(name, url.verbatim, &file.install_path, file.ext)
623 }
624 ParsedUrl::Directory(directory) => Self::from_directory_url(
625 name,
626 url.verbatim,
627 &directory.install_path,
628 directory.editable,
629 directory.r#virtual,
630 ),
631 ParsedUrl::GitDirectory(git) => {
632 Self::from_git_directory_url(name, url.verbatim, git.url, git.subdirectory)
633 }
634 ParsedUrl::GitPath(git) => {
635 Self::from_git_path_url(name, url.verbatim, git.url, git.install_path, git.ext)
636 }
637 }
638 }
639
640 fn is_editable(&self) -> bool {
642 match self {
643 Self::Source(dist) => dist.is_editable(),
644 Self::Built(_) => false,
645 }
646 }
647
648 fn is_local(&self) -> bool {
650 match self {
651 Self::Source(dist) => dist.is_local(),
652 Self::Built(dist) => dist.is_local(),
653 }
654 }
655
656 pub fn index(&self) -> Option<&IndexUrl> {
658 match self {
659 Self::Built(dist) => dist.index(),
660 Self::Source(dist) => dist.index(),
661 }
662 }
663
664 pub fn file(&self) -> Option<&File> {
666 match self {
667 Self::Built(built) => built.file(),
668 Self::Source(source) => source.file(),
669 }
670 }
671
672 pub fn source_tree(&self) -> Option<&Path> {
674 match self {
675 Self::Built { .. } => None,
676 Self::Source(source) => source.source_tree(),
677 }
678 }
679
680 pub fn version(&self) -> Option<&Version> {
682 match self {
683 Self::Built(wheel) => Some(wheel.version()),
684 Self::Source(source_dist) => source_dist.version(),
685 }
686 }
687}
688
689impl<'a> From<&'a Dist> for DistRef<'a> {
690 fn from(dist: &'a Dist) -> Self {
691 match dist {
692 Dist::Built(built) => DistRef::Built(built),
693 Dist::Source(source) => DistRef::Source(source),
694 }
695 }
696}
697
698impl<'a> From<&'a SourceDist> for DistRef<'a> {
699 fn from(dist: &'a SourceDist) -> Self {
700 DistRef::Source(dist)
701 }
702}
703
704impl<'a> From<&'a BuiltDist> for DistRef<'a> {
705 fn from(dist: &'a BuiltDist) -> Self {
706 DistRef::Built(dist)
707 }
708}
709
710impl BuiltDist {
711 fn is_local(&self) -> bool {
713 matches!(self, Self::Path(_))
714 }
715
716 pub fn index(&self) -> Option<&IndexUrl> {
718 match self {
719 Self::Registry(registry) => Some(®istry.best_wheel().index),
720 Self::DirectUrl(_) => None,
721 Self::Path(_) => None,
722 Self::GitPath(_) => None,
723 }
724 }
725
726 fn file(&self) -> Option<&File> {
728 match self {
729 Self::Registry(registry) => Some(®istry.best_wheel().file),
730 Self::DirectUrl(_) | Self::Path(_) | Self::GitPath(_) => None,
731 }
732 }
733
734 pub fn version(&self) -> &Version {
735 match self {
736 Self::Registry(wheels) => &wheels.best_wheel().filename.version,
737 Self::DirectUrl(wheel) => &wheel.filename.version,
738 Self::Path(wheel) => &wheel.filename.version,
739 Self::GitPath(wheel) => &wheel.filename.version,
740 }
741 }
742}
743
744impl SourceDist {
745 fn index(&self) -> Option<&IndexUrl> {
747 match self {
748 Self::Registry(registry) => Some(®istry.index),
749 Self::DirectUrl(_)
750 | Self::GitPath(_)
751 | Self::GitDirectory(_)
752 | Self::Path(_)
753 | Self::Directory(_) => None,
754 }
755 }
756
757 fn file(&self) -> Option<&File> {
759 match self {
760 Self::Registry(registry) => Some(®istry.file),
761 Self::DirectUrl(_)
762 | Self::GitPath(_)
763 | Self::GitDirectory(_)
764 | Self::Path(_)
765 | Self::Directory(_) => None,
766 }
767 }
768
769 pub fn version(&self) -> Option<&Version> {
771 match self {
772 Self::Registry(source_dist) => Some(&source_dist.version),
773 Self::DirectUrl(_)
774 | Self::GitPath(_)
775 | Self::GitDirectory(_)
776 | Self::Path(_)
777 | Self::Directory(_) => None,
778 }
779 }
780
781 pub fn is_editable(&self) -> bool {
783 match self {
784 Self::Directory(DirectorySourceDist { editable, .. }) => editable.unwrap_or(false),
785 _ => false,
786 }
787 }
788
789 pub fn is_virtual(&self) -> bool {
791 match self {
792 Self::Directory(DirectorySourceDist { r#virtual, .. }) => r#virtual.unwrap_or(false),
793 _ => false,
794 }
795 }
796
797 pub fn is_first_party(&self) -> bool {
799 match self {
800 Self::Directory(DirectorySourceDist {
801 first_party: FirstParty::Yes,
802 ..
803 }) => true,
804 Self::Directory(DirectorySourceDist {
805 first_party: FirstParty::No,
806 ..
807 })
808 | Self::Registry(_)
809 | Self::DirectUrl(_)
810 | Self::GitDirectory(_)
811 | Self::GitPath(_)
812 | Self::Path(_) => false,
813 }
814 }
815
816 fn is_local(&self) -> bool {
818 matches!(self, Self::Directory(_) | Self::Path(_))
819 }
820
821 pub fn as_path(&self) -> Option<&Path> {
823 match self {
824 Self::Path(dist) => Some(&dist.install_path),
825 Self::Directory(dist) => Some(&dist.install_path),
826 _ => None,
827 }
828 }
829
830 fn source_tree(&self) -> Option<&Path> {
832 match self {
833 Self::Directory(dist) => Some(&dist.install_path),
834 _ => None,
835 }
836 }
837}
838
839impl RegistryBuiltDist {
840 pub fn best_wheel(&self) -> &RegistryBuiltWheel {
842 &self.wheels[self.best_wheel_index]
843 }
844}
845
846impl DirectUrlBuiltDist {
847 pub fn to_parsed_url(&self) -> ParsedUrl {
849 ParsedUrl::Archive(ParsedArchiveUrl::from_source(
850 (*self.location).clone(),
851 None,
852 DistExtension::Wheel,
853 ))
854 }
855}
856
857impl PathBuiltDist {
858 pub fn to_parsed_url(&self) -> ParsedUrl {
860 ParsedUrl::Path(ParsedPathUrl::from_source(
861 self.install_path.clone(),
862 DistExtension::Wheel,
863 self.url.to_url(),
864 ))
865 }
866}
867
868impl PathSourceDist {
869 pub fn to_parsed_url(&self) -> ParsedUrl {
871 ParsedUrl::Path(ParsedPathUrl::from_source(
872 self.install_path.clone(),
873 DistExtension::Source(self.ext),
874 self.url.to_url(),
875 ))
876 }
877}
878
879impl DirectUrlSourceDist {
880 pub fn to_parsed_url(&self) -> ParsedUrl {
882 ParsedUrl::Archive(ParsedArchiveUrl::from_source(
883 (*self.location).clone(),
884 self.subdirectory.clone(),
885 DistExtension::Source(self.ext),
886 ))
887 }
888}
889
890impl GitDirectorySourceDist {
891 pub fn to_parsed_url(&self) -> ParsedUrl {
893 ParsedUrl::GitDirectory(ParsedGitDirectoryUrl::from_source(
894 (*self.git).clone(),
895 self.subdirectory.clone(),
896 ))
897 }
898}
899
900impl GitPathBuiltDist {
901 pub fn to_parsed_url(&self) -> ParsedUrl {
903 ParsedUrl::GitPath(ParsedGitPathUrl::from_source(
904 (*self.git).clone(),
905 self.install_path.clone(),
906 DistExtension::Wheel,
907 ))
908 }
909}
910
911impl GitPathSourceDist {
912 pub fn to_parsed_url(&self) -> ParsedUrl {
914 ParsedUrl::GitPath(ParsedGitPathUrl::from_source(
915 (*self.git).clone(),
916 self.install_path.clone(),
917 DistExtension::Source(self.ext),
918 ))
919 }
920}
921
922impl DirectorySourceDist {
923 pub fn to_parsed_url(&self) -> ParsedUrl {
925 ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
926 self.install_path.clone(),
927 self.editable,
928 self.r#virtual,
929 self.url.to_url(),
930 ))
931 }
932}
933
934impl Name for RegistryBuiltWheel {
935 fn name(&self) -> &PackageName {
936 &self.filename.name
937 }
938}
939
940impl Name for RegistryBuiltDist {
941 fn name(&self) -> &PackageName {
942 self.best_wheel().name()
943 }
944}
945
946impl Name for DirectUrlBuiltDist {
947 fn name(&self) -> &PackageName {
948 &self.filename.name
949 }
950}
951
952impl Name for PathBuiltDist {
953 fn name(&self) -> &PackageName {
954 &self.filename.name
955 }
956}
957
958impl Name for GitPathBuiltDist {
959 fn name(&self) -> &PackageName {
960 &self.filename.name
961 }
962}
963
964impl Name for RegistrySourceDist {
965 fn name(&self) -> &PackageName {
966 &self.name
967 }
968}
969
970impl Name for DirectUrlSourceDist {
971 fn name(&self) -> &PackageName {
972 &self.name
973 }
974}
975
976impl Name for GitPathSourceDist {
977 fn name(&self) -> &PackageName {
978 &self.name
979 }
980}
981
982impl Name for GitDirectorySourceDist {
983 fn name(&self) -> &PackageName {
984 &self.name
985 }
986}
987
988impl Name for PathSourceDist {
989 fn name(&self) -> &PackageName {
990 &self.name
991 }
992}
993
994impl Name for DirectorySourceDist {
995 fn name(&self) -> &PackageName {
996 &self.name
997 }
998}
999
1000impl Name for SourceDist {
1001 fn name(&self) -> &PackageName {
1002 match self {
1003 Self::Registry(dist) => dist.name(),
1004 Self::DirectUrl(dist) => dist.name(),
1005 Self::GitPath(dist) => dist.name(),
1006 Self::GitDirectory(dist) => dist.name(),
1007 Self::Path(dist) => dist.name(),
1008 Self::Directory(dist) => dist.name(),
1009 }
1010 }
1011}
1012
1013impl Name for BuiltDist {
1014 fn name(&self) -> &PackageName {
1015 match self {
1016 Self::Registry(dist) => dist.name(),
1017 Self::DirectUrl(dist) => dist.name(),
1018 Self::Path(dist) => dist.name(),
1019 Self::GitPath(dist) => dist.name(),
1020 }
1021 }
1022}
1023
1024impl Name for Dist {
1025 fn name(&self) -> &PackageName {
1026 match self {
1027 Self::Built(dist) => dist.name(),
1028 Self::Source(dist) => dist.name(),
1029 }
1030 }
1031}
1032
1033impl Name for CompatibleDist<'_> {
1034 fn name(&self) -> &PackageName {
1035 match self {
1036 Self::InstalledDist(dist) => dist.name(),
1037 Self::SourceDist {
1038 sdist,
1039 prioritized: _,
1040 } => sdist.name(),
1041 Self::CompatibleWheel {
1042 wheel,
1043 priority: _,
1044 prioritized: _,
1045 } => wheel.name(),
1046 Self::IncompatibleWheel {
1047 sdist,
1048 wheel: _,
1049 prioritized: _,
1050 } => sdist.name(),
1051 }
1052 }
1053}
1054
1055impl DistributionMetadata for RegistryBuiltWheel {
1056 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1057 VersionOrUrlRef::Version(&self.filename.version)
1058 }
1059}
1060
1061impl DistributionMetadata for RegistryBuiltDist {
1062 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1063 self.best_wheel().version_or_url()
1064 }
1065}
1066
1067impl DistributionMetadata for DirectUrlBuiltDist {
1068 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1069 VersionOrUrlRef::Url(&self.url)
1070 }
1071
1072 fn version_id(&self) -> VersionId {
1073 VersionId::from_archive(self.location.as_ref().clone(), None)
1074 }
1075}
1076
1077impl DistributionMetadata for PathBuiltDist {
1078 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1079 VersionOrUrlRef::Url(&self.url)
1080 }
1081
1082 fn version_id(&self) -> VersionId {
1083 VersionId::from_path(self.install_path.as_ref())
1084 }
1085}
1086
1087impl DistributionMetadata for GitPathBuiltDist {
1088 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1089 VersionOrUrlRef::Url(&self.url)
1090 }
1091}
1092
1093impl DistributionMetadata for RegistrySourceDist {
1094 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1095 VersionOrUrlRef::Version(&self.version)
1096 }
1097}
1098
1099impl DistributionMetadata for DirectUrlSourceDist {
1100 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1101 VersionOrUrlRef::Url(&self.url)
1102 }
1103
1104 fn version_id(&self) -> VersionId {
1105 VersionId::from_archive(
1106 self.location.as_ref().clone(),
1107 self.subdirectory.clone().map(Path::into_path_buf),
1108 )
1109 }
1110}
1111
1112impl DistributionMetadata for GitPathSourceDist {
1113 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1114 VersionOrUrlRef::Url(&self.url)
1115 }
1116
1117 fn version_id(&self) -> VersionId {
1118 VersionId::from_git(self.git.as_ref(), Some(&self.install_path))
1119 }
1120}
1121
1122impl DistributionMetadata for GitDirectorySourceDist {
1123 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1124 VersionOrUrlRef::Url(&self.url)
1125 }
1126
1127 fn version_id(&self) -> VersionId {
1128 VersionId::from_git(self.git.as_ref(), self.subdirectory.as_deref())
1129 }
1130}
1131
1132impl DistributionMetadata for PathSourceDist {
1133 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1134 VersionOrUrlRef::Url(&self.url)
1135 }
1136
1137 fn version_id(&self) -> VersionId {
1138 VersionId::from_path(self.install_path.as_ref())
1139 }
1140}
1141
1142impl DistributionMetadata for DirectorySourceDist {
1143 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1144 VersionOrUrlRef::Url(&self.url)
1145 }
1146
1147 fn version_id(&self) -> VersionId {
1148 VersionId::from_directory(self.install_path.as_ref())
1149 }
1150}
1151
1152impl DistributionMetadata for SourceDist {
1153 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1154 match self {
1155 Self::Registry(dist) => dist.version_or_url(),
1156 Self::DirectUrl(dist) => dist.version_or_url(),
1157 Self::GitPath(dist) => dist.version_or_url(),
1158 Self::GitDirectory(dist) => dist.version_or_url(),
1159 Self::Path(dist) => dist.version_or_url(),
1160 Self::Directory(dist) => dist.version_or_url(),
1161 }
1162 }
1163
1164 fn version_id(&self) -> VersionId {
1165 match self {
1166 Self::Registry(dist) => dist.version_id(),
1167 Self::DirectUrl(dist) => dist.version_id(),
1168 Self::GitPath(dist) => dist.version_id(),
1169 Self::GitDirectory(dist) => dist.version_id(),
1170 Self::Path(dist) => dist.version_id(),
1171 Self::Directory(dist) => dist.version_id(),
1172 }
1173 }
1174}
1175
1176impl DistributionMetadata for BuiltDist {
1177 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1178 match self {
1179 Self::Registry(dist) => dist.version_or_url(),
1180 Self::DirectUrl(dist) => dist.version_or_url(),
1181 Self::Path(dist) => dist.version_or_url(),
1182 Self::GitPath(dist) => dist.version_or_url(),
1183 }
1184 }
1185
1186 fn version_id(&self) -> VersionId {
1187 match self {
1188 Self::Registry(dist) => dist.version_id(),
1189 Self::DirectUrl(dist) => dist.version_id(),
1190 Self::Path(dist) => dist.version_id(),
1191 Self::GitPath(dist) => dist.version_id(),
1192 }
1193 }
1194}
1195
1196impl DistributionMetadata for Dist {
1197 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1198 match self {
1199 Self::Built(dist) => dist.version_or_url(),
1200 Self::Source(dist) => dist.version_or_url(),
1201 }
1202 }
1203
1204 fn version_id(&self) -> VersionId {
1205 match self {
1206 Self::Built(dist) => dist.version_id(),
1207 Self::Source(dist) => dist.version_id(),
1208 }
1209 }
1210}
1211
1212impl RemoteSource for File {
1213 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1214 Ok(Cow::Borrowed(&self.filename))
1215 }
1216
1217 fn size(&self) -> Option<u64> {
1218 self.size
1219 }
1220}
1221
1222impl RemoteSource for Url {
1223 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1224 let mut path_segments = self.path_segments().ok_or_else(|| {
1226 Error::MissingPathSegments(DisplaySafeUrl::ref_cast(self).to_string())
1227 })?;
1228
1229 let last = path_segments
1231 .next_back()
1232 .expect("path segments is non-empty");
1233
1234 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1236
1237 Ok(filename)
1238 }
1239
1240 fn size(&self) -> Option<u64> {
1241 None
1242 }
1243}
1244
1245impl RemoteSource for UrlString {
1246 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1247 let url = self.as_ref();
1248 if memchr3(b'?', b'#', b'%', url.as_bytes()).is_none()
1249 && let Some((_, filename)) = url.rsplit_once('/')
1250 {
1251 return Ok(Cow::Borrowed(filename));
1252 }
1253
1254 let last = self
1256 .base_str()
1257 .split('/')
1258 .next_back()
1259 .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1260
1261 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1263
1264 Ok(filename)
1265 }
1266
1267 fn size(&self) -> Option<u64> {
1268 None
1269 }
1270}
1271
1272impl RemoteSource for RegistryBuiltWheel {
1273 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1274 self.file.filename()
1275 }
1276
1277 fn size(&self) -> Option<u64> {
1278 self.file.size()
1279 }
1280}
1281
1282impl RemoteSource for RegistryBuiltDist {
1283 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1284 self.best_wheel().filename()
1285 }
1286
1287 fn size(&self) -> Option<u64> {
1288 self.best_wheel().size()
1289 }
1290}
1291
1292impl RemoteSource for RegistrySourceDist {
1293 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1294 self.file.filename()
1295 }
1296
1297 fn size(&self) -> Option<u64> {
1298 self.file.size()
1299 }
1300}
1301
1302impl RemoteSource for DirectUrlBuiltDist {
1303 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1304 self.url.filename()
1305 }
1306
1307 fn size(&self) -> Option<u64> {
1308 self.size
1309 }
1310}
1311
1312impl RemoteSource for DirectUrlSourceDist {
1313 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1314 self.url.filename()
1315 }
1316
1317 fn size(&self) -> Option<u64> {
1318 self.size
1319 }
1320}
1321
1322impl RemoteSource for GitPathSourceDist {
1323 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1324 match self.url.filename()? {
1326 Cow::Borrowed(filename) if let Some((_, suffix)) = filename.rsplit_once('@') => {
1327 Ok(Cow::Borrowed(suffix))
1328 }
1329 Cow::Owned(ref filename) if let Some((_, suffix)) = filename.rsplit_once('@') => {
1330 Ok(Cow::Owned(suffix.to_owned()))
1331 }
1332 filename => Ok(filename),
1333 }
1334 }
1335
1336 fn size(&self) -> Option<u64> {
1337 self.url.size()
1338 }
1339}
1340
1341impl RemoteSource for GitDirectorySourceDist {
1342 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1343 match self.url.filename()? {
1345 Cow::Borrowed(filename) if let Some((_, suffix)) = filename.rsplit_once('@') => {
1346 Ok(Cow::Borrowed(suffix))
1347 }
1348 Cow::Owned(ref filename) if let Some((_, suffix)) = filename.rsplit_once('@') => {
1349 Ok(Cow::Owned(suffix.to_owned()))
1350 }
1351 filename => Ok(filename),
1352 }
1353 }
1354
1355 fn size(&self) -> Option<u64> {
1356 self.url.size()
1357 }
1358}
1359
1360impl RemoteSource for PathBuiltDist {
1361 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1362 self.url.filename()
1363 }
1364
1365 fn size(&self) -> Option<u64> {
1366 self.url.size()
1367 }
1368}
1369
1370impl RemoteSource for GitPathBuiltDist {
1371 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1372 self.url.filename()
1373 }
1374
1375 fn size(&self) -> Option<u64> {
1376 self.url.size()
1377 }
1378}
1379
1380impl RemoteSource for PathSourceDist {
1381 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1382 self.url.filename()
1383 }
1384
1385 fn size(&self) -> Option<u64> {
1386 self.url.size()
1387 }
1388}
1389
1390impl RemoteSource for DirectorySourceDist {
1391 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1392 self.url.filename()
1393 }
1394
1395 fn size(&self) -> Option<u64> {
1396 self.url.size()
1397 }
1398}
1399
1400impl RemoteSource for SourceDist {
1401 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1402 match self {
1403 Self::Registry(dist) => dist.filename(),
1404 Self::DirectUrl(dist) => dist.filename(),
1405 Self::GitPath(dist) => dist.filename(),
1406 Self::GitDirectory(dist) => dist.filename(),
1407 Self::Path(dist) => dist.filename(),
1408 Self::Directory(dist) => dist.filename(),
1409 }
1410 }
1411
1412 fn size(&self) -> Option<u64> {
1413 match self {
1414 Self::Registry(dist) => dist.size(),
1415 Self::DirectUrl(dist) => dist.size(),
1416 Self::GitPath(dist) => dist.size(),
1417 Self::GitDirectory(dist) => dist.size(),
1418 Self::Path(dist) => dist.size(),
1419 Self::Directory(dist) => dist.size(),
1420 }
1421 }
1422}
1423
1424impl RemoteSource for BuiltDist {
1425 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1426 match self {
1427 Self::Registry(dist) => dist.filename(),
1428 Self::DirectUrl(dist) => dist.filename(),
1429 Self::Path(dist) => dist.filename(),
1430 Self::GitPath(dist) => dist.filename(),
1431 }
1432 }
1433
1434 fn size(&self) -> Option<u64> {
1435 match self {
1436 Self::Registry(dist) => dist.size(),
1437 Self::DirectUrl(dist) => dist.size(),
1438 Self::Path(dist) => dist.size(),
1439 Self::GitPath(dist) => dist.size(),
1440 }
1441 }
1442}
1443
1444impl RemoteSource for Dist {
1445 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1446 match self {
1447 Self::Built(dist) => dist.filename(),
1448 Self::Source(dist) => dist.filename(),
1449 }
1450 }
1451
1452 fn size(&self) -> Option<u64> {
1453 match self {
1454 Self::Built(dist) => dist.size(),
1455 Self::Source(dist) => dist.size(),
1456 }
1457 }
1458}
1459
1460impl Identifier for DisplaySafeUrl {
1461 fn distribution_id(&self) -> DistributionId {
1462 DistributionId::Url(uv_cache_key::CanonicalUrl::new(self.clone()))
1463 }
1464
1465 fn resource_id(&self) -> ResourceId {
1466 ResourceId::Url(uv_cache_key::RepositoryUrl::new(self.clone()))
1467 }
1468}
1469
1470impl Identifier for File {
1471 fn distribution_id(&self) -> DistributionId {
1472 self.hashes
1473 .first()
1474 .cloned()
1475 .map(DistributionId::Digest)
1476 .unwrap_or_else(|| self.url.distribution_id())
1477 }
1478
1479 fn resource_id(&self) -> ResourceId {
1480 self.hashes
1481 .first()
1482 .cloned()
1483 .map(ResourceId::Digest)
1484 .unwrap_or_else(|| self.url.resource_id())
1485 }
1486}
1487
1488impl Identifier for Path {
1489 fn distribution_id(&self) -> DistributionId {
1490 DistributionId::PathBuf(self.to_path_buf())
1491 }
1492
1493 fn resource_id(&self) -> ResourceId {
1494 ResourceId::PathBuf(self.to_path_buf())
1495 }
1496}
1497
1498impl Identifier for FileLocation {
1499 fn distribution_id(&self) -> DistributionId {
1500 match self {
1501 Self::RelativeUrl(base, url) => {
1502 DistributionId::RelativeUrl(base.to_string(), url.to_string())
1503 }
1504 Self::AbsoluteUrl(url) => DistributionId::AbsoluteUrl(url.to_string()),
1505 }
1506 }
1507
1508 fn resource_id(&self) -> ResourceId {
1509 match self {
1510 Self::RelativeUrl(base, url) => {
1511 ResourceId::RelativeUrl(base.to_string(), url.to_string())
1512 }
1513 Self::AbsoluteUrl(url) => ResourceId::AbsoluteUrl(url.to_string()),
1514 }
1515 }
1516}
1517
1518impl Identifier for RegistryBuiltWheel {
1519 fn distribution_id(&self) -> DistributionId {
1520 self.file.distribution_id()
1521 }
1522
1523 fn resource_id(&self) -> ResourceId {
1524 self.file.resource_id()
1525 }
1526}
1527
1528impl Identifier for RegistryBuiltDist {
1529 fn distribution_id(&self) -> DistributionId {
1530 self.best_wheel().distribution_id()
1531 }
1532
1533 fn resource_id(&self) -> ResourceId {
1534 self.best_wheel().resource_id()
1535 }
1536}
1537
1538impl Identifier for RegistrySourceDist {
1539 fn distribution_id(&self) -> DistributionId {
1540 self.file.distribution_id()
1541 }
1542
1543 fn resource_id(&self) -> ResourceId {
1544 self.file.resource_id()
1545 }
1546}
1547
1548impl Identifier for DirectUrlBuiltDist {
1549 fn distribution_id(&self) -> DistributionId {
1550 self.url.distribution_id()
1551 }
1552
1553 fn resource_id(&self) -> ResourceId {
1554 self.url.resource_id()
1555 }
1556}
1557
1558impl Identifier for DirectUrlSourceDist {
1559 fn distribution_id(&self) -> DistributionId {
1560 self.url.distribution_id()
1561 }
1562
1563 fn resource_id(&self) -> ResourceId {
1564 self.url.resource_id()
1565 }
1566}
1567
1568impl Identifier for PathBuiltDist {
1569 fn distribution_id(&self) -> DistributionId {
1570 self.url.distribution_id()
1571 }
1572
1573 fn resource_id(&self) -> ResourceId {
1574 self.url.resource_id()
1575 }
1576}
1577
1578impl Identifier for GitPathBuiltDist {
1579 fn distribution_id(&self) -> DistributionId {
1580 self.url.distribution_id()
1581 }
1582
1583 fn resource_id(&self) -> ResourceId {
1584 self.url.resource_id()
1585 }
1586}
1587
1588impl Identifier for PathSourceDist {
1589 fn distribution_id(&self) -> DistributionId {
1590 self.url.distribution_id()
1591 }
1592
1593 fn resource_id(&self) -> ResourceId {
1594 self.url.resource_id()
1595 }
1596}
1597
1598impl Identifier for DirectorySourceDist {
1599 fn distribution_id(&self) -> DistributionId {
1600 self.url.distribution_id()
1601 }
1602
1603 fn resource_id(&self) -> ResourceId {
1604 self.url.resource_id()
1605 }
1606}
1607
1608impl Identifier for GitPathSourceDist {
1609 fn distribution_id(&self) -> DistributionId {
1610 self.url.distribution_id()
1611 }
1612
1613 fn resource_id(&self) -> ResourceId {
1614 self.url.resource_id()
1615 }
1616}
1617
1618impl Identifier for GitDirectorySourceDist {
1619 fn distribution_id(&self) -> DistributionId {
1620 self.url.distribution_id()
1621 }
1622
1623 fn resource_id(&self) -> ResourceId {
1624 self.url.resource_id()
1625 }
1626}
1627
1628impl Identifier for SourceDist {
1629 fn distribution_id(&self) -> DistributionId {
1630 match self {
1631 Self::Registry(dist) => dist.distribution_id(),
1632 Self::DirectUrl(dist) => dist.distribution_id(),
1633 Self::GitPath(dist) => dist.distribution_id(),
1634 Self::GitDirectory(dist) => dist.distribution_id(),
1635 Self::Path(dist) => dist.distribution_id(),
1636 Self::Directory(dist) => dist.distribution_id(),
1637 }
1638 }
1639
1640 fn resource_id(&self) -> ResourceId {
1641 match self {
1642 Self::Registry(dist) => dist.resource_id(),
1643 Self::DirectUrl(dist) => dist.resource_id(),
1644 Self::GitPath(dist) => dist.resource_id(),
1645 Self::GitDirectory(dist) => dist.resource_id(),
1646 Self::Path(dist) => dist.resource_id(),
1647 Self::Directory(dist) => dist.resource_id(),
1648 }
1649 }
1650}
1651
1652impl Identifier for BuiltDist {
1653 fn distribution_id(&self) -> DistributionId {
1654 match self {
1655 Self::Registry(dist) => dist.distribution_id(),
1656 Self::DirectUrl(dist) => dist.distribution_id(),
1657 Self::Path(dist) => dist.distribution_id(),
1658 Self::GitPath(dist) => dist.distribution_id(),
1659 }
1660 }
1661
1662 fn resource_id(&self) -> ResourceId {
1663 match self {
1664 Self::Registry(dist) => dist.resource_id(),
1665 Self::DirectUrl(dist) => dist.resource_id(),
1666 Self::Path(dist) => dist.resource_id(),
1667 Self::GitPath(dist) => dist.resource_id(),
1668 }
1669 }
1670}
1671
1672impl Identifier for InstalledDist {
1673 fn distribution_id(&self) -> DistributionId {
1674 self.install_path().distribution_id()
1675 }
1676
1677 fn resource_id(&self) -> ResourceId {
1678 self.install_path().resource_id()
1679 }
1680}
1681
1682impl Identifier for Dist {
1683 fn distribution_id(&self) -> DistributionId {
1684 match self {
1685 Self::Built(dist) => dist.distribution_id(),
1686 Self::Source(dist) => dist.distribution_id(),
1687 }
1688 }
1689
1690 fn resource_id(&self) -> ResourceId {
1691 match self {
1692 Self::Built(dist) => dist.resource_id(),
1693 Self::Source(dist) => dist.resource_id(),
1694 }
1695 }
1696}
1697
1698impl Identifier for DirectSourceUrl<'_> {
1699 fn distribution_id(&self) -> DistributionId {
1700 self.url.distribution_id()
1701 }
1702
1703 fn resource_id(&self) -> ResourceId {
1704 self.url.resource_id()
1705 }
1706}
1707
1708impl Identifier for GitDirectorySourceUrl<'_> {
1709 fn distribution_id(&self) -> DistributionId {
1710 self.url.distribution_id()
1711 }
1712
1713 fn resource_id(&self) -> ResourceId {
1714 self.url.resource_id()
1715 }
1716}
1717
1718impl Identifier for GitPathSourceUrl<'_> {
1719 fn distribution_id(&self) -> DistributionId {
1720 self.url.distribution_id()
1721 }
1722
1723 fn resource_id(&self) -> ResourceId {
1724 self.url.resource_id()
1725 }
1726}
1727
1728impl Identifier for PathSourceUrl<'_> {
1729 fn distribution_id(&self) -> DistributionId {
1730 self.url.distribution_id()
1731 }
1732
1733 fn resource_id(&self) -> ResourceId {
1734 self.url.resource_id()
1735 }
1736}
1737
1738impl Identifier for DirectorySourceUrl<'_> {
1739 fn distribution_id(&self) -> DistributionId {
1740 self.url.distribution_id()
1741 }
1742
1743 fn resource_id(&self) -> ResourceId {
1744 self.url.resource_id()
1745 }
1746}
1747
1748impl Identifier for SourceUrl<'_> {
1749 fn distribution_id(&self) -> DistributionId {
1750 match self {
1751 Self::Direct(url) => url.distribution_id(),
1752 Self::GitDirectory(url) => url.distribution_id(),
1753 Self::GitPath(url) => url.distribution_id(),
1754 Self::Path(url) => url.distribution_id(),
1755 Self::Directory(url) => url.distribution_id(),
1756 }
1757 }
1758
1759 fn resource_id(&self) -> ResourceId {
1760 match self {
1761 Self::Direct(url) => url.resource_id(),
1762 Self::GitDirectory(url) => url.resource_id(),
1763 Self::GitPath(url) => url.resource_id(),
1764 Self::Path(url) => url.resource_id(),
1765 Self::Directory(url) => url.resource_id(),
1766 }
1767 }
1768}
1769
1770impl Identifier for BuildableSource<'_> {
1771 fn distribution_id(&self) -> DistributionId {
1772 match self {
1773 Self::Dist(source) => source.distribution_id(),
1774 Self::Url(source) => source.distribution_id(),
1775 }
1776 }
1777
1778 fn resource_id(&self) -> ResourceId {
1779 match self {
1780 Self::Dist(source) => source.resource_id(),
1781 Self::Url(source) => source.resource_id(),
1782 }
1783 }
1784}
1785
1786#[cfg(test)]
1787mod test {
1788 use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
1789 use uv_redacted::DisplaySafeUrl;
1790
1791 #[test]
1793 fn dist_size() {
1794 assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>());
1795 assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>());
1796 assert!(
1797 size_of::<SourceDist>() <= 176,
1798 "{}",
1799 size_of::<SourceDist>()
1800 );
1801 }
1802
1803 #[test]
1804 fn remote_source() {
1805 for url in [
1806 "https://example.com/foo-0.1.0.tar.gz",
1807 "https://example.com/foo%2D0.1.0.tar.gz",
1808 "https://example.com/foo-0.1.0.tar.gz#fragment",
1809 "https://example.com/foo-0.1.0.tar.gz?query",
1810 "https://example.com/foo-0.1.0.tar.gz?query#fragment",
1811 "https://example.com/foo-0.1.0.tar.gz#fragment?query",
1812 "https://example.com/foo-0.1.0.tar.gz#fragment/3?query",
1813 "https://example.com/foo%2D0.1.0.tar.gz#fragment/3?query",
1814 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment",
1815 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment/3",
1816 "https://example.com/foo%2D0.1.0.tar.gz?query=1/2#fragment/3",
1817 ] {
1818 let url = DisplaySafeUrl::parse(url).unwrap();
1819 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1820 let url = UrlString::from(url.clone());
1821 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1822 }
1823 }
1824
1825 #[test]
1826 fn remote_source_redacts_missing_path_segments() {
1827 for (input, expected) in [
1828 (
1829 "mailto:ferris@example.com?X-Amz-Signature=sentinel",
1830 "mailto:ferris@example.com?X-Amz-Signature=****",
1831 ),
1832 (
1833 "ssh://user:secret@example.com?sig=sentinel",
1834 "ssh://user:****@example.com?sig=****",
1835 ),
1836 (
1837 "mailto:ferris@example.com?sig=one&X-Amz-Credential=two&X-Amz-Security-Token=three&X-Amz-Signature=four&token=kept#fragment",
1838 "mailto:ferris@example.com?sig=****&X-Amz-Credential=****&X-Amz-Security-Token=****&X-Amz-Signature=****&token=kept#fragment",
1839 ),
1840 (
1841 "mailto:ferris@example.com?x-amz%2dsignature=sentinel&safe=value",
1842 "mailto:ferris@example.com?x-amz-signature=****&safe=value",
1843 ),
1844 (
1845 "mailto:ferris@example.com?token=kept#fragment",
1846 "mailto:ferris@example.com?token=kept#fragment",
1847 ),
1848 ] {
1849 let url = url::Url::parse(input).unwrap();
1850 let error = RemoteSource::filename(&url).unwrap_err();
1851 let crate::Error::MissingPathSegments(payload) = &error else {
1852 panic!("expected missing path segments");
1853 };
1854 assert_eq!(payload, expected);
1855 assert_eq!(
1856 error.to_string(),
1857 format!("Could not extract path segments from URL: {expected}")
1858 );
1859 assert_eq!(
1860 format!("{error:?}"),
1861 format!("MissingPathSegments({expected:?})")
1862 );
1863 assert!(std::error::Error::source(&error).is_none());
1864 assert_eq!(url.as_str(), input);
1865 }
1866
1867 let input = "https://example.org/demo%20name.whl?sig=sentinel";
1868 let url = url::Url::parse(input).unwrap();
1869 assert_eq!(RemoteSource::filename(&url).unwrap(), "demo name.whl");
1870 assert_eq!(url.as_str(), input);
1871 }
1872}