WIP: represent CID as enum and not as newtype struct
Changing from newtype struct to enum should hopefully make things
more robust.
diff --git a/Cargo.toml b/Cargo.toml
index d4f38e4..5879751 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -63,3 +63,6 @@
[[bench]]
name = "store"
harness = false
+
+[patch.crates-io]
+cid = { git = "https://github.com/ipld/rust-cid", branch = "cid-as-enum" }
diff --git a/core/src/serde/de.rs b/core/src/serde/de.rs
index 6f0655a..c20d4a8 100644
--- a/core/src/serde/de.rs
+++ b/core/src/serde/de.rs
@@ -4,7 +4,7 @@
use cid::serde::{BytesToCidVisitor, CID_SERDE_PRIVATE_IDENTIFIER};
use cid::Cid;
use serde::{
- de::{self, IntoDeserializer},
+ de::{self, IntoDeserializer, VariantAccess},
forward_to_deserialize_any,
};
@@ -170,15 +170,24 @@
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",
+ )),
+ }
}
}
@@ -207,6 +216,26 @@
};
}
+/// 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`
@@ -231,6 +260,60 @@
}
}
+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
@@ -254,7 +337,7 @@
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)),
}
}
@@ -464,31 +547,30 @@
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] {
+ 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() {
@@ -514,7 +596,8 @@
)),
};
- visitor.visit_enum(EnumDeserializer { variant, value })
+ visitor.visit_enum(EnumDeserializer { variant, value })
+ }
}
// Heavily based on
diff --git a/core/src/serde/mod.rs b/core/src/serde/mod.rs
index e502fe7..5f7c9fd 100644
--- a/core/src/serde/mod.rs
+++ b/core/src/serde/mod.rs
@@ -17,7 +17,7 @@
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};
@@ -61,7 +61,9 @@
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 {
@@ -80,14 +82,17 @@
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,
],
);
diff --git a/core/src/serde/ser.rs b/core/src/serde/ser.rs
index 94b6038..4ffedc9 100644
--- a/core/src/serde/ser.rs
+++ b/core/src/serde/ser.rs
@@ -209,21 +209,13 @@
#[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>(
@@ -398,7 +390,15 @@
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))
}
diff --git a/core/tests/serde_deserialize.rs b/core/tests/serde_deserialize.rs
index 3e67ce2..6a1b45a 100644
--- a/core/tests/serde_deserialize.rs
+++ b/core/tests/serde_deserialize.rs
@@ -5,9 +5,10 @@
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]
@@ -124,25 +125,6 @@
}
#[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() {
let cid = Cid::try_from("bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m").unwrap();
@@ -155,3 +137,70 @@
])],
);
}
+
+#[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);
+}
diff --git a/core/tests/serde_serialize.rs b/core/tests/serde_serialize.rs
index 1e9c17b..83a6017 100644
--- a/core/tests/serde_serialize.rs
+++ b/core/tests/serde_serialize.rs
@@ -95,14 +95,17 @@
assert_ser_tokens(
&ipld,
&[
- Token::NewtypeStruct {
+ Token::TupleVariant {
name: CID_SERDE_PRIVATE_IDENTIFIER,
+ variant: CID_SERDE_PRIVATE_IDENTIFIER,
+ len: 1,
},
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,
]),
+ Token::TupleVariantEnd,
],
);
}