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