diff --git a/crates/uv-auth/src/pyx.rs b/crates/uv-auth/src/pyx.rs index b2804dad8..c670f410d 100644 --- a/crates/uv-auth/src/pyx.rs +++ b/crates/uv-auth/src/pyx.rs @@ -591,7 +591,7 @@ mod tests { #[test] fn test_is_known_url() { - let api_url = DisplaySafeUrl::from(Url::parse("https://api.pyx.dev").unwrap()); + let api_url = DisplaySafeUrl::parse("https://api.pyx.dev").unwrap(); let cdn_domain = "astralhosted.com"; // Same realm as API. @@ -646,7 +646,7 @@ mod tests { #[test] fn test_is_known_domain() { - let api_url = DisplaySafeUrl::from(Url::parse("https://api.pyx.dev").unwrap()); + let api_url = DisplaySafeUrl::parse("https://api.pyx.dev").unwrap(); let cdn_domain = "astralhosted.com"; // Same realm as API. diff --git a/crates/uv-auth/src/realm.rs b/crates/uv-auth/src/realm.rs index c23d48649..718da0951 100644 --- a/crates/uv-auth/src/realm.rs +++ b/crates/uv-auth/src/realm.rs @@ -1,6 +1,7 @@ use std::hash::{Hash, Hasher}; use std::{fmt::Display, fmt::Formatter}; use url::Url; +use uv_redacted::DisplaySafeUrl; use uv_small_str::SmallString; /// Used to determine if authentication information should be retained on a new URL. @@ -29,6 +30,12 @@ pub struct Realm { port: Option, } +impl From<&DisplaySafeUrl> for Realm { + fn from(url: &DisplaySafeUrl) -> Self { + Self::from(&**url) + } +} + impl From<&Url> for Realm { fn from(url: &Url) -> Self { Self { diff --git a/crates/uv-auth/src/store.rs b/crates/uv-auth/src/store.rs index d51cc0e26..fb05150e3 100644 --- a/crates/uv-auth/src/store.rs +++ b/crates/uv-auth/src/store.rs @@ -5,7 +5,6 @@ use fs_err as fs; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use thiserror::Error; -use url::Url; use uv_fs::{LockedFile, with_added_extension}; use uv_preview::{Preview, PreviewFeatures}; use uv_redacted::DisplaySafeUrl; @@ -310,13 +309,17 @@ impl TextCredentialStore { /// Get credentials for a given URL and username. /// /// The most specific URL prefix match in the same [`Realm`] is returned, if any. - pub fn get_credentials(&self, url: &Url, username: Option<&str>) -> Option<&Credentials> { + pub fn get_credentials( + &self, + url: &DisplaySafeUrl, + username: Option<&str>, + ) -> Option<&Credentials> { let request_realm = Realm::from(url); // Perform an exact lookup first // TODO(zanieb): Consider adding `DisplaySafeUrlRef` so we can avoid this clone // TODO(zanieb): We could also return early here if we can't normalize to a `Service` - if let Ok(url_service) = Service::try_from(DisplaySafeUrl::from(url.clone())) { + if let Ok(url_service) = Service::try_from(url.clone()) { if let Some(credential) = self.credentials.get(&( url_service.clone(), Username::from(username.map(str::to_string)), @@ -430,10 +433,10 @@ mod tests { let service = Service::from_str("https://example.com").unwrap(); store.insert(service.clone(), credentials.clone()); - let url = Url::parse("https://example.com/").unwrap(); + let url = DisplaySafeUrl::parse("https://example.com/").unwrap(); assert!(store.get_credentials(&url, None).is_some()); - let url = Url::parse("https://example.com/path").unwrap(); + let url = DisplaySafeUrl::parse("https://example.com/path").unwrap(); let retrieved = store.get_credentials(&url, None).unwrap(); assert_eq!(retrieved.username(), Some("user")); assert_eq!(retrieved.password(), Some("pass")); @@ -443,7 +446,7 @@ mod tests { .remove(&service, Username::from(Some("user".to_string()))) .is_some() ); - let url = Url::parse("https://example.com/").unwrap(); + let url = DisplaySafeUrl::parse("https://example.com/").unwrap(); assert!(store.get_credentials(&url, None).is_none()); } @@ -469,12 +472,12 @@ password = "pass2" let store = TextCredentialStore::from_file(temp_file.path()).unwrap(); - let url = Url::parse("https://example.com/").unwrap(); + let url = DisplaySafeUrl::parse("https://example.com/").unwrap(); assert!(store.get_credentials(&url, None).is_some()); - let url = Url::parse("https://test.org/").unwrap(); + let url = DisplaySafeUrl::parse("https://test.org/").unwrap(); assert!(store.get_credentials(&url, None).is_some()); - let url = Url::parse("https://example.com").unwrap(); + let url = DisplaySafeUrl::parse("https://example.com").unwrap(); let cred = store.get_credentials(&url, None).unwrap(); assert_eq!(cred.username(), Some("testuser")); assert_eq!(cred.password(), Some("testpass")); @@ -510,7 +513,7 @@ password = "pass2" ]; for url_str in matching_urls { - let url = Url::parse(url_str).unwrap(); + let url = DisplaySafeUrl::parse(url_str).unwrap(); let cred = store.get_credentials(&url, None); assert!(cred.is_some(), "Failed to match URL with prefix: {url_str}"); } @@ -523,7 +526,7 @@ password = "pass2" ]; for url_str in non_matching_urls { - let url = Url::parse(url_str).unwrap(); + let url = DisplaySafeUrl::parse(url_str).unwrap(); let cred = store.get_credentials(&url, None); assert!(cred.is_none(), "Should not match non-prefix URL: {url_str}"); } @@ -547,7 +550,7 @@ password = "pass2" ]; for url_str in matching_urls { - let url = Url::parse(url_str).unwrap(); + let url = DisplaySafeUrl::parse(url_str).unwrap(); let cred = store.get_credentials(&url, None); assert!( cred.is_some(), @@ -563,7 +566,7 @@ password = "pass2" ]; for url_str in non_matching_urls { - let url = Url::parse(url_str).unwrap(); + let url = DisplaySafeUrl::parse(url_str).unwrap(); let cred = store.get_credentials(&url, None); assert!( cred.is_none(), @@ -587,12 +590,12 @@ password = "pass2" store.insert(specific_service.clone(), specific_cred); // Should match the most specific prefix - let url = Url::parse("https://example.com/api/v1/users").unwrap(); + let url = DisplaySafeUrl::parse("https://example.com/api/v1/users").unwrap(); let cred = store.get_credentials(&url, None).unwrap(); assert_eq!(cred.username(), Some("specific")); // Should match the general prefix for non-specific paths - let url = Url::parse("https://example.com/api/v2").unwrap(); + let url = DisplaySafeUrl::parse("https://example.com/api/v2").unwrap(); let cred = store.get_credentials(&url, None).unwrap(); assert_eq!(cred.username(), Some("general")); } @@ -600,7 +603,7 @@ password = "pass2" #[test] fn test_username_exact_url_match() { let mut store = TextCredentialStore::default(); - let url = Url::parse("https://example.com").unwrap(); + let url = DisplaySafeUrl::parse("https://example.com").unwrap(); let service = Service::from_str("https://example.com").unwrap(); let user1_creds = Credentials::basic(Some("user1".to_string()), Some("pass1".to_string())); store.insert(service.clone(), user1_creds.clone()); @@ -641,7 +644,7 @@ password = "pass2" store.insert(general_service, general_creds); store.insert(specific_service, specific_creds); - let url = Url::parse("https://example.com/api/v1/users").unwrap(); + let url = DisplaySafeUrl::parse("https://example.com/api/v1/users").unwrap(); // Should match specific credentials when username matches let result = store.get_credentials(&url, Some("specific_user")); diff --git a/crates/uv-client/src/html.rs b/crates/uv-client/src/html.rs index a9f2ce161..1233096e6 100644 --- a/crates/uv-client/src/html.rs +++ b/crates/uv-client/src/html.rs @@ -3,7 +3,6 @@ use std::str::FromStr; use jiff::Timestamp; use tl::HTMLTag; use tracing::{debug, instrument, warn}; -use url::Url; use uv_normalize::PackageName; use uv_pep440::VersionSpecifiers; @@ -23,13 +22,13 @@ pub(crate) struct SimpleDetailHTML { impl SimpleDetailHTML { /// Parse the list of [`PypiFile`]s from the simple HTML page returned by the given URL. #[instrument(skip_all, fields(url = % url))] - pub(crate) fn parse(text: &str, url: &Url) -> Result { + pub(crate) fn parse(text: &str, url: &DisplaySafeUrl) -> Result { let dom = tl::parse(text, tl::ParserOptions::default())?; // Parse the first `` tag, if any, to determine the base URL to which all // relative URLs should be resolved. The HTML spec requires that the `` tag // appear before other tags with attribute values of URLs. - let base = BaseUrl::from(DisplaySafeUrl::from( + let base = BaseUrl::from( dom.nodes() .iter() .filter_map(|node| node.as_tag()) @@ -39,7 +38,7 @@ impl SimpleDetailHTML { .transpose()? .flatten() .unwrap_or_else(|| url.clone()), - )); + ); // Parse each `` tag, to extract the filename, hash, and URL. let mut files: Vec = dom @@ -68,12 +67,13 @@ impl SimpleDetailHTML { } /// Parse the `href` from a `` tag. - fn parse_base(base: &HTMLTag) -> Result, Error> { + fn parse_base(base: &HTMLTag) -> Result, Error> { let Some(Some(href)) = base.attributes().get("href") else { return Ok(None); }; let href = std::str::from_utf8(href.as_bytes())?; - let url = Url::parse(href).map_err(|err| Error::UrlParse(href.to_string(), err))?; + let url = + DisplaySafeUrl::parse(href).map_err(|err| Error::UrlParse(href.to_string(), err))?; Ok(Some(url)) } @@ -325,7 +325,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -382,7 +382,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -442,7 +442,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -499,7 +499,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -556,7 +556,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -613,7 +613,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -668,7 +668,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -723,7 +723,7 @@ mod tests { "; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -761,7 +761,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -799,7 +799,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -854,7 +854,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -909,7 +909,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base); insta::assert_debug_snapshot!(result, @r#" Ok( @@ -966,7 +966,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base); insta::assert_debug_snapshot!(result, @r#" Ok( @@ -1023,7 +1023,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap_err(); insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`"); } @@ -1040,8 +1040,10 @@ mod tests { "#; - let base = Url::parse("https://storage.googleapis.com/jax-releases/jax_cuda_releases.html") - .unwrap(); + let base = DisplaySafeUrl::parse( + "https://storage.googleapis.com/jax-releases/jax_cuda_releases.html", + ) + .unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -1122,7 +1124,7 @@ mod tests { "#; - let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/") + let base = DisplaySafeUrl::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/") .unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" @@ -1226,7 +1228,7 @@ mod tests { "#; - let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); + let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" SimpleDetailHTML { @@ -1298,7 +1300,7 @@ mod tests { "#; - let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/") + let base = DisplaySafeUrl::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/") .unwrap(); let result = SimpleDetailHTML::parse(text, &base).unwrap(); insta::assert_debug_snapshot!(result, @r#" diff --git a/crates/uv-distribution-types/src/installed.rs b/crates/uv-distribution-types/src/installed.rs index 4c291013c..1f6a0f9d0 100644 --- a/crates/uv-distribution-types/src/installed.rs +++ b/crates/uv-distribution-types/src/installed.rs @@ -185,14 +185,14 @@ impl InstalledDist { let build_info = Self::read_build_info(path)?; return if let Some(direct_url) = Self::read_direct_url(path)? { - match Url::try_from(&direct_url) { + match DisplaySafeUrl::try_from(&direct_url) { Ok(url) => Ok(Some(Self::from(InstalledDistKind::Url( InstalledDirectUrlDist { name, version, editable: matches!(&direct_url, DirectUrl::LocalDirectory { dir_info, .. } if dir_info.editable == Some(true)), direct_url: Box::new(direct_url), - url: DisplaySafeUrl::from(url), + url, path: path.to_path_buf().into_boxed_path(), cache_info, build_info, @@ -323,7 +323,7 @@ impl InstalledDist { // Normalisation comes from `pkg_resources.to_filename`. let egg_info = target.join(file_stem.replace('-', "_") + ".egg-info"); - let url = Url::from_file_path(&target) + let url = DisplaySafeUrl::from_file_path(&target) .map_err(|()| InstalledDistError::InvalidEggLinkTarget(path.to_path_buf()))?; // Mildly unfortunate that we must read metadata to get the version. @@ -337,7 +337,7 @@ impl InstalledDist { version: Version::from_str(&egg_metadata.version)?, egg_link: path.to_path_buf().into_boxed_path(), target: target.into_boxed_path(), - target_url: DisplaySafeUrl::from(url), + target_url: url, egg_info: egg_info.into_boxed_path(), }, )))); diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 769919585..dfe37a4e2 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -555,7 +555,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { async fn url_metadata<'data>( &self, source: &BuildableSource<'data>, - url: &'data Url, + url: &'data DisplaySafeUrl, index: Option<&'data IndexUrl>, cache_shard: &CacheShard, subdirectory: Option<&'data Path>, @@ -637,7 +637,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { if let Some(subdirectory) = subdirectory { if !source_dist_entry.path().join(subdirectory).is_dir() { return Err(Error::MissingSubdirectory( - DisplaySafeUrl::from(url.clone()), + url.clone(), subdirectory.to_path_buf(), )); } @@ -738,7 +738,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &self, source: &BuildableSource<'_>, ext: SourceDistExtension, - url: &Url, + url: &DisplaySafeUrl, index: Option<&IndexUrl>, cache_shard: &CacheShard, hashes: HashPolicy<'_>, @@ -786,7 +786,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .boxed_local() .instrument(info_span!("download", source_dist = %source)) }; - let req = Self::request(DisplaySafeUrl::from(url.clone()), client.unmanaged)?; + let req = Self::request(url.clone(), client.unmanaged)?; let revision = client .managed(|client| { client.cached_client().get_serde_with_retry( @@ -811,7 +811,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { client .cached_client() .skip_cache_with_retry( - Self::request(DisplaySafeUrl::from(url.clone()), client)?, + Self::request(url.clone(), client)?, &cache_entry, cache_control, download, @@ -2146,7 +2146,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &self, source: &BuildableSource<'_>, ext: SourceDistExtension, - url: &Url, + url: &DisplaySafeUrl, index: Option<&IndexUrl>, entry: &CacheEntry, revision: Revision, @@ -2208,7 +2208,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { client .cached_client() .skip_cache_with_retry( - Self::request(DisplaySafeUrl::from(url.clone()), client)?, + Self::request(url.clone(), client)?, &cache_entry, cache_control, download, diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index a236a35eb..12ce67105 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -58,11 +58,8 @@ impl VerbatimUrl { /// Parse a URL from a string. pub fn parse_url(given: impl AsRef) -> Result { - let url = Url::parse(given.as_ref())?; - Ok(Self { - url: DisplaySafeUrl::from(url), - given: None, - }) + let url = DisplaySafeUrl::parse(given.as_ref())?; + Ok(Self { url, given: None }) } /// Convert a [`VerbatimUrl`] from a path or a URL. @@ -189,10 +186,8 @@ impl VerbatimUrl { let (path, fragment) = split_fragment(path); // Convert to a URL. - let mut url = DisplaySafeUrl::from( - Url::from_file_path(path.clone()) - .unwrap_or_else(|()| panic!("path is absolute: {}", path.display())), - ); + let mut url = DisplaySafeUrl::from_file_path(path.clone()) + .unwrap_or_else(|()| panic!("path is absolute: {}", path.display())); // Set the fragment, if it exists. if let Some(fragment) = fragment { diff --git a/crates/uv-pypi-types/src/direct_url.rs b/crates/uv-pypi-types/src/direct_url.rs index 4e344d922..dcb7a4f83 100644 --- a/crates/uv-pypi-types/src/direct_url.rs +++ b/crates/uv-pypi-types/src/direct_url.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::path::Path; use serde::{Deserialize, Serialize}; -use url::Url; +use uv_redacted::DisplaySafeUrl; /// Metadata for a distribution that was installed via a direct URL. /// @@ -92,7 +92,7 @@ impl std::fmt::Display for VcsKind { } } -impl TryFrom<&DirectUrl> for Url { +impl TryFrom<&DirectUrl> for DisplaySafeUrl { type Error = url::ParseError; fn try_from(value: &DirectUrl) -> Result { @@ -126,9 +126,11 @@ impl TryFrom<&DirectUrl> for Url { } => { let mut url = Self::parse(&format!("{}+{}", vcs_info.vcs, url))?; if let Some(commit_id) = &vcs_info.commit_id { - url.set_path(&format!("{}@{commit_id}", url.path())); + let path = format!("{}@{commit_id}", url.path()); + url.set_path(&path); } else if let Some(requested_revision) = &vcs_info.requested_revision { - url.set_path(&format!("{}@{requested_revision}", url.path())); + let path = format!("{}@{requested_revision}", url.path()); + url.set_path(&path); } if let Some(subdirectory) = subdirectory { url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display()))); diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index d7293d0bd..418e34147 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -77,7 +77,7 @@ pub enum Error { #[error("Invalid download URL")] InvalidUrl(#[from] url::ParseError), #[error("Invalid download URL: {0}")] - InvalidUrlFormat(Url), + InvalidUrlFormat(DisplaySafeUrl), #[error("Invalid path in file URL: `{0}`")] InvalidFileUrl(String), #[error("Failed to create download directory")] @@ -109,7 +109,7 @@ pub enum Error { #[error("An offline Python installation was requested, but {file} (from {url}) is missing in {}", python_builds_dir.user_display())] OfflinePythonMissing { file: Box, - url: Box, + url: Box, python_builds_dir: PathBuf, }, #[error(transparent)] @@ -1198,7 +1198,7 @@ impl ManagedPythonDownload { /// Download the managed Python archive into the cache directory. async fn download_archive( &self, - url: &Url, + url: &DisplaySafeUrl, client: &BaseClient, reporter: Option<&dyn Reporter>, python_builds_dir: &Path, @@ -1300,7 +1300,7 @@ impl ManagedPythonDownload { &self, python_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>, - ) -> Result { + ) -> Result { match self.key.implementation { LenientImplementationName::Known(ImplementationName::CPython) => { if let Some(mirror) = python_install_mirror { @@ -1312,7 +1312,7 @@ impl ManagedPythonDownload { self.url.to_string(), )); }; - return Ok(Url::parse( + return Ok(DisplaySafeUrl::parse( format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(), )?); } @@ -1327,7 +1327,7 @@ impl ManagedPythonDownload { self.url.to_string(), )); }; - return Ok(Url::parse( + return Ok(DisplaySafeUrl::parse( format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(), )?); } @@ -1336,7 +1336,7 @@ impl ManagedPythonDownload { _ => {} } - Ok(Url::parse(&self.url)?) + Ok(DisplaySafeUrl::parse(&self.url)?) } } @@ -1558,10 +1558,9 @@ where /// Convert a [`Url`] into an [`AsyncRead`] stream. async fn read_url( - url: &Url, + url: &DisplaySafeUrl, client: &BaseClient, ) -> Result<(impl AsyncRead + Unpin, Option), Error> { - let url = DisplaySafeUrl::from(url.clone()); if url.scheme() == "file" { // Loads downloaded distribution from the given `file://` URL. let path = url @@ -1574,7 +1573,7 @@ async fn read_url( Ok((Either::Left(reader), Some(size))) } else { let response = client - .for_host(&url) + .for_host(url) .get(Url::from(url.clone())) .send() .await @@ -1588,7 +1587,7 @@ async fn read_url( // Check the status code. let response = response .error_for_status() - .map_err(|err| Error::from_reqwest(url, err, retry_count))?; + .map_err(|err| Error::from_reqwest(url.clone(), err, retry_count))?; let size = response.content_length(); let stream = response diff --git a/crates/uv-redacted/src/lib.rs b/crates/uv-redacted/src/lib.rs index 5441eec35..dac647ac1 100644 --- a/crates/uv-redacted/src/lib.rs +++ b/crates/uv-redacted/src/lib.rs @@ -60,7 +60,7 @@ impl DisplaySafeUrl { /// Parse a string as an URL, with this URL as the base URL. #[inline] pub fn join(&self, input: &str) -> Result { - self.0.join(input).map(Self::from) + self.0.join(input).map(Self) } /// Serialize with Serde using the internal representation of the `Url` struct. @@ -78,7 +78,7 @@ impl DisplaySafeUrl { where D: serde::Deserializer<'de>, { - Url::deserialize_internal(deserializer).map(Self::from) + Url::deserialize_internal(deserializer).map(Self) } #[allow(clippy::result_unit_err)] @@ -250,8 +250,8 @@ mod tests { #[test] fn from_url_no_credentials() { let url_str = "https://pypi-proxy.fly.dev/basic-auth/simple"; - let url = Url::parse(url_str).unwrap(); - let log_safe_url = DisplaySafeUrl::from(url); + let url = DisplaySafeUrl::parse(url_str).unwrap(); + let log_safe_url = url; assert_eq!(log_safe_url.username(), ""); assert!(log_safe_url.password().is_none()); assert_eq!(log_safe_url.to_string(), url_str); @@ -260,8 +260,8 @@ mod tests { #[test] fn from_url_username_and_password() { let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple"; - let url = Url::parse(url_str).unwrap(); - let log_safe_url = DisplaySafeUrl::from(url); + let url = DisplaySafeUrl::parse(url_str).unwrap(); + let log_safe_url = url; assert_eq!(log_safe_url.username(), "user"); assert!(log_safe_url.password().is_some_and(|p| p == "pass")); assert_eq!( @@ -273,8 +273,8 @@ mod tests { #[test] fn from_url_just_password() { let url_str = "https://:pass@pypi-proxy.fly.dev/basic-auth/simple"; - let url = Url::parse(url_str).unwrap(); - let log_safe_url = DisplaySafeUrl::from(url); + let url = DisplaySafeUrl::parse(url_str).unwrap(); + let log_safe_url = url; assert_eq!(log_safe_url.username(), ""); assert!(log_safe_url.password().is_some_and(|p| p == "pass")); assert_eq!( @@ -286,8 +286,8 @@ mod tests { #[test] fn from_url_just_username() { let url_str = "https://user@pypi-proxy.fly.dev/basic-auth/simple"; - let url = Url::parse(url_str).unwrap(); - let log_safe_url = DisplaySafeUrl::from(url); + let url = DisplaySafeUrl::parse(url_str).unwrap(); + let log_safe_url = url; assert_eq!(log_safe_url.username(), "user"); assert!(log_safe_url.password().is_none()); assert_eq!( @@ -374,7 +374,7 @@ mod tests { #[test] fn log_safe_url_ref() { let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple"; - let url = Url::parse(url_str).unwrap(); + let url = DisplaySafeUrl::parse(url_str).unwrap(); let log_safe_url = DisplaySafeUrl::ref_cast(&url); assert_eq!(log_safe_url.username(), "user"); assert!(log_safe_url.password().is_some_and(|p| p == "pass")); diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 024281f85..0476bda7f 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -11,7 +11,6 @@ use itertools::Itertools; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashMap}; use tracing::{debug, warn}; -use url::Url; use uv_cache::Cache; use uv_cache_key::RepositoryUrl; @@ -1104,8 +1103,7 @@ async fn lock_and_sync( // Invalidate the project metadata. if let AddTarget::Project(VirtualProject::Project(ref project), _) = target { - let url = Url::from_file_path(project.project_root()) - .map(DisplaySafeUrl::from) + let url = DisplaySafeUrl::from_file_path(project.project_root()) .expect("project root is a valid URL"); let version_id = VersionId::from_url(&url); let existing = lock_state.index().distributions().remove(&version_id);