diff --git a/Cargo.lock b/Cargo.lock
index 89ecf256c..d311fba8d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5915,6 +5915,7 @@ dependencies = [
"insta",
"itertools 0.14.0",
"regex",
+ "rkyv",
"rustc-hash",
"schemars",
"serde",
diff --git a/crates/uv-normalize/src/extra_name.rs b/crates/uv-normalize/src/extra_name.rs
index 818aa8051..7b9000ac6 100644
--- a/crates/uv-normalize/src/extra_name.rs
+++ b/crates/uv-normalize/src/extra_name.rs
@@ -97,8 +97,21 @@ impl Default for DefaultExtras {
/// See:
/// -
/// -
-#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
+#[derive(
+ Debug,
+ Clone,
+ PartialEq,
+ Eq,
+ PartialOrd,
+ Ord,
+ Hash,
+ Serialize,
+ rkyv::Archive,
+ rkyv::Deserialize,
+ rkyv::Serialize,
+)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+#[rkyv(derive(Debug))]
pub struct ExtraName(SmallString);
impl ExtraName {
diff --git a/crates/uv-normalize/src/group_name.rs b/crates/uv-normalize/src/group_name.rs
index ce4c78bf8..124079c08 100644
--- a/crates/uv-normalize/src/group_name.rs
+++ b/crates/uv-normalize/src/group_name.rs
@@ -17,8 +17,20 @@ use crate::{
/// See:
/// -
/// -
-#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[derive(
+ Debug,
+ Clone,
+ PartialEq,
+ Eq,
+ PartialOrd,
+ Ord,
+ Hash,
+ rkyv::Archive,
+ rkyv::Deserialize,
+ rkyv::Serialize,
+)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+#[rkyv(derive(Debug))]
pub struct GroupName(SmallString);
impl GroupName {
diff --git a/crates/uv-pep508/Cargo.toml b/crates/uv-pep508/Cargo.toml
index c4830043b..aaa9698b4 100644
--- a/crates/uv-pep508/Cargo.toml
+++ b/crates/uv-pep508/Cargo.toml
@@ -30,6 +30,7 @@ boxcar = { workspace = true }
indexmap = { workspace = true }
itertools = { workspace = true }
regex = { workspace = true }
+rkyv = { workspace = true, optional = true }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive", "rc"] }
@@ -56,5 +57,6 @@ schemars = ["dep:schemars"]
# be supported.
non-pep508-extensions = []
default = []
+rkyv = ["dep:rkyv"]
# Match the API of the published crate, for compatibility.
serde = []
diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs
index 85da84938..30c7b9362 100644
--- a/crates/uv-pep508/src/lib.rs
+++ b/crates/uv-pep508/src/lib.rs
@@ -1045,6 +1045,41 @@ fn parse_pep508_requirement(
})
}
+#[cfg(feature = "rkyv")]
+/// An [`rkyv`] implementation for [`Requirement`].
+impl rkyv::Archive for Requirement {
+ type Archived = rkyv::string::ArchivedString;
+ type Resolver = rkyv::string::StringResolver;
+
+ #[inline]
+ fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place) {
+ let as_str = self.to_string();
+ rkyv::string::ArchivedString::resolve_from_str(&as_str, resolver, out);
+ }
+}
+
+#[cfg(feature = "rkyv")]
+impl rkyv::Serialize for Requirement
+where
+ S: rkyv::rancor::Fallible + rkyv::ser::Allocator + rkyv::ser::Writer + ?Sized,
+ S::Error: rkyv::rancor::Source,
+{
+ fn serialize(&self, serializer: &mut S) -> Result {
+ let as_str = self.to_string();
+ rkyv::string::ArchivedString::serialize_from_str(&as_str, serializer)
+ }
+}
+
+#[cfg(feature = "rkyv")]
+impl
+ rkyv::Deserialize, D> for rkyv::string::ArchivedString
+{
+ fn deserialize(&self, _deserializer: &mut D) -> Result, D::Error> {
+ // SAFETY: We only serialize valid requirements.
+ Ok(Requirement::::from_str(self.as_str()).unwrap())
+ }
+}
+
#[cfg(test)]
mod tests {
//! Half of these tests are copied from
diff --git a/crates/uv-pypi-types/Cargo.toml b/crates/uv-pypi-types/Cargo.toml
index e5ceef631..e75a6c057 100644
--- a/crates/uv-pypi-types/Cargo.toml
+++ b/crates/uv-pypi-types/Cargo.toml
@@ -21,7 +21,7 @@ uv-distribution-filename = { workspace = true }
uv-git-types = { workspace = true }
uv-normalize = { workspace = true }
uv-pep440 = { workspace = true }
-uv-pep508 = { workspace = true }
+uv-pep508 = { workspace = true, features = ["rkyv"] }
uv-redacted = { workspace = true }
uv-small-str = { workspace = true }
diff --git a/crates/uv-pypi-types/src/metadata/metadata_resolver.rs b/crates/uv-pypi-types/src/metadata/metadata_resolver.rs
index 8f90b3614..506b7651d 100644
--- a/crates/uv-pypi-types/src/metadata/metadata_resolver.rs
+++ b/crates/uv-pypi-types/src/metadata/metadata_resolver.rs
@@ -19,8 +19,11 @@ use crate::{LenientVersionSpecifiers, MetadataError, VerbatimParsedUrl, metadata
/// fields that are relevant to dependency resolution.
///
/// Core Metadata 2.3 is specified in .
-#[derive(Serialize, Deserialize, Debug, Clone)]
+#[derive(
+ Serialize, Deserialize, Debug, Clone, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize,
+)]
#[serde(rename_all = "kebab-case")]
+#[rkyv(derive(Debug))]
pub struct ResolutionMetadata {
// Mandatory fields
pub name: PackageName,
diff --git a/crates/uv-pypi-types/src/simple_json.rs b/crates/uv-pypi-types/src/simple_json.rs
index f92516f81..b26837cff 100644
--- a/crates/uv-pypi-types/src/simple_json.rs
+++ b/crates/uv-pypi-types/src/simple_json.rs
@@ -141,6 +141,18 @@ impl<'de> Deserialize<'de> for CoreMetadata {
}
}
+impl Serialize for CoreMetadata {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ match self {
+ Self::Bool(is_available) => serializer.serialize_bool(*is_available),
+ Self::Hashes(hashes) => hashes.serialize(serializer),
+ }
+ }
+}
+
impl CoreMetadata {
pub fn is_available(&self) -> bool {
match self {
@@ -169,6 +181,18 @@ impl<'de> Deserialize<'de> for Yanked {
}
}
+impl Serialize for Yanked {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ match self {
+ Self::Bool(is_yanked) => serializer.serialize_bool(*is_yanked),
+ Self::Reason(reason) => serializer.serialize_str(reason.as_ref()),
+ }
+ }
+}
+
impl Yanked {
pub fn is_yanked(&self) -> bool {
match self {