diff --git a/Cargo.toml b/Cargo.toml index d4f38e4b..58797513 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/core/src/serde/de.rs b/core/src/serde/de.rs index 6f0655aa..c20d4a84 100644 --- a/core/src/serde/de.rs +++ b/core/src/serde/de.rs @@ -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, }; @@ -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(self, deserializer: D) -> Result + fn visit_enum(self, data: A) -> Result 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 @@ 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>(self, visitor: V) -> Result { + 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::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(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(self, _seed: T) -> Result + where + T: de::DeserializeSeed<'de>, + { + unreachable!(); + } + + fn tuple_variant(self, len: usize, visitor: V) -> Result + 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( + self, + _fields: &'static [&'static str], + _visitor: V, + ) -> Result + 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 @@ 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)), } } @@ -464,31 +547,30 @@ impl<'de> de::Deserializer<'de> for Ipld { fn deserialize_newtype_struct>( self, - name: &str, + _name: &str, visitor: V, ) -> Result { - 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>( self, - _name: &str, - _variants: &[&str], + name: &str, + variants: &[&str], visitor: V, ) -> Result { - 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 @@ impl<'de> de::Deserializer<'de> for Ipld { )), }; - 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 e502fe79..5f7c9fd7 100644 --- a/core/src/serde/mod.rs +++ b/core/src/serde/mod.rs @@ -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}; @@ -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 { @@ -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, ], ); diff --git a/core/src/serde/ser.rs b/core/src/serde/ser.rs index 94b60384..4ffedc99 100644 --- a/core/src/serde/ser.rs +++ b/core/src/serde/ser.rs @@ -209,21 +209,13 @@ impl serde::Serializer for Serializer { #[inline] fn serialize_newtype_struct( self, - name: &'static str, + _name: &'static str, value: &T, ) -> Result 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( @@ -398,7 +390,15 @@ impl ser::SerializeTupleVariant for SerializeTupleVariant { Ok(()) } - fn end(self) -> Result { + fn end(mut self) -> Result { + 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 3e67ce2d..6a1b45a7 100644 --- a/core/tests/serde_deserialize.rs +++ b/core/tests/serde_deserialize.rs @@ -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] @@ -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() { @@ -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); +} diff --git a/core/tests/serde_serialize.rs b/core/tests/serde_serialize.rs index 1e9c17b9..83a60177 100644 --- a/core/tests/serde_serialize.rs +++ b/core/tests/serde_serialize.rs @@ -95,14 +95,17 @@ fn ipld_serialize_link() { 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, ], ); }