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(), 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(self.location.as_ref(), self.subdirectory.as_deref())
1060 }
1061}
1062
1063impl DistributionMetadata for GitPathSourceDist {
1064 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1065 VersionOrUrlRef::Url(&self.url)
1066 }
1067
1068 fn version_id(&self) -> VersionId {
1069 VersionId::from_git(self.git.as_ref(), Some(&self.install_path))
1070 }
1071}
1072
1073impl DistributionMetadata for GitDirectorySourceDist {
1074 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1075 VersionOrUrlRef::Url(&self.url)
1076 }
1077
1078 fn version_id(&self) -> VersionId {
1079 VersionId::from_git(self.git.as_ref(), self.subdirectory.as_deref())
1080 }
1081}
1082
1083impl DistributionMetadata for PathSourceDist {
1084 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1085 VersionOrUrlRef::Url(&self.url)
1086 }
1087
1088 fn version_id(&self) -> VersionId {
1089 VersionId::from_path(self.install_path.as_ref())
1090 }
1091}
1092
1093impl DistributionMetadata for DirectorySourceDist {
1094 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1095 VersionOrUrlRef::Url(&self.url)
1096 }
1097
1098 fn version_id(&self) -> VersionId {
1099 VersionId::from_directory(self.install_path.as_ref())
1100 }
1101}
1102
1103impl DistributionMetadata for SourceDist {
1104 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1105 match self {
1106 Self::Registry(dist) => dist.version_or_url(),
1107 Self::DirectUrl(dist) => dist.version_or_url(),
1108 Self::GitPath(dist) => dist.version_or_url(),
1109 Self::GitDirectory(dist) => dist.version_or_url(),
1110 Self::Path(dist) => dist.version_or_url(),
1111 Self::Directory(dist) => dist.version_or_url(),
1112 }
1113 }
1114
1115 fn version_id(&self) -> VersionId {
1116 match self {
1117 Self::Registry(dist) => dist.version_id(),
1118 Self::DirectUrl(dist) => dist.version_id(),
1119 Self::GitPath(dist) => dist.version_id(),
1120 Self::GitDirectory(dist) => dist.version_id(),
1121 Self::Path(dist) => dist.version_id(),
1122 Self::Directory(dist) => dist.version_id(),
1123 }
1124 }
1125}
1126
1127impl DistributionMetadata for BuiltDist {
1128 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1129 match self {
1130 Self::Registry(dist) => dist.version_or_url(),
1131 Self::DirectUrl(dist) => dist.version_or_url(),
1132 Self::Path(dist) => dist.version_or_url(),
1133 Self::GitPath(dist) => dist.version_or_url(),
1134 }
1135 }
1136
1137 fn version_id(&self) -> VersionId {
1138 match self {
1139 Self::Registry(dist) => dist.version_id(),
1140 Self::DirectUrl(dist) => dist.version_id(),
1141 Self::Path(dist) => dist.version_id(),
1142 Self::GitPath(dist) => dist.version_id(),
1143 }
1144 }
1145}
1146
1147impl DistributionMetadata for Dist {
1148 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1149 match self {
1150 Self::Built(dist) => dist.version_or_url(),
1151 Self::Source(dist) => dist.version_or_url(),
1152 }
1153 }
1154
1155 fn version_id(&self) -> VersionId {
1156 match self {
1157 Self::Built(dist) => dist.version_id(),
1158 Self::Source(dist) => dist.version_id(),
1159 }
1160 }
1161}
1162
1163impl RemoteSource for File {
1164 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1165 Ok(Cow::Borrowed(&self.filename))
1166 }
1167
1168 fn size(&self) -> Option<u64> {
1169 self.size
1170 }
1171}
1172
1173impl RemoteSource for Url {
1174 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1175 let mut path_segments = self
1177 .path_segments()
1178 .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1179
1180 let last = path_segments
1182 .next_back()
1183 .expect("path segments is non-empty");
1184
1185 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1187
1188 Ok(filename)
1189 }
1190
1191 fn size(&self) -> Option<u64> {
1192 None
1193 }
1194}
1195
1196impl RemoteSource for UrlString {
1197 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1198 let last = self
1200 .base_str()
1201 .split('/')
1202 .next_back()
1203 .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1204
1205 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1207
1208 Ok(filename)
1209 }
1210
1211 fn size(&self) -> Option<u64> {
1212 None
1213 }
1214}
1215
1216impl RemoteSource for RegistryBuiltWheel {
1217 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1218 self.file.filename()
1219 }
1220
1221 fn size(&self) -> Option<u64> {
1222 self.file.size()
1223 }
1224}
1225
1226impl RemoteSource for RegistryBuiltDist {
1227 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1228 self.best_wheel().filename()
1229 }
1230
1231 fn size(&self) -> Option<u64> {
1232 self.best_wheel().size()
1233 }
1234}
1235
1236impl RemoteSource for RegistrySourceDist {
1237 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1238 self.file.filename()
1239 }
1240
1241 fn size(&self) -> Option<u64> {
1242 self.file.size()
1243 }
1244}
1245
1246impl RemoteSource for DirectUrlBuiltDist {
1247 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1248 self.url.filename()
1249 }
1250
1251 fn size(&self) -> Option<u64> {
1252 self.url.size()
1253 }
1254}
1255
1256impl RemoteSource for DirectUrlSourceDist {
1257 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1258 self.url.filename()
1259 }
1260
1261 fn size(&self) -> Option<u64> {
1262 self.url.size()
1263 }
1264}
1265
1266impl RemoteSource for GitPathSourceDist {
1267 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1268 match self.url.filename()? {
1270 Cow::Borrowed(filename) => {
1271 if let Some((_, filename)) = filename.rsplit_once('@') {
1272 Ok(Cow::Borrowed(filename))
1273 } else {
1274 Ok(Cow::Borrowed(filename))
1275 }
1276 }
1277 Cow::Owned(filename) => {
1278 if let Some((_, filename)) = filename.rsplit_once('@') {
1279 Ok(Cow::Owned(filename.to_owned()))
1280 } else {
1281 Ok(Cow::Owned(filename))
1282 }
1283 }
1284 }
1285 }
1286
1287 fn size(&self) -> Option<u64> {
1288 self.url.size()
1289 }
1290}
1291
1292impl RemoteSource for GitDirectorySourceDist {
1293 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1294 match self.url.filename()? {
1296 Cow::Borrowed(filename) => {
1297 if let Some((_, filename)) = filename.rsplit_once('@') {
1298 Ok(Cow::Borrowed(filename))
1299 } else {
1300 Ok(Cow::Borrowed(filename))
1301 }
1302 }
1303 Cow::Owned(filename) => {
1304 if let Some((_, filename)) = filename.rsplit_once('@') {
1305 Ok(Cow::Owned(filename.to_owned()))
1306 } else {
1307 Ok(Cow::Owned(filename))
1308 }
1309 }
1310 }
1311 }
1312
1313 fn size(&self) -> Option<u64> {
1314 self.url.size()
1315 }
1316}
1317
1318impl RemoteSource for PathBuiltDist {
1319 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1320 self.url.filename()
1321 }
1322
1323 fn size(&self) -> Option<u64> {
1324 self.url.size()
1325 }
1326}
1327
1328impl RemoteSource for GitPathBuiltDist {
1329 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1330 self.url.filename()
1331 }
1332
1333 fn size(&self) -> Option<u64> {
1334 self.url.size()
1335 }
1336}
1337
1338impl RemoteSource for PathSourceDist {
1339 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1340 self.url.filename()
1341 }
1342
1343 fn size(&self) -> Option<u64> {
1344 self.url.size()
1345 }
1346}
1347
1348impl RemoteSource for DirectorySourceDist {
1349 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1350 self.url.filename()
1351 }
1352
1353 fn size(&self) -> Option<u64> {
1354 self.url.size()
1355 }
1356}
1357
1358impl RemoteSource for SourceDist {
1359 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1360 match self {
1361 Self::Registry(dist) => dist.filename(),
1362 Self::DirectUrl(dist) => dist.filename(),
1363 Self::GitPath(dist) => dist.filename(),
1364 Self::GitDirectory(dist) => dist.filename(),
1365 Self::Path(dist) => dist.filename(),
1366 Self::Directory(dist) => dist.filename(),
1367 }
1368 }
1369
1370 fn size(&self) -> Option<u64> {
1371 match self {
1372 Self::Registry(dist) => dist.size(),
1373 Self::DirectUrl(dist) => dist.size(),
1374 Self::GitPath(dist) => dist.size(),
1375 Self::GitDirectory(dist) => dist.size(),
1376 Self::Path(dist) => dist.size(),
1377 Self::Directory(dist) => dist.size(),
1378 }
1379 }
1380}
1381
1382impl RemoteSource for BuiltDist {
1383 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1384 match self {
1385 Self::Registry(dist) => dist.filename(),
1386 Self::DirectUrl(dist) => dist.filename(),
1387 Self::Path(dist) => dist.filename(),
1388 Self::GitPath(dist) => dist.filename(),
1389 }
1390 }
1391
1392 fn size(&self) -> Option<u64> {
1393 match self {
1394 Self::Registry(dist) => dist.size(),
1395 Self::DirectUrl(dist) => dist.size(),
1396 Self::Path(dist) => dist.size(),
1397 Self::GitPath(dist) => dist.size(),
1398 }
1399 }
1400}
1401
1402impl RemoteSource for Dist {
1403 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1404 match self {
1405 Self::Built(dist) => dist.filename(),
1406 Self::Source(dist) => dist.filename(),
1407 }
1408 }
1409
1410 fn size(&self) -> Option<u64> {
1411 match self {
1412 Self::Built(dist) => dist.size(),
1413 Self::Source(dist) => dist.size(),
1414 }
1415 }
1416}
1417
1418impl Identifier for DisplaySafeUrl {
1419 fn distribution_id(&self) -> DistributionId {
1420 DistributionId::Url(uv_cache_key::CanonicalUrl::new(self))
1421 }
1422
1423 fn resource_id(&self) -> ResourceId {
1424 ResourceId::Url(uv_cache_key::RepositoryUrl::new(self))
1425 }
1426}
1427
1428impl Identifier for File {
1429 fn distribution_id(&self) -> DistributionId {
1430 self.hashes
1431 .first()
1432 .cloned()
1433 .map(DistributionId::Digest)
1434 .unwrap_or_else(|| self.url.distribution_id())
1435 }
1436
1437 fn resource_id(&self) -> ResourceId {
1438 self.hashes
1439 .first()
1440 .cloned()
1441 .map(ResourceId::Digest)
1442 .unwrap_or_else(|| self.url.resource_id())
1443 }
1444}
1445
1446impl Identifier for Path {
1447 fn distribution_id(&self) -> DistributionId {
1448 DistributionId::PathBuf(self.to_path_buf())
1449 }
1450
1451 fn resource_id(&self) -> ResourceId {
1452 ResourceId::PathBuf(self.to_path_buf())
1453 }
1454}
1455
1456impl Identifier for FileLocation {
1457 fn distribution_id(&self) -> DistributionId {
1458 match self {
1459 Self::RelativeUrl(base, url) => {
1460 DistributionId::RelativeUrl(base.to_string(), url.to_string())
1461 }
1462 Self::AbsoluteUrl(url) => DistributionId::AbsoluteUrl(url.to_string()),
1463 }
1464 }
1465
1466 fn resource_id(&self) -> ResourceId {
1467 match self {
1468 Self::RelativeUrl(base, url) => {
1469 ResourceId::RelativeUrl(base.to_string(), url.to_string())
1470 }
1471 Self::AbsoluteUrl(url) => ResourceId::AbsoluteUrl(url.to_string()),
1472 }
1473 }
1474}
1475
1476impl Identifier for RegistryBuiltWheel {
1477 fn distribution_id(&self) -> DistributionId {
1478 self.file.distribution_id()
1479 }
1480
1481 fn resource_id(&self) -> ResourceId {
1482 self.file.resource_id()
1483 }
1484}
1485
1486impl Identifier for RegistryBuiltDist {
1487 fn distribution_id(&self) -> DistributionId {
1488 self.best_wheel().distribution_id()
1489 }
1490
1491 fn resource_id(&self) -> ResourceId {
1492 self.best_wheel().resource_id()
1493 }
1494}
1495
1496impl Identifier for RegistrySourceDist {
1497 fn distribution_id(&self) -> DistributionId {
1498 self.file.distribution_id()
1499 }
1500
1501 fn resource_id(&self) -> ResourceId {
1502 self.file.resource_id()
1503 }
1504}
1505
1506impl Identifier for DirectUrlBuiltDist {
1507 fn distribution_id(&self) -> DistributionId {
1508 self.url.distribution_id()
1509 }
1510
1511 fn resource_id(&self) -> ResourceId {
1512 self.url.resource_id()
1513 }
1514}
1515
1516impl Identifier for DirectUrlSourceDist {
1517 fn distribution_id(&self) -> DistributionId {
1518 self.url.distribution_id()
1519 }
1520
1521 fn resource_id(&self) -> ResourceId {
1522 self.url.resource_id()
1523 }
1524}
1525
1526impl Identifier for PathBuiltDist {
1527 fn distribution_id(&self) -> DistributionId {
1528 self.url.distribution_id()
1529 }
1530
1531 fn resource_id(&self) -> ResourceId {
1532 self.url.resource_id()
1533 }
1534}
1535
1536impl Identifier for GitPathBuiltDist {
1537 fn distribution_id(&self) -> DistributionId {
1538 self.url.distribution_id()
1539 }
1540
1541 fn resource_id(&self) -> ResourceId {
1542 self.url.resource_id()
1543 }
1544}
1545
1546impl Identifier for PathSourceDist {
1547 fn distribution_id(&self) -> DistributionId {
1548 self.url.distribution_id()
1549 }
1550
1551 fn resource_id(&self) -> ResourceId {
1552 self.url.resource_id()
1553 }
1554}
1555
1556impl Identifier for DirectorySourceDist {
1557 fn distribution_id(&self) -> DistributionId {
1558 self.url.distribution_id()
1559 }
1560
1561 fn resource_id(&self) -> ResourceId {
1562 self.url.resource_id()
1563 }
1564}
1565
1566impl Identifier for GitPathSourceDist {
1567 fn distribution_id(&self) -> DistributionId {
1568 self.url.distribution_id()
1569 }
1570
1571 fn resource_id(&self) -> ResourceId {
1572 self.url.resource_id()
1573 }
1574}
1575
1576impl Identifier for GitDirectorySourceDist {
1577 fn distribution_id(&self) -> DistributionId {
1578 self.url.distribution_id()
1579 }
1580
1581 fn resource_id(&self) -> ResourceId {
1582 self.url.resource_id()
1583 }
1584}
1585
1586impl Identifier for SourceDist {
1587 fn distribution_id(&self) -> DistributionId {
1588 match self {
1589 Self::Registry(dist) => dist.distribution_id(),
1590 Self::DirectUrl(dist) => dist.distribution_id(),
1591 Self::GitPath(dist) => dist.distribution_id(),
1592 Self::GitDirectory(dist) => dist.distribution_id(),
1593 Self::Path(dist) => dist.distribution_id(),
1594 Self::Directory(dist) => dist.distribution_id(),
1595 }
1596 }
1597
1598 fn resource_id(&self) -> ResourceId {
1599 match self {
1600 Self::Registry(dist) => dist.resource_id(),
1601 Self::DirectUrl(dist) => dist.resource_id(),
1602 Self::GitPath(dist) => dist.resource_id(),
1603 Self::GitDirectory(dist) => dist.resource_id(),
1604 Self::Path(dist) => dist.resource_id(),
1605 Self::Directory(dist) => dist.resource_id(),
1606 }
1607 }
1608}
1609
1610impl Identifier for BuiltDist {
1611 fn distribution_id(&self) -> DistributionId {
1612 match self {
1613 Self::Registry(dist) => dist.distribution_id(),
1614 Self::DirectUrl(dist) => dist.distribution_id(),
1615 Self::Path(dist) => dist.distribution_id(),
1616 Self::GitPath(dist) => dist.distribution_id(),
1617 }
1618 }
1619
1620 fn resource_id(&self) -> ResourceId {
1621 match self {
1622 Self::Registry(dist) => dist.resource_id(),
1623 Self::DirectUrl(dist) => dist.resource_id(),
1624 Self::Path(dist) => dist.resource_id(),
1625 Self::GitPath(dist) => dist.resource_id(),
1626 }
1627 }
1628}
1629
1630impl Identifier for InstalledDist {
1631 fn distribution_id(&self) -> DistributionId {
1632 self.install_path().distribution_id()
1633 }
1634
1635 fn resource_id(&self) -> ResourceId {
1636 self.install_path().resource_id()
1637 }
1638}
1639
1640impl Identifier for Dist {
1641 fn distribution_id(&self) -> DistributionId {
1642 match self {
1643 Self::Built(dist) => dist.distribution_id(),
1644 Self::Source(dist) => dist.distribution_id(),
1645 }
1646 }
1647
1648 fn resource_id(&self) -> ResourceId {
1649 match self {
1650 Self::Built(dist) => dist.resource_id(),
1651 Self::Source(dist) => dist.resource_id(),
1652 }
1653 }
1654}
1655
1656impl Identifier for DirectSourceUrl<'_> {
1657 fn distribution_id(&self) -> DistributionId {
1658 self.url.distribution_id()
1659 }
1660
1661 fn resource_id(&self) -> ResourceId {
1662 self.url.resource_id()
1663 }
1664}
1665
1666impl Identifier for GitDirectorySourceUrl<'_> {
1667 fn distribution_id(&self) -> DistributionId {
1668 self.url.distribution_id()
1669 }
1670
1671 fn resource_id(&self) -> ResourceId {
1672 self.url.resource_id()
1673 }
1674}
1675
1676impl Identifier for GitPathSourceUrl<'_> {
1677 fn distribution_id(&self) -> DistributionId {
1678 self.url.distribution_id()
1679 }
1680
1681 fn resource_id(&self) -> ResourceId {
1682 self.url.resource_id()
1683 }
1684}
1685
1686impl Identifier for PathSourceUrl<'_> {
1687 fn distribution_id(&self) -> DistributionId {
1688 self.url.distribution_id()
1689 }
1690
1691 fn resource_id(&self) -> ResourceId {
1692 self.url.resource_id()
1693 }
1694}
1695
1696impl Identifier for DirectorySourceUrl<'_> {
1697 fn distribution_id(&self) -> DistributionId {
1698 self.url.distribution_id()
1699 }
1700
1701 fn resource_id(&self) -> ResourceId {
1702 self.url.resource_id()
1703 }
1704}
1705
1706impl Identifier for SourceUrl<'_> {
1707 fn distribution_id(&self) -> DistributionId {
1708 match self {
1709 Self::Direct(url) => url.distribution_id(),
1710 Self::GitDirectory(url) => url.distribution_id(),
1711 Self::GitPath(url) => url.distribution_id(),
1712 Self::Path(url) => url.distribution_id(),
1713 Self::Directory(url) => url.distribution_id(),
1714 }
1715 }
1716
1717 fn resource_id(&self) -> ResourceId {
1718 match self {
1719 Self::Direct(url) => url.resource_id(),
1720 Self::GitDirectory(url) => url.resource_id(),
1721 Self::GitPath(url) => url.resource_id(),
1722 Self::Path(url) => url.resource_id(),
1723 Self::Directory(url) => url.resource_id(),
1724 }
1725 }
1726}
1727
1728impl Identifier for BuildableSource<'_> {
1729 fn distribution_id(&self) -> DistributionId {
1730 match self {
1731 Self::Dist(source) => source.distribution_id(),
1732 Self::Url(source) => source.distribution_id(),
1733 }
1734 }
1735
1736 fn resource_id(&self) -> ResourceId {
1737 match self {
1738 Self::Dist(source) => source.resource_id(),
1739 Self::Url(source) => source.resource_id(),
1740 }
1741 }
1742}
1743
1744#[cfg(test)]
1745mod test {
1746 use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
1747 use uv_redacted::DisplaySafeUrl;
1748
1749 #[test]
1751 fn dist_size() {
1752 assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>());
1753 assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>());
1754 assert!(
1755 size_of::<SourceDist>() <= 176,
1756 "{}",
1757 size_of::<SourceDist>()
1758 );
1759 }
1760
1761 #[test]
1762 fn remote_source() {
1763 for url in [
1764 "https://example.com/foo-0.1.0.tar.gz",
1765 "https://example.com/foo-0.1.0.tar.gz#fragment",
1766 "https://example.com/foo-0.1.0.tar.gz?query",
1767 "https://example.com/foo-0.1.0.tar.gz?query#fragment",
1768 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment",
1769 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment/3",
1770 ] {
1771 let url = DisplaySafeUrl::parse(url).unwrap();
1772 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1773 let url = UrlString::from(url.clone());
1774 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1775 }
1776 }
1777}