Files
uv/crates/uv-normalize/src/package_name.rs
T
Andrew Gallant 1089abda3f require serde and rkyv everywhere; remove optional serde and rkyv features (#3345)
In *some* places in our crates, `serde` (and `rkyv`) are optional
dependencies. I believe this was done out of reasons of "good sense,"
that is, it follows a Rust ecosystem pattern where serde integration
tends to be an opt-in crate feature. (And similarly for `rkyv`.)

However, ultimately, `uv` itself requires `serde` and `rkyv` to
function. Since our crates are strictly internal, there are limited
consumers for our crates without `serde` (and `rkyv`) enabled. I think
one possibility is that optional `serde` (and `rkyv`) integration means
that someone can do this:

    cargo test -p pep440_rs

And this will run tests _without_ `serde` or `rkyv` enabled. That in
turn could lead to faster iteration time by reducing compile times. But,
I'm not sure this is worth supporting. The iterative compilation times
of
individual crates are probably fast enough in debug mode, even with
`serde` and `rkyv` enabled. Namely, `serde` and `rkyv` themselves
shouldn't need to be re-compiled in most cases. On `main`:

```
from-scratch: `cargo test -p pep440_rs --lib` 0.685
incremental: `cargo test -p pep440_rs --lib` 0.278s
from-scratch: `cargo test -p pep440_rs --features serde,rkyv --lib` 3.948s
incremental: `cargo test -p pep440_rs --features serde,rkyv --lib` 0.321s
```

So while a from-scratch build does take significantly longer, an
incremental build is about the same.

The benefit of doing this change is two-fold:

1. It brings out crates into alignment with "reality." In particular,
   some crates were _implicitly_ relying on `serde` being enabled
   without explicitly declaring it. This technically means that our
   `Cargo.toml`s were wrong in some cases, but it is hard to observe it
   because of feature unification in a Cargo workspace.
2. We no longer need to deal with the cognitive burden of writing
   `#[cfg_attr(feature = "serde", ...)]` everywhere.
2024-05-03 10:21:03 -04:00

100 lines
2.8 KiB
Rust

use std::borrow::Cow;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize};
use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError};
/// The normalized name of a package.
///
/// Converts the name to lowercase and collapses any run of the characters `-`, `_` and `.`
/// down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`.
///
/// See: <https://packaging.python.org/en/latest/specifications/name-normalization/>
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
Serialize,
rkyv::Archive,
rkyv::Deserialize,
rkyv::Serialize,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[archive(check_bytes)]
#[archive_attr(derive(Debug))]
pub struct PackageName(String);
impl PackageName {
/// Create a validated, normalized package name.
pub fn new(name: String) -> Result<Self, InvalidNameError> {
validate_and_normalize_owned(name).map(Self)
}
/// Escape this name with underscores (`_`) instead of dashes (`-`)
///
/// See: <https://packaging.python.org/en/latest/specifications/recording-installed-packages/#recording-installed-packages>
pub fn as_dist_info_name(&self) -> Cow<'_, str> {
if let Some(dash_position) = self.0.find('-') {
// Initialize `replaced` with the start of the string up to the current character.
let mut owned_string = String::with_capacity(self.0.len());
owned_string.push_str(&self.0[..dash_position]);
owned_string.push('_');
// Iterate over the rest of the string.
owned_string.extend(self.0[dash_position + 1..].chars().map(|character| {
if character == '-' {
'_'
} else {
character
}
}));
Cow::Owned(owned_string)
} else {
Cow::Borrowed(self.0.as_str())
}
}
}
impl From<&Self> for PackageName {
/// Required for `WaitMap::wait`.
fn from(package_name: &Self) -> Self {
package_name.clone()
}
}
impl FromStr for PackageName {
type Err = InvalidNameError;
fn from_str(name: &str) -> Result<Self, Self::Err> {
validate_and_normalize_ref(name).map(Self)
}
}
impl<'de> Deserialize<'de> for PackageName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl std::fmt::Display for PackageName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<str> for PackageName {
fn as_ref(&self) -> &str {
&self.0
}
}