Skip to content

Commit

Permalink
serde impls
Browse files Browse the repository at this point in the history
  • Loading branch information
Manishearth committed Nov 9, 2024
1 parent f426f3e commit fb908a2
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 15 deletions.
19 changes: 19 additions & 0 deletions utils/zerovec/derive/examples/make_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use std::borrow::Cow;
use std::fmt::Debug;

use zerofrom::ZeroFrom;
use zerovec::{ule::AsULE, *};
Expand Down Expand Up @@ -105,6 +106,8 @@ where
U: ule::EncodeAsVarULE<T> + serde::Serialize,
F: Fn(&U, &T),
for<'a> Box<T>: serde::Deserialize<'a>,
for<'a> &'a T: serde::Deserialize<'a>,
T: PartialEq + Debug,
{
let varzerovec: VarZeroVec<T> = slice.into();

Expand Down Expand Up @@ -142,6 +145,22 @@ where
for (stack, zero) in slice.iter().zip(deserialized.iter()) {
assert(stack, zero)
}

if let Some(first) = varzerovec.get(0) {
let bincode = bincode::serialize(first).unwrap();
let deserialized: &T = bincode::deserialize(&bincode).unwrap();
assert_eq!(
first, deserialized,
"Single element roundtrips with bincode"
);

let json = serde_json::to_string(first).unwrap();
let deserialized: Box<T> = serde_json::from_str(&json).unwrap();
assert_eq!(
first, &*deserialized,
"Single element roundtrips with serde"
);
}
}

fn main() {
Expand Down
45 changes: 41 additions & 4 deletions utils/zerovec/derive/src/make_varule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,19 @@ pub fn make_varule_impl(ule_name: Ident, mut input: DeriveInput) -> TokenStream2
quote!()
};

let maybe_toowned = if !attrs.skip_toowned {
quote!(
impl zerovec::__zerovec_internal_reexport::borrow::ToOwned for #ule_name {
type Owned = zerovec::__zerovec_internal_reexport::boxed::Box<Self>;
fn to_owned(&self) -> Self::Owned {
zerovec::ule::encode_varule_to_box(self)
}
}
)
} else {
quote!()
};

let zmkv = if attrs.skip_kv {
quote!()
} else {
Expand All @@ -219,21 +232,43 @@ pub fn make_varule_impl(ule_name: Ident, mut input: DeriveInput) -> TokenStream2
quote!(
impl #serde_path::Serialize for #ule_name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: #serde_path::Serializer {
let this = #zerofrom_fq_path::zero_from(self);
<#name as #serde_path::Serialize>::serialize(&this, serializer)
if serializer.is_human_readable() {
let this = #zerofrom_fq_path::zero_from(self);
<#name as #serde_path::Serialize>::serialize(&this, serializer)
} else {
serializer.serialize_bytes(zerovec::ule::VarULE::as_byte_slice(self))
}
}
}
)
} else {
quote!()
};

let deserialize_error = format!("&{ule_name} can only deserialize in zero-copy ways");

let maybe_de = if attrs.deserialize {
quote!(
impl<'de> #serde_path::Deserialize<'de> for zerovec::__zerovec_internal_reexport::boxed::Box<#ule_name> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: #serde_path::Deserializer<'de> {
let this = <#name as #serde_path::Deserialize>::deserialize(deserializer)?;
Ok(zerovec::ule::encode_varule_to_box(&this))
if deserializer.is_human_readable() {
let this = <#name as #serde_path::Deserialize>::deserialize(deserializer)?;
Ok(zerovec::ule::encode_varule_to_box(&this))
} else {
// This branch should usually not be hit, since Cow-like use cases will hit the Deserialize impl for &'a ULEType instead.
let deserialized = <& #ule_name>::deserialize(deserializer)?;
Ok(zerovec::ule::VarULE::to_boxed(deserialized))
}
}
}
impl<'a, 'de: 'a> #serde_path::Deserialize<'de> for &'a #ule_name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: #serde_path::Deserializer<'de> {
if !deserializer.is_human_readable() {
let bytes = <&[u8]>::deserialize(deserializer)?;
<#ule_name as zerovec::ule::VarULE>::parse_byte_slice(bytes).map_err(#serde_path::de::Error::custom)
} else {
Err(#serde_path::de::Error::custom(#deserialize_error))
}
}
}
)
Expand Down Expand Up @@ -289,6 +324,8 @@ pub fn make_varule_impl(ule_name: Ident, mut input: DeriveInput) -> TokenStream2

#maybe_debug

#maybe_toowned

#maybe_hash
)
}
Expand Down
3 changes: 3 additions & 0 deletions utils/zerovec/derive/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ pub fn extract_field_attributes(attrs: &mut Vec<Attribute>) -> Result<Option<Ide
pub struct ZeroVecAttrs {
pub skip_kv: bool,
pub skip_ord: bool,
pub skip_toowned: bool,
pub serialize: bool,
pub deserialize: bool,
pub debug: bool,
Expand Down Expand Up @@ -322,6 +323,8 @@ pub fn extract_attributes_common(
attrs.skip_kv = true;
} else if ident == "Ord" {
attrs.skip_ord = true;
} else if ident == "ToOwned" && is_var {
attrs.skip_toowned = true;
} else {
return Err(Error::new(
ident.span(),
Expand Down
1 change: 1 addition & 0 deletions utils/zerovec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ pub use crate::zerovec::{ZeroSlice, ZeroVec};
pub mod __zerovec_internal_reexport {
pub use zerofrom::ZeroFrom;

pub use alloc::borrow;
pub use alloc::boxed;

#[cfg(feature = "serde")]
Expand Down
5 changes: 5 additions & 0 deletions utils/zerovec/src/ule/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ impl<const LEN: usize, Format: VarZeroVecFormat> MultiFieldsULE<LEN, Format> {
// &Self is transparent over &VZS<..>
mem::transmute(<VarZeroLengthlessSlice<[u8]>>::from_bytes_unchecked(bytes))
}

/// Get the bytes behind this value
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}

impl<const LEN: usize, Format: VarZeroVecFormat> fmt::Debug for MultiFieldsULE<LEN, Format> {
Expand Down
56 changes: 56 additions & 0 deletions utils/zerovec/src/ule/tuplevar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,62 @@ macro_rules! tuple_varule {
)
}
}

#[cfg(feature = "serde")]
impl<$($T: serde::Serialize),+> serde::Serialize for $name<$($T),+>
where
$($T: VarULE + ?Sized,)+
// This impl should be present on almost all VarULE types. if it isn't, that is a bug
$(for<'a> &'a $T: ZeroFrom<'a, $T>),+
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
if serializer.is_human_readable() {
let this = (
$(self.$t()),+
);
<($(&$T),+) as serde::Serialize>::serialize(&this, serializer)
} else {
serializer.serialize_bytes(self.multi.as_bytes())
}
}
}

#[cfg(feature = "serde")]
impl<'de, $($T: VarULE + ?Sized),+> serde::Deserialize<'de> for Box<$name<$($T),+>>
where
// This impl should be present on almost all deserializable VarULE types
$( Box<$T>: serde::Deserialize<'de>),+ {
fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error> where Des: serde::Deserializer<'de> {
if deserializer.is_human_readable() {
let this = <( $(Box<$T>),+) as serde::Deserialize>::deserialize(deserializer)?;
let this_ref = (
$(&*this.$i),+
);
Ok(crate::ule::encode_varule_to_box(&this_ref))
} else {
// This branch should usually not be hit, since Cow-like use cases will hit the Deserialize impl for &'a TupleNVarULE instead.

let deserialized = <&$name<$($T),+>>::deserialize(deserializer)?;
Ok(deserialized.to_boxed())
}
}
}

#[cfg(feature = "serde")]
impl<'a, 'de, $($T: VarULE + ?Sized),+> serde::Deserialize<'de> for &'a $name<$($T),+>
where
'de: 'a {
fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error> where Des: serde::Deserializer<'de> {
if !deserializer.is_human_readable() {
let bytes = <&[u8]>::deserialize(deserializer)?;
$name::<$($T),+>::parse_byte_slice(bytes).map_err(serde::de::Error::custom)
} else {
Err(serde::de::Error::custom(
concat!("&", stringify!($name), " can only deserialize in zero-copy ways"),
))
}
}
}
};
}

Expand Down
99 changes: 99 additions & 0 deletions utils/zerovec/src/ule/vartuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@
//! assert_eq!(&employees_vzv.get(1).unwrap().variable, "John Doe");
//! ```
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use core::mem::{size_of, transmute_copy};
use zerofrom::ZeroFrom;

use super::{AsULE, EncodeAsVarULE, UleError, VarULE, ULE};

Expand All @@ -58,6 +61,7 @@ use super::{AsULE, EncodeAsVarULE, UleError, VarULE, ULE};
/// See the module for examples.
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)] // well-defined type
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VarTuple<A, B> {
pub sized: A,
pub variable: B,
Expand Down Expand Up @@ -180,6 +184,101 @@ where
}
}

impl<A, V> ToOwned for VarTupleULE<A, V>
where
A: AsULE + 'static,
V: VarULE + ?Sized,
{
type Owned = Box<Self>;
fn to_owned(&self) -> Self::Owned {
crate::ule::encode_varule_to_box(self)
}
}

impl<'a, A, B, V> ZeroFrom<'a, VarTupleULE<A, V>> for VarTuple<A, B>
where
A: AsULE + 'static,
V: VarULE + ?Sized,
B: ZeroFrom<'a, V>,
{
fn zero_from(other: &'a VarTupleULE<A, V>) -> Self {
VarTuple {
sized: AsULE::from_unaligned(other.sized),
variable: B::zero_from(&other.variable),
}
}
}

#[cfg(feature = "serde")]
impl<A, V> serde::Serialize for VarTupleULE<A, V>
where
A: AsULE + 'static,
V: VarULE + ?Sized,
A: serde::Serialize,
V: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
let this = VarTuple {
sized: A::from_unaligned(self.sized),
variable: &self.variable,
};
this.serialize(serializer)
} else {
serializer.serialize_bytes(self.as_byte_slice())
}
}
}

#[cfg(feature = "serde")]
impl<'a, 'de: 'a, A, V> serde::Deserialize<'de> for &'a VarTupleULE<A, V>
where
A: AsULE + 'static,
V: VarULE + ?Sized,
A: serde::Deserialize<'de>,
{
fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
where
Des: serde::Deserializer<'de>,
{
if !deserializer.is_human_readable() {
let bytes = <&[u8]>::deserialize(deserializer)?;
VarTupleULE::<A, V>::parse_byte_slice(bytes).map_err(serde::de::Error::custom)
} else {
Err(serde::de::Error::custom(
"&VarTupleULE can only deserialize in zero-copy ways",
))
}
}
}

#[cfg(feature = "serde")]
impl<'de, A, V> serde::Deserialize<'de> for Box<VarTupleULE<A, V>>
where
A: AsULE + 'static,
V: VarULE + ?Sized,
A: serde::Deserialize<'de>,
Box<V>: serde::Deserialize<'de>,
{
fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
where
Des: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let this = VarTuple::<A, Box<V>>::deserialize(deserializer)?;
Ok(crate::ule::encode_varule_to_box(&this))
} else {
// This branch should usually not be hit, since Cow-like use cases will hit the Deserialize impl for &'a TupleNVarULE instead.

let deserialized = <&VarTupleULE<A, V>>::deserialize(deserializer)?;
Ok(deserialized.to_boxed())
}
}
}

#[test]
fn test_simple() {
let var_tuple = VarTuple {
Expand Down
13 changes: 2 additions & 11 deletions utils/zerovec/src/varzerovec/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use super::vec::VarZeroVecInner;
use super::{VarZeroSlice, VarZeroVec, VarZeroVecFormat};
use crate::ule::*;
use alloc::boxed::Box;
Expand Down Expand Up @@ -86,7 +85,6 @@ where
impl<'de, 'a, T, F> Deserialize<'de> for &'a VarZeroSlice<T, F>
where
T: VarULE + ?Sized,
Box<T>: Deserialize<'de>,
F: VarZeroVecFormat,
'de: 'a,
{
Expand All @@ -99,15 +97,8 @@ where
"&VarZeroSlice cannot be deserialized from human-readable formats",
))
} else {
let deserialized = VarZeroVec::<'a, T, F>::deserialize(deserializer)?;
let borrowed = if let VarZeroVec(VarZeroVecInner::Borrowed(b)) = deserialized {
b
} else {
return Err(de::Error::custom(
"&VarZeroSlice can only deserialize in zero-copy ways",
));
};
Ok(borrowed)
let bytes = <&[u8]>::deserialize(deserializer)?;
VarZeroSlice::<T, F>::parse_byte_slice(bytes).map_err(de::Error::custom)
}
}
}
Expand Down

0 comments on commit fb908a2

Please sign in to comment.