Improve interface

This commit is contained in:
John Mumm 2025-07-07 16:26:11 +02:00
parent f7a6359663
commit c5cd30d2bb
No known key found for this signature in database
GPG Key ID: 73D2271AFDC26EA8
9 changed files with 60 additions and 63 deletions

View File

@ -990,7 +990,7 @@ fn parse_find_links(input: &str) -> Result<Maybe<PipFindLinks>, 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),

View File

@ -222,7 +222,7 @@ impl Index {
pub fn relative_to(mut self, root_dir: &Path) -> Result<Self, IndexUrlError> {
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)

View File

@ -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, IndexUrlError> {
Self::parse_with_trailing_slash_policy(path, root_dir, TrailingSlashPolicy::Remove)
pub fn parse_simple_api(path: &str, root_dir: Option<&Path>) -> Result<Self, IndexUrlError> {
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, IndexUrlError> {
Self::parse_with_trailing_slash_policy(path, None, TrailingSlashPolicy::Preserve)
pub fn parse_find_links(path: &str) -> Result<Self, IndexUrlError> {
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, Self::Err> {
Self::parse(s, None)
Self::parse_simple_api(s, None)
}
}
@ -322,14 +322,6 @@ impl<'de> serde::de::Deserialize<'de> for IndexUrl {
}
}
impl From<VerbatimUrl> 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<IndexUrl> 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<VerbatimUrl>` 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
);
}

View File

@ -821,8 +821,9 @@ impl TryFrom<RequirementSourceWire> 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 } => {

View File

@ -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::<FxHashSet<_>>(),
};
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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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<Option<IndexUrl>, 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,

View File

@ -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![],