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