Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: represent CID as enum and not as newtype struct #143

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ harness = false
[[bench]]
name = "store"
harness = false

[patch.crates-io]
cid = { git = "https://github.com/ipld/rust-cid", branch = "cid-as-enum" }
131 changes: 107 additions & 24 deletions core/src/serde/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core::{convert::TryFrom, fmt};
use cid::serde::{BytesToCidVisitor, CID_SERDE_PRIVATE_IDENTIFIER};
use cid::Cid;
use serde::{
de::{self, IntoDeserializer},
de::{self, IntoDeserializer, VariantAccess},
forward_to_deserialize_any,
};

Expand Down Expand Up @@ -170,15 +170,24 @@ impl<'de> de::Deserialize<'de> for Ipld {
Ok(Ipld::Map(values))
}

/// Newtype structs are only used to deserialize CIDs.
/// Enums are only used to deserialize CIDs.
#[inline]
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
where
D: de::Deserializer<'de>,
A: de::EnumAccess<'de>,
{
deserializer
.deserialize_bytes(BytesToCidVisitor)
.map(Ipld::Link)
match data.variant() {
// Make sure that we only deserialize a CID when we clearly intended to.
Ok((CID_SERDE_PRIVATE_IDENTIFIER, value)) => {
// It's not really a tuple, we use the `tuple_variant` call in order to be
// able to pass in a custom visitor.
let cid = value.tuple_variant(1, BytesToCidVisitor)?;
Ok(Ipld::Link(cid))
}
_ => Err(de::Error::custom(
"invalid type: enum, expected any valid IPLD kind",
)),
}
}
}

Expand Down Expand Up @@ -207,6 +216,26 @@ macro_rules! impl_deserialize_integer {
};
}

/// Dummy deserializer to deserialize an existing string.
///
/// From https://github.com/honsunrise/path-value/blob/d5eb3283f68b82e73cbc627889c32d32d484a009/src/value/de.rs#L141-L162
struct StrDeserializer<'a>(&'a str);

impl<'de, 'a: 'de> de::Deserializer<'de> for StrDeserializer<'a> {
type Error = SerdeError;

#[inline]
fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_borrowed_str(self.0)
}

forward_to_deserialize_any! {
bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
bytes byte_buf map struct unit enum newtype_struct
identifier ignored_any unit_struct tuple_struct tuple option
}
}

/// A Deserializer for CIDs.
///
/// A separate deserializer is needed to make sure we always deserialize only CIDs as `Ipld::Link`
Expand All @@ -231,6 +260,60 @@ impl<'de> de::Deserializer<'de> for CidDeserializer {
}
}

impl<'de> de::EnumAccess<'de> for CidDeserializer {
type Error = SerdeError;
// We just implement `VariantAccess` for `CidDeserializer`.
type Variant = Self;

fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
where
V: de::DeserializeSeed<'de>,
{
// This is the Serde way of saying `let value = CID_SERDE_PRIVATE_IDENTIFIER;`.
let key = seed.deserialize(StrDeserializer(CID_SERDE_PRIVATE_IDENTIFIER))?;
// The `CidDeserializer` already contains the CID, hence return itself.
Ok((key, self))
}
}

impl<'de> de::VariantAccess<'de> for CidDeserializer {
type Error = SerdeError;

fn unit_variant(self) -> Result<(), Self::Error> {
unreachable!();
}

fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, Self::Error>
where
T: de::DeserializeSeed<'de>,
{
unreachable!();
}

fn tuple_variant<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
if len == 1 {
// This is not how tuple variants usually work. This is a hack in order to get a CID out.
visitor.visit_bytes(&self.0.to_bytes())
} else {
error("CidDeserializer only support deserializing CIDs")
}
}

fn struct_variant<V>(
self,
_fields: &'static [&'static str],
_visitor: V,
) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
unreachable!();
}
}

/// Deserialize from an [`Ipld`] enum into a Rust type.
///
/// The deserialization will return an error if you try to deserialize into an integer type that
Expand All @@ -254,7 +337,7 @@ impl<'de> de::Deserializer<'de> for Ipld {
Self::Bytes(bytes) => visitor.visit_bytes(&bytes),
Self::List(list) => visit_seq(list, visitor),
Self::Map(map) => visit_map(map, visitor),
Self::Link(cid) => visitor.visit_newtype_struct(CidDeserializer(cid)),
Self::Link(cid) => visitor.visit_enum(CidDeserializer(cid)),
}
}

Expand Down Expand Up @@ -464,31 +547,30 @@ impl<'de> de::Deserializer<'de> for Ipld {

fn deserialize_newtype_struct<V: de::Visitor<'de>>(
self,
name: &str,
_name: &str,
visitor: V,
) -> Result<V::Value, Self::Error> {
if name == CID_SERDE_PRIVATE_IDENTIFIER {
match self {
Ipld::Link(cid) => visitor.visit_newtype_struct(CidDeserializer(cid)),
_ => error(format!(
"Only `Ipld::Link`s can be deserialized to CIDs, input was `{:#?}`",
self
)),
}
} else {
visitor.visit_newtype_struct(self)
}
visitor.visit_newtype_struct(self)
}

// Heavily based on
// https://github.com/serde-rs/json/blob/95f67a09399d546d9ecadeb747a845a77ff309b2/src/value/de.rs#L249
fn deserialize_enum<V: de::Visitor<'de>>(
self,
_name: &str,
_variants: &[&str],
name: &str,
variants: &[&str],
visitor: V,
) -> Result<V::Value, Self::Error> {
let (variant, value) = match self {
if name == CID_SERDE_PRIVATE_IDENTIFIER && variants == [CID_SERDE_PRIVATE_IDENTIFIER] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible extension: allow the variants to be arbitrary IPLD kinds.

match self {
Ipld::Link(cid) => visitor.visit_enum(CidDeserializer(cid)),
_ => error(format!(
"Only `Ipld::Link`s can be deserialized to CIDs, input was `{:#?}`",
self
)),
}
} else {
let (variant, value) = match self {
Ipld::Map(map) => {
let mut iter = map.into_iter();
let (variant, value) = match iter.next() {
Expand All @@ -514,7 +596,8 @@ impl<'de> de::Deserializer<'de> for Ipld {
)),
};

visitor.visit_enum(EnumDeserializer { variant, value })
visitor.visit_enum(EnumDeserializer { variant, value })
}
}

// Heavily based on
Expand Down
11 changes: 8 additions & 3 deletions core/src/serde/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mod tests {
use cid::serde::CID_SERDE_PRIVATE_IDENTIFIER;
use cid::Cid;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_test::{assert_tokens, Token};
use serde_test::{assert_ser_tokens, Token};

use crate::ipld::Ipld;
use crate::serde::{from_ipld, to_ipld};
Expand Down Expand Up @@ -61,7 +61,9 @@ mod tests {
fn test_tokens() {
let person = Person::default();

assert_tokens(
// The `serde_test` deserializer doesn't deserialize enums as one would expect (it doesn't
// call `visit_enum`), hence only the serializer is tested.
assert_ser_tokens(
&person,
&[
Token::Struct {
Expand All @@ -80,14 +82,17 @@ mod tests {
Token::Str("is_cool"),
Token::Bool(true),
Token::Str("link"),
Token::NewtypeStruct {
Token::TupleVariant {
name: CID_SERDE_PRIVATE_IDENTIFIER,
variant: CID_SERDE_PRIVATE_IDENTIFIER,
len: 1,
},
Token::Bytes(&[
0x01, 0x71, 0x12, 0x20, 0x35, 0x4d, 0x45, 0x5f, 0xf3, 0xa6, 0x41, 0xb8, 0xca,
0xc2, 0x5c, 0x38, 0xa7, 0x7e, 0x64, 0xaa, 0x73, 0x5d, 0xc8, 0xa4, 0x89, 0x66,
0xa6, 0xf, 0x1a, 0x78, 0xca, 0xa1, 0x72, 0xa4, 0x88, 0x5e,
]),
Token::TupleVariantEnd,
Token::StructEnd,
],
);
Expand Down
22 changes: 11 additions & 11 deletions core/src/serde/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,21 +209,13 @@ impl serde::Serializer for Serializer {
#[inline]
fn serialize_newtype_struct<T: ?Sized>(
self,
name: &'static str,
_name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: ser::Serialize,
{
let ipld = value.serialize(self);
if name == CID_SERDE_PRIVATE_IDENTIFIER {
if let Ok(Ipld::Bytes(bytes)) = ipld {
let cid = Cid::try_from(bytes)
.map_err(|err| ser::Error::custom(format!("Invalid CID: {}", err)))?;
return Ok(Self::Ok::Link(cid));
}
}
ipld
value.serialize(self)
}

fn serialize_newtype_variant<T: ?Sized>(
Expand Down Expand Up @@ -398,7 +390,15 @@ impl ser::SerializeTupleVariant for SerializeTupleVariant {
Ok(())
}

fn end(self) -> Result<Self::Ok, Self::Error> {
fn end(mut self) -> Result<Self::Ok, Self::Error> {
if self.name == CID_SERDE_PRIVATE_IDENTIFIER && self.vec.len() == 1 {
if let Ipld::Bytes(bytes) = self.vec.pop().unwrap() {
let cid = Cid::try_from(bytes)
.map_err(|err| ser::Error::custom(format!("Invalid CID: {}", err)))?;
return Ok(Self::Ok::Link(cid));
}
}

let map = BTreeMap::from([(self.name, Self::Ok::List(self.vec))]);
Ok(Self::Ok::Map(map))
}
Expand Down
89 changes: 69 additions & 20 deletions core/tests/serde_deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ extern crate alloc;
use alloc::collections::BTreeMap;
use core::convert::TryFrom;

use serde::Deserialize;
use serde_test::{assert_de_tokens, Token};

use libipld_core::cid::{serde::CID_SERDE_PRIVATE_IDENTIFIER, Cid};
use libipld_core::cid::Cid;
use libipld_core::ipld::Ipld;

#[test]
Expand Down Expand Up @@ -123,25 +124,6 @@ fn ipld_deserialize_map() {
);
}

#[test]
fn ipld_deserialize_link() {
let cid = Cid::try_from("bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m").unwrap();
let ipld = Ipld::Link(cid);
assert_de_tokens(
&ipld,
&[
Token::NewtypeStruct {
name: CID_SERDE_PRIVATE_IDENTIFIER,
},
Token::Bytes(&[
1, 85, 18, 32, 159, 228, 204, 198, 222, 22, 114, 79, 58, 48, 199, 232, 242, 84,
243, 198, 71, 25, 134, 172, 177, 248, 216, 207, 142, 150, 206, 42, 215, 219, 231,
251,
]),
],
);
}

#[test]
#[should_panic(expected = "assertion failed")]
fn ipld_deserialize_link_not_as_bytes() {
Expand All @@ -155,3 +137,70 @@ fn ipld_deserialize_link_not_as_bytes() {
])],
);
}

#[test]
fn ipld_deserialize_ipld_null() {
let ipld = Ipld::Null;
let deserialized = Ipld::deserialize(ipld.clone()).unwrap();
assert_eq!(deserialized, ipld);
}

#[test]
fn ipld_deserialize_ipld_bool() {
let ipld = Ipld::Bool(true);
let deserialized = Ipld::deserialize(ipld.clone()).unwrap();
assert_eq!(deserialized, ipld);
}

#[test]
fn ipld_deserialize_ipld_integer() {
let ipld = Ipld::Integer(31);
let deserialized = Ipld::deserialize(ipld.clone()).unwrap();
assert_eq!(deserialized, ipld);
}

#[test]
fn ipld_deserialize_ipld_float() {
let ipld = Ipld::Float(211.421);
let deserialized = Ipld::deserialize(ipld.clone()).unwrap();
assert_eq!(deserialized, ipld);
}

#[test]
fn ipld_deserialize_ipld_string() {
let ipld = Ipld::String("hello".into());
let deserialized = Ipld::deserialize(ipld.clone()).unwrap();
assert_eq!(deserialized, ipld);
}

#[test]
fn ipld_deserialize_ipld_bytes() {
let ipld = Ipld::Bytes(vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]);
let deserialized = Ipld::deserialize(ipld.clone()).unwrap();
assert_eq!(deserialized, ipld);
}

#[test]
fn ipld_deserialize_ipld_list() {
let ipld = Ipld::List(vec![Ipld::Bool(false), Ipld::Float(22.7)]);
let deserialized = Ipld::deserialize(ipld.clone()).unwrap();
assert_eq!(deserialized, ipld);
}

#[test]
fn ipld_deserialize_ipld_map() {
let ipld = Ipld::Map(BTreeMap::from([
("hello".to_string(), Ipld::Bool(true)),
("world!".to_string(), Ipld::Bool(false)),
]));
let deserialized = Ipld::deserialize(ipld.clone()).unwrap();
assert_eq!(deserialized, ipld);
}

#[test]
fn ipld_deserialize_ipld_link() {
let cid = Cid::try_from("bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m").unwrap();
let ipld = Ipld::Link(cid);
let deserialized = Ipld::deserialize(ipld.clone()).unwrap();
assert_eq!(deserialized, ipld);
}
Loading