diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index f7f740f89..46ad20de6 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -990,7 +990,7 @@ fn parse_find_links(input: &str) -> Result, String> { if input.is_empty() { Ok(Maybe::None) } else { - IndexUrl::parse_preserving_trailing_slash(input) + IndexUrl::parse_find_links(input) .map(Index::from_find_links) .map(|index| Index { origin: Some(Origin::Cli), diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index 8ac7c3cd4..22158191c 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -222,7 +222,7 @@ impl Index { pub fn relative_to(mut self, root_dir: &Path) -> Result { if let IndexUrl::Path(ref url) = self.url { if let Some(given) = url.given() { - self.url = IndexUrl::parse(given, Some(root_dir))?; + self.url = IndexUrl::parse_simple_api(given, Some(root_dir))?; } } Ok(self) diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index 25d2d53d9..fb1a7e281 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -34,24 +34,24 @@ pub enum IndexUrl { } impl IndexUrl { - /// Parse an [`IndexUrl`] from a string, relative to an optional root directory. + /// Parse a Simple API [`IndexUrl`] from a string, relative to an optional root directory. /// /// If no root directory is provided, relative paths are resolved against the current working /// directory. /// /// Normalizes non-file URLs by removing trailing slashes for consistency. - pub fn parse(path: &str, root_dir: Option<&Path>) -> Result { - Self::parse_with_trailing_slash_policy(path, root_dir, TrailingSlashPolicy::Remove) + pub fn parse_simple_api(path: &str, root_dir: Option<&Path>) -> Result { + Self::parse(path, root_dir, TrailingSlashPolicy::Remove) } - /// Parse an [`IndexUrl`] from a string, relative to an optional root directory. + /// Parse a find-links [`IndexUrl`] from a string, relative to an optional root directory. /// /// If no root directory is provided, relative paths are resolved against the current working /// directory. /// /// Preserves trailing slash if present in `path`. - pub fn parse_preserving_trailing_slash(path: &str) -> Result { - Self::parse_with_trailing_slash_policy(path, None, TrailingSlashPolicy::Preserve) + pub fn parse_find_links(path: &str) -> Result { + Self::parse(path, None, TrailingSlashPolicy::Preserve) } /// Parse an [`IndexUrl`] from a string, relative to an optional root directory. @@ -60,7 +60,7 @@ impl IndexUrl { /// directory. /// /// Applies trailing slash policy to non-file URLs. - fn parse_with_trailing_slash_policy( + fn parse( path: &str, root_dir: Option<&Path>, slash_policy: TrailingSlashPolicy, @@ -91,10 +91,7 @@ impl IndexUrl { } } }; - Ok(Self::from_verbatim_url_with_trailing_slash_policy( - url.with_given(path), - slash_policy, - )) + Ok(Self::from_verbatim_url(url.with_given(path), slash_policy)) } /// Return the root [`Url`] of the index, if applicable. @@ -119,18 +116,21 @@ impl IndexUrl { Some(url) } - /// Construct an [`IndexUrl`] from a [`VerbatimUrl`], preserving a trailing + /// Construct a Simple API [`IndexUrl`] from a [`VerbatimUrl`], removing a trailing /// slash if present. - pub fn from_verbatim_url_preserving_trailing_slash(url: VerbatimUrl) -> Self { - Self::from_verbatim_url_with_trailing_slash_policy(url, TrailingSlashPolicy::Preserve) + pub fn from_simple_api_url(url: VerbatimUrl) -> Self { + Self::from_verbatim_url(url, TrailingSlashPolicy::Remove) + } + + /// Construct a find-links [`IndexUrl`] from a [`VerbatimUrl`], preserving a trailing + /// slash if present. + pub fn from_find_links_url(url: VerbatimUrl) -> Self { + Self::from_verbatim_url(url, TrailingSlashPolicy::Preserve) } /// Construct an [`IndexUrl`] from a [`VerbatimUrl`], applying a [`TrailingSlashPolicy`] /// to non-file URLs. - fn from_verbatim_url_with_trailing_slash_policy( - mut url: VerbatimUrl, - slash_policy: TrailingSlashPolicy, - ) -> Self { + fn from_verbatim_url(mut url: VerbatimUrl, slash_policy: TrailingSlashPolicy) -> Self { if url.scheme() == "file" { return Self::Path(Arc::new(url)); } @@ -286,7 +286,7 @@ impl FromStr for IndexUrl { type Err = IndexUrlError; fn from_str(s: &str) -> Result { - Self::parse(s, None) + Self::parse_simple_api(s, None) } } @@ -322,14 +322,6 @@ impl<'de> serde::de::Deserialize<'de> for IndexUrl { } } -impl From for IndexUrl { - fn from(url: VerbatimUrl) -> Self { - // Remove trailing slashes for consistency. They'll be re-added if necessary when - // querying the Simple API. - Self::from_verbatim_url_with_trailing_slash_policy(url, TrailingSlashPolicy::Remove) - } -} - impl From for DisplaySafeUrl { fn from(index: IndexUrl) -> Self { match index { @@ -790,31 +782,25 @@ mod tests { let verbatim_url_without_trailing_slash = VerbatimUrl::from_url(url_without_trailing_slash.clone()); - // Test `From` implementation. + // Test `from_verbatim_url_without_trailing_slash`. // Trailing slash should be removed if present. assert_eq!( - IndexUrl::from(verbatim_url_with_trailing_slash.clone()).url(), + IndexUrl::from_simple_api_url(verbatim_url_with_trailing_slash.clone()).url(), &url_without_trailing_slash ); assert_eq!( - IndexUrl::from(verbatim_url_without_trailing_slash.clone()).url(), + IndexUrl::from_simple_api_url(verbatim_url_without_trailing_slash.clone()).url(), &url_without_trailing_slash ); // Test `from_verbatim_url_preserving_trailing_slash`. // Trailing slash should be preserved if present. assert_eq!( - IndexUrl::from_verbatim_url_preserving_trailing_slash( - verbatim_url_with_trailing_slash.clone() - ) - .url(), + IndexUrl::from_find_links_url(verbatim_url_with_trailing_slash.clone()).url(), &url_with_trailing_slash ); assert_eq!( - IndexUrl::from_verbatim_url_preserving_trailing_slash( - verbatim_url_without_trailing_slash.clone() - ) - .url(), + IndexUrl::from_find_links_url(verbatim_url_without_trailing_slash.clone()).url(), &url_without_trailing_slash ); } diff --git a/crates/uv-distribution-types/src/requirement.rs b/crates/uv-distribution-types/src/requirement.rs index 432cc4e12..bfeca5af1 100644 --- a/crates/uv-distribution-types/src/requirement.rs +++ b/crates/uv-distribution-types/src/requirement.rs @@ -821,8 +821,9 @@ impl TryFrom for RequirementSource { conflict, } => Ok(Self::Registry { specifier, - index: index - .map(|index| IndexMetadata::from(IndexUrl::from(VerbatimUrl::from_url(index)))), + index: index.map(|index| { + IndexMetadata::from(IndexUrl::from_simple_api_url(VerbatimUrl::from_url(index))) + }), conflict, }), RequirementSourceWire::Git { git } => { diff --git a/crates/uv-distribution-types/src/status_code_strategy.rs b/crates/uv-distribution-types/src/status_code_strategy.rs index b019d0329..ee9290764 100644 --- a/crates/uv-distribution-types/src/status_code_strategy.rs +++ b/crates/uv-distribution-types/src/status_code_strategy.rs @@ -196,7 +196,8 @@ mod tests { fn test_decision_default_400() { let strategy = IndexStatusCodeStrategy::Default; let status_code = StatusCode::BAD_REQUEST; - let index_url = IndexUrl::parse("https://internal-registry.com/simple", None).unwrap(); + let index_url = + IndexUrl::parse_simple_api("https://internal-registry.com/simple", None).unwrap(); let capabilities = IndexCapabilities::default(); let decision = strategy.handle_status_code(status_code, &index_url, &capabilities); assert_eq!( @@ -209,7 +210,8 @@ mod tests { fn test_decision_default_401() { let strategy = IndexStatusCodeStrategy::Default; let status_code = StatusCode::UNAUTHORIZED; - let index_url = IndexUrl::parse("https://internal-registry.com/simple", None).unwrap(); + let index_url = + IndexUrl::parse_simple_api("https://internal-registry.com/simple", None).unwrap(); let capabilities = IndexCapabilities::default(); let decision = strategy.handle_status_code(status_code, &index_url, &capabilities); assert_eq!( @@ -224,7 +226,8 @@ mod tests { fn test_decision_default_403() { let strategy = IndexStatusCodeStrategy::Default; let status_code = StatusCode::FORBIDDEN; - let index_url = IndexUrl::parse("https://internal-registry.com/simple", None).unwrap(); + let index_url = + IndexUrl::parse_simple_api("https://internal-registry.com/simple", None).unwrap(); let capabilities = IndexCapabilities::default(); let decision = strategy.handle_status_code(status_code, &index_url, &capabilities); assert_eq!( @@ -239,7 +242,8 @@ mod tests { fn test_decision_default_404() { let strategy = IndexStatusCodeStrategy::Default; let status_code = StatusCode::NOT_FOUND; - let index_url = IndexUrl::parse("https://internal-registry.com/simple", None).unwrap(); + let index_url = + IndexUrl::parse_simple_api("https://internal-registry.com/simple", None).unwrap(); let capabilities = IndexCapabilities::default(); let decision = strategy.handle_status_code(status_code, &index_url, &capabilities); assert_eq!(decision, IndexStatusCodeDecision::Ignore); @@ -249,7 +253,8 @@ mod tests { #[test] fn test_decision_pytorch() { - let index_url = IndexUrl::parse("https://download.pytorch.org/whl/cu118", None).unwrap(); + let index_url = + IndexUrl::parse_simple_api("https://download.pytorch.org/whl/cu118", None).unwrap(); let strategy = IndexStatusCodeStrategy::from_index_url(&index_url); let capabilities = IndexCapabilities::default(); // Test we continue on 403 for PyTorch registry. @@ -275,7 +280,8 @@ mod tests { let strategy = IndexStatusCodeStrategy::IgnoreErrorCodes { status_codes: status_codes.iter().copied().collect::>(), }; - let index_url = IndexUrl::parse("https://internal-registry.com/simple", None).unwrap(); + let index_url = + IndexUrl::parse_simple_api("https://internal-registry.com/simple", None).unwrap(); let capabilities = IndexCapabilities::default(); // Test each ignored status code for status_code in status_codes { diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index e637c6012..a285cc359 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -141,17 +141,19 @@ impl RequirementsSpecification { .map(Requirement::from) .map(NameRequirementSpecification::from) .collect(), - index_url: requirements_txt.index_url.map(IndexUrl::from), + index_url: requirements_txt + .index_url + .map(IndexUrl::from_simple_api_url), extra_index_urls: requirements_txt .extra_index_urls .into_iter() - .map(IndexUrl::from) + .map(IndexUrl::from_simple_api_url) .collect(), no_index: requirements_txt.no_index, find_links: requirements_txt .find_links .into_iter() - .map(IndexUrl::from_verbatim_url_preserving_trailing_slash) + .map(IndexUrl::from_find_links_url) .collect(), no_binary: requirements_txt.no_binary, no_build: requirements_txt.only_binary, diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index d2c2383a5..be55a2199 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -1346,7 +1346,7 @@ impl PylockTomlWheel { }; let index = if let Some(index) = index { - IndexUrl::from(VerbatimUrl::from_url(index.clone())) + IndexUrl::from_simple_api_url(VerbatimUrl::from_url(index.clone())) } else { // Including the index is only a SHOULD in PEP 751. If it's omitted, we treat the // URL (less the filename) as the index. This isn't correct, but it's the best we can @@ -1354,7 +1354,7 @@ impl PylockTomlWheel { // of this URL (since we cache under the hash of the index). let mut index = file_url.to_url().map_err(PylockTomlErrorKind::ToUrl)?; index.path_segments_mut().unwrap().pop(); - IndexUrl::from(VerbatimUrl::from_url(index)) + IndexUrl::from_simple_api_url(VerbatimUrl::from_url(index)) }; let file = Box::new(uv_distribution_types::File { @@ -1502,7 +1502,7 @@ impl PylockTomlSdist { }; let index = if let Some(index) = index { - IndexUrl::from(VerbatimUrl::from_url(index.clone())) + IndexUrl::from_simple_api_url(VerbatimUrl::from_url(index.clone())) } else { // Including the index is only a SHOULD in PEP 751. If it's omitted, we treat the // URL (less the filename) as the index. This isn't correct, but it's the best we can @@ -1510,7 +1510,7 @@ impl PylockTomlSdist { // of this URL (since we cache under the hash of the index). let mut index = file_url.to_url().map_err(PylockTomlErrorKind::ToUrl)?; index.path_segments_mut().unwrap().pop(); - IndexUrl::from(VerbatimUrl::from_url(index)) + IndexUrl::from_simple_api_url(VerbatimUrl::from_url(index)) }; let file = Box::new(uv_distribution_types::File { diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index beeadc912..14e2ed258 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -2521,7 +2521,7 @@ impl Package { yanked: None, }); - let index = IndexUrl::from(VerbatimUrl::from_url( + let index = IndexUrl::from_simple_api_url(VerbatimUrl::from_url( url.to_url().map_err(LockErrorKind::InvalidUrl)?, )); @@ -2595,7 +2595,7 @@ impl Package { yanked: None, }); - let index = IndexUrl::from( + let index = IndexUrl::from_simple_api_url( VerbatimUrl::from_absolute_path(workspace_root.join(path)) .map_err(LockErrorKind::RegistryVerbatimUrl)?, ); @@ -2808,13 +2808,13 @@ impl Package { pub fn index(&self, root: &Path) -> Result, LockError> { match &self.id.source { Source::Registry(RegistrySource::Url(url)) => { - let index = IndexUrl::from(VerbatimUrl::from_url( + let index = IndexUrl::from_simple_api_url(VerbatimUrl::from_url( url.to_url().map_err(LockErrorKind::InvalidUrl)?, )); Ok(Some(index)) } Source::Registry(RegistrySource::Path(path)) => { - let index = IndexUrl::from( + let index = IndexUrl::from_simple_api_url( VerbatimUrl::from_absolute_path(root.join(path)) .map_err(LockErrorKind::RegistryVerbatimUrl)?, ); @@ -4283,7 +4283,7 @@ impl Wheel { url: file_location, yanked: None, }); - let index = IndexUrl::from(VerbatimUrl::from_url( + let index = IndexUrl::from_simple_api_url(VerbatimUrl::from_url( url.to_url().map_err(LockErrorKind::InvalidUrl)?, )); Ok(RegistryBuiltWheel { @@ -4325,7 +4325,7 @@ impl Wheel { url: file_location, yanked: None, }); - let index = IndexUrl::from( + let index = IndexUrl::from_simple_api_url( VerbatimUrl::from_absolute_path(root.join(index_path)) .map_err(LockErrorKind::RegistryVerbatimUrl)?, ); @@ -4825,7 +4825,9 @@ fn normalize_requirement( index.remove_credentials(); index }) - .map(|index| IndexMetadata::from(IndexUrl::from(VerbatimUrl::from_url(index)))); + .map(|index| { + IndexMetadata::from(IndexUrl::from_simple_api_url(VerbatimUrl::from_url(index))) + }); Ok(Requirement { name: requirement.name, extras: requirement.extras, diff --git a/crates/uv-resolver/src/preferences.rs b/crates/uv-resolver/src/preferences.rs index 51e325d68..2c78cdb33 100644 --- a/crates/uv-resolver/src/preferences.rs +++ b/crates/uv-resolver/src/preferences.rs @@ -111,7 +111,7 @@ impl Preference { package .index .as_ref() - .map(|index| IndexUrl::from(VerbatimUrl::from(index.clone()))), + .map(|index| IndexUrl::from_simple_api_url(VerbatimUrl::from(index.clone()))), ), // `pylock.toml` doesn't have fork annotations. fork_markers: vec![],