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