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