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