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