diff --git a/crates/uv-configuration/src/extras.rs b/crates/uv-configuration/src/extras.rs index 79cdc2714..dfca8f41e 100644 --- a/crates/uv-configuration/src/extras.rs +++ b/crates/uv-configuration/src/extras.rs @@ -95,7 +95,7 @@ mod tests { Vec::new() ); ($($x:expr),+ $(,)?) => ( - vec![$(ExtraName::new($x.into()).unwrap()),+] + vec![$(ExtraName::from_owned($x.into()).unwrap()),+] ) } diff --git a/crates/uv-normalize/src/extra_name.rs b/crates/uv-normalize/src/extra_name.rs index 11a6707ef..774c2a175 100644 --- a/crates/uv-normalize/src/extra_name.rs +++ b/crates/uv-normalize/src/extra_name.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use uv_small_str::SmallString; -use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError}; +use crate::{validate_and_normalize_ref, InvalidNameError}; /// The normalized name of an extra dependency. /// @@ -22,8 +22,11 @@ pub struct ExtraName(SmallString); impl ExtraName { /// Create a validated, normalized extra name. - pub fn new(name: String) -> Result { - validate_and_normalize_owned(name).map(Self) + /// + /// At present, this is no more efficient than calling [`ExtraName::from_str`]. + #[allow(clippy::needless_pass_by_value)] + pub fn from_owned(name: String) -> Result { + validate_and_normalize_ref(&name).map(Self) } /// Return the underlying extra name as a string. diff --git a/crates/uv-normalize/src/group_name.rs b/crates/uv-normalize/src/group_name.rs index a11ed01bb..d22ba8141 100644 --- a/crates/uv-normalize/src/group_name.rs +++ b/crates/uv-normalize/src/group_name.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use uv_small_str::SmallString; -use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError}; +use crate::{validate_and_normalize_ref, InvalidNameError}; /// The normalized name of a dependency group. /// @@ -20,8 +20,11 @@ pub struct GroupName(SmallString); impl GroupName { /// Create a validated, normalized group name. - pub fn new(name: String) -> Result { - validate_and_normalize_owned(name).map(Self) + /// + /// At present, this is no more efficient than calling [`GroupName::from_str`]. + #[allow(clippy::needless_pass_by_value)] + pub fn from_owned(name: String) -> Result { + validate_and_normalize_ref(&name).map(Self) } } @@ -69,4 +72,4 @@ impl AsRef for GroupName { /// Internally, we model dependency groups as a generic concept; but externally, we only expose the /// `dev-dependencies` group. pub static DEV_DEPENDENCIES: LazyLock = - LazyLock::new(|| GroupName::new("dev".to_string()).unwrap()); + LazyLock::new(|| GroupName::from_str("dev").unwrap()); diff --git a/crates/uv-normalize/src/lib.rs b/crates/uv-normalize/src/lib.rs index 06234db96..06fd7a57f 100644 --- a/crates/uv-normalize/src/lib.rs +++ b/crates/uv-normalize/src/lib.rs @@ -13,15 +13,6 @@ mod extra_name; mod group_name; mod package_name; -/// Validate and normalize an owned package or extra name. -pub(crate) fn validate_and_normalize_owned(name: String) -> Result { - if is_normalized(&name)? { - Ok(SmallString::from(name)) - } else { - Ok(SmallString::from(normalize(&name)?)) - } -} - /// Validate and normalize an unowned package or extra name. pub(crate) fn validate_and_normalize_ref( name: impl AsRef, @@ -151,12 +142,6 @@ mod tests { validate_and_normalize_ref(input).unwrap().as_ref(), "friendly-bard" ); - assert_eq!( - validate_and_normalize_owned(input.to_string()) - .unwrap() - .as_ref(), - "friendly-bard" - ); } } @@ -186,12 +171,6 @@ mod tests { let unchanged = ["friendly-bard", "1okay", "okay2"]; for input in unchanged { assert_eq!(validate_and_normalize_ref(input).unwrap().as_ref(), input); - assert_eq!( - validate_and_normalize_owned(input.to_string()) - .unwrap() - .as_ref(), - input - ); assert!(is_normalized(input).unwrap()); } } @@ -209,7 +188,6 @@ mod tests { ]; for input in failures { assert!(validate_and_normalize_ref(input).is_err()); - assert!(validate_and_normalize_owned(input.to_string()).is_err()); assert!(is_normalized(input).is_err()); } } diff --git a/crates/uv-normalize/src/package_name.rs b/crates/uv-normalize/src/package_name.rs index 59d7ea912..41532ef70 100644 --- a/crates/uv-normalize/src/package_name.rs +++ b/crates/uv-normalize/src/package_name.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use uv_small_str::SmallString; -use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError}; +use crate::{validate_and_normalize_ref, InvalidNameError}; /// The normalized name of a package. /// @@ -33,8 +33,11 @@ pub struct PackageName(SmallString); impl PackageName { /// Create a validated, normalized package name. - pub fn new(name: String) -> Result { - validate_and_normalize_owned(name).map(Self) + /// + /// At present, this is no more efficient than calling [`PackageName::from_str`]. + #[allow(clippy::needless_pass_by_value)] + pub fn from_owned(name: String) -> Result { + validate_and_normalize_ref(&name).map(Self) } /// Escape this name with underscores (`_`) instead of dashes (`-`) diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index b30bbc670..c950482d6 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -625,7 +625,8 @@ fn parse_extras_cursor( // Add the parsed extra extras.push( - ExtraName::new(buffer).expect("`ExtraName` validation should match PEP 508 parsing"), + ExtraName::from_str(&buffer) + .expect("`ExtraName` validation should match PEP 508 parsing"), ); is_first_iteration = false; } diff --git a/crates/uv-pypi-types/src/metadata/metadata10.rs b/crates/uv-pypi-types/src/metadata/metadata10.rs index 98efc300f..57113f1f0 100644 --- a/crates/uv-pypi-types/src/metadata/metadata10.rs +++ b/crates/uv-pypi-types/src/metadata/metadata10.rs @@ -20,7 +20,7 @@ impl Metadata10 { /// Parse the [`Metadata10`] from a `PKG-INFO` file, as included in a source distribution. pub fn parse_pkg_info(content: &[u8]) -> Result { let headers = Headers::parse(content)?; - let name = PackageName::new( + let name = PackageName::from_owned( headers .get_first_value("Name") .ok_or(MetadataError::FieldNotFound("Name"))?, diff --git a/crates/uv-pypi-types/src/metadata/metadata_resolver.rs b/crates/uv-pypi-types/src/metadata/metadata_resolver.rs index c4dcebb41..8d232b920 100644 --- a/crates/uv-pypi-types/src/metadata/metadata_resolver.rs +++ b/crates/uv-pypi-types/src/metadata/metadata_resolver.rs @@ -40,7 +40,7 @@ impl ResolutionMetadata { pub fn parse_metadata(content: &[u8]) -> Result { let headers = Headers::parse(content)?; - let name = PackageName::new( + let name = PackageName::from_owned( headers .get_first_value("Name") .ok_or(MetadataError::FieldNotFound("Name"))?, @@ -63,13 +63,15 @@ impl ResolutionMetadata { .map(VersionSpecifiers::from); let provides_extras = headers .get_all_values("Provides-Extra") - .filter_map(|provides_extra| match ExtraName::new(provides_extra) { - Ok(extra_name) => Some(extra_name), - Err(err) => { - warn!("Ignoring invalid extra: {err}"); - None - } - }) + .filter_map( + |provides_extra| match ExtraName::from_owned(provides_extra) { + Ok(extra_name) => Some(extra_name), + Err(err) => { + warn!("Ignoring invalid extra: {err}"); + None + } + }, + ) .collect::>(); let dynamic = headers .get_all_values("Dynamic") @@ -116,7 +118,7 @@ impl ResolutionMetadata { } // The `Name` and `Version` fields are required, and can't be dynamic. - let name = PackageName::new( + let name = PackageName::from_owned( headers .get_first_value("Name") .ok_or(MetadataError::FieldNotFound("Name"))?, @@ -141,13 +143,15 @@ impl ResolutionMetadata { .map(VersionSpecifiers::from); let provides_extras = headers .get_all_values("Provides-Extra") - .filter_map(|provides_extra| match ExtraName::new(provides_extra) { - Ok(extra_name) => Some(extra_name), - Err(err) => { - warn!("Ignoring invalid extra: {err}"); - None - } - }) + .filter_map( + |provides_extra| match ExtraName::from_owned(provides_extra) { + Ok(extra_name) => Some(extra_name), + Err(err) => { + warn!("Ignoring invalid extra: {err}"); + None + } + }, + ) .collect::>(); Ok(Self { diff --git a/crates/uv-resolver/src/universal_marker.rs b/crates/uv-resolver/src/universal_marker.rs index 67b8be623..62bbb6de4 100644 --- a/crates/uv-resolver/src/universal_marker.rs +++ b/crates/uv-resolver/src/universal_marker.rs @@ -1,7 +1,9 @@ use std::borrow::Borrow; +use std::str::FromStr; use itertools::Itertools; use rustc_hash::FxHashMap; + use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep508::{ ExtraOperator, MarkerEnvironment, MarkerEnvironmentBuilder, MarkerExpression, MarkerOperator, @@ -506,7 +508,7 @@ fn encode_package_extra(package: &PackageName, extra: &ExtraName) -> ExtraName { // character in `package` or `extra` values. But if we know the length of // the package name, we can always parse each field unambiguously. let package_len = package.as_str().len(); - ExtraName::new(format!("extra-{package_len}-{package}-{extra}")).unwrap() + ExtraName::from_owned(format!("extra-{package_len}-{package}-{extra}")).unwrap() } /// Encodes the given package name and its corresponding group into a valid @@ -514,7 +516,7 @@ fn encode_package_extra(package: &PackageName, extra: &ExtraName) -> ExtraName { fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName { // See `encode_package_extra`, the same considerations apply here. let package_len = package.as_str().len(); - ExtraName::new(format!("group-{package_len}-{package}-{group}")).unwrap() + ExtraName::from_owned(format!("group-{package_len}-{package}-{group}")).unwrap() } #[derive(Debug)] @@ -583,7 +585,7 @@ impl<'a> ParsedRawExtra<'a> { } fn to_conflict_item(&self) -> Result { - let package = PackageName::new(self.package().to_string()).map_err(|name_error| { + let package = PackageName::from_str(self.package()).map_err(|name_error| { ResolveError::InvalidValueInConflictMarker { kind: "package", name_error, @@ -591,7 +593,7 @@ impl<'a> ParsedRawExtra<'a> { })?; match *self { ParsedRawExtra::Extra { extra, .. } => { - let extra = ExtraName::new(extra.to_string()).map_err(|name_error| { + let extra = ExtraName::from_str(extra).map_err(|name_error| { ResolveError::InvalidValueInConflictMarker { kind: "extra", name_error, @@ -600,7 +602,7 @@ impl<'a> ParsedRawExtra<'a> { Ok(ConflictItem::from((package, extra))) } ParsedRawExtra::Group { group, .. } => { - let group = GroupName::new(group.to_string()).map_err(|name_error| { + let group = GroupName::from_str(group).map_err(|name_error| { ResolveError::InvalidValueInConflictMarker { kind: "group", name_error, @@ -760,7 +762,7 @@ mod tests { /// Shortcut for creating an extra name. fn create_extra(name: &str) -> ExtraName { - ExtraName::new(name.to_string()).unwrap() + ExtraName::from_str(name).unwrap() } /// Shortcut for creating a conflict marker from an extra name. diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index d89553728..5790b2e04 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -861,7 +861,7 @@ impl PyProjectTomlMut { let Some(dependencies) = dependencies.as_array() else { continue; }; - let Ok(extra) = ExtraName::new(extra.to_string()) else { + let Ok(extra) = ExtraName::from_str(extra) else { continue; }; @@ -878,7 +878,7 @@ impl PyProjectTomlMut { let Some(dependencies) = dependencies.as_array() else { continue; }; - let Ok(group) = GroupName::new(group.to_string()) else { + let Ok(group) = GroupName::from_str(group) else { continue; }; diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 8bdc08330..5f41d8a8b 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -126,7 +126,7 @@ pub(crate) async fn init( // Pre-normalize the package name by removing any leading or trailing // whitespace, and replacing any internal whitespace with hyphens. let name = name.trim().replace(' ', "-"); - PackageName::new(name)? + PackageName::from_owned(name)? } };