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: Jcb/serde minimal #147

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
1,353 changes: 718 additions & 635 deletions core/src/serde/de.rs

Large diffs are not rendered by default.

285 changes: 168 additions & 117 deletions core/src/serde/mod.rs
Original file line number Diff line number Diff line change
@@ -1,135 +1,186 @@
//! Serde (de)serializtion for [`crate::ipld::Ipld`].
//!
//! This implementation enables Serde to serialize to/deserialize from [`crate::ipld::Ipld`]
//! values. The `Ipld` enum is similar to the `Value` enum in `serde_json` or `serde_cbor`.
//! This implementation enables Serde to serialize to/deserialize from
//! [`crate::ipld::Ipld`] values. The `Ipld` enum is similar to the `Value` enum
//! in `serde_json` or `serde_cbor`.

mod de;
mod ser;

pub use de::from_ipld;
pub use ser::to_ipld;

#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fmt;

use cid::serde::CID_SERDE_PRIVATE_IDENTIFIER;
use cid::Cid;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_test::{assert_tokens, Token};

use crate::ipld::Ipld;
use crate::serde::{from_ipld, to_ipld};

/// Utility for testing (de)serialization of [`Ipld`].
///
/// Checks if `data` and `ipld` match if they are encoded into each other.
fn assert_roundtrip<T>(data: &T, ipld: &Ipld)
where
T: Serialize + DeserializeOwned + PartialEq + fmt::Debug,
{
let encoded: Ipld = to_ipld(&data).unwrap();
assert_eq!(&encoded, ipld);
let decoded: T = from_ipld(ipld.clone()).unwrap();
assert_eq!(&decoded, data);
}
mod test {
use std::{
convert::TryFrom,
fmt,
};

#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct Person {
name: String,
age: u8,
hobbies: Vec<String>,
is_cool: bool,
link: Cid,
}
use alloc::collections::BTreeMap;
use cid::{
serde::CID_SERDE_PRIVATE_IDENTIFIER,
Cid,
};
use serde::{
de::DeserializeOwned,
Deserialize,
Serialize,
};
use serde_test::{
assert_tokens,
Token,
};

impl Default for Person {
fn default() -> Self {
Self {
name: "Hello World!".into(),
age: 52,
hobbies: vec!["geography".into(), "programming".into()],
is_cool: true,
link: Cid::try_from("bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily")
.unwrap(),
}
}
}
use crate::{
ipld::Ipld,
serde::{
from_ipld,
to_ipld,
},
};

#[test]
fn test_tokens() {
let person = Person::default();

assert_tokens(
&person,
&[
Token::Struct {
name: "Person",
len: 5,
},
Token::Str("name"),
Token::Str("Hello World!"),
Token::Str("age"),
Token::U8(52),
Token::Str("hobbies"),
Token::Seq { len: Some(2) },
Token::Str("geography"),
Token::Str("programming"),
Token::SeqEnd,
Token::Str("is_cool"),
Token::Bool(true),
Token::Str("link"),
Token::NewtypeStruct {
name: CID_SERDE_PRIVATE_IDENTIFIER,
},
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::StructEnd,
],
);
}
/// Utility for testing (de)serialization of [`Ipld`].
/// Checks if `data` and `ipld` match if they are encoded into each other.
fn assert_roundtrip<T>(data: &T, ipld: &Ipld)
where T: Serialize + DeserializeOwned + PartialEq + fmt::Debug {
let encoded: Ipld = to_ipld(&data).unwrap();
assert_eq!(&encoded, ipld);
let decoded: T = from_ipld(ipld.clone()).unwrap();
assert_eq!(&decoded, data);
}

/// Test if converting to a struct from [`crate::ipld::Ipld`] and back works.
#[test]
fn test_ipld() {
let person = Person::default();

let expected_ipld = Ipld::Map({
BTreeMap::from([
("name".into(), Ipld::String("Hello World!".into())),
("age".into(), Ipld::Integer(52)),
(
"hobbies".into(),
Ipld::List(vec![
Ipld::String("geography".into()),
Ipld::String("programming".into()),
]),
),
("is_cool".into(), Ipld::Bool(true)),
("link".into(), Ipld::Link(person.link)),
])
});

assert_roundtrip(&person, &expected_ipld);
#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct Person {
name: String,
age: u8,
hobbies: Vec<String>,
is_cool: bool,
link: Cid,
}

impl Default for Person {
fn default() -> Self {
Self {
name: "Hello World!".into(),
age: 52,
hobbies: vec!["geography".into(), "programming".into()],
is_cool: true,
link: Cid::try_from(
"bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily",
)
.unwrap(),
}
}
}

/// Test that deserializing arbitrary bytes are not accidently recognized as CID.
#[test]
fn test_bytes_not_cid() {
let cid =
Cid::try_from("bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily").unwrap();
#[derive(Debug, Deserialize, PartialEq, Serialize)]
enum Enum {
UnitVariant,
NewTypeVariant(bool),
TupleVariant(bool, u8),
StructVariant { x: bool, y: u8 },
}

let bytes_not_cid = Ipld::Bytes(cid.to_bytes());
let not_a_cid: Result<Cid, _> = from_ipld(bytes_not_cid);
assert!(not_a_cid.is_err());
#[test]
fn test_tokens() {
let person = Person::default();

// Make sure that a Ipld::Link deserializes correctly though.
let link = Ipld::Link(cid);
let a_cid: Cid = from_ipld(link).unwrap();
assert_eq!(a_cid, cid);
}
assert_tokens(&person, &[
Token::Struct { name: "Person", len: 5 },
Token::Str("name"),
Token::Str("Hello World!"),
Token::Str("age"),
Token::U8(52),
Token::Str("hobbies"),
Token::Seq { len: Some(2) },
Token::Str("geography"),
Token::Str("programming"),
Token::SeqEnd,
Token::Str("is_cool"),
Token::Bool(true),
Token::Str("link"),
Token::NewtypeStruct { name: CID_SERDE_PRIVATE_IDENTIFIER },
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::StructEnd,
]);
}

/// Test if converting to a struct from [`crate::ipld::Ipld`] and back works.
#[test]
fn test_ipld() {
let person = Person::default();

let expected_ipld = Ipld::List(vec![
Ipld::String("Hello World!".into()),
Ipld::Integer(52),
Ipld::List(vec![
Ipld::String("geography".into()),
Ipld::String("programming".into()),
]),
Ipld::Bool(true),
Ipld::Link(person.link),
]);

assert_roundtrip(&person, &expected_ipld);

let unit_enum = Enum::UnitVariant;
let expected_ipld = Ipld::List(vec![Ipld::Integer(0)]);
assert_roundtrip(&unit_enum, &expected_ipld);

let newtype_enum = Enum::NewTypeVariant(true);
let expected_ipld = Ipld::List(vec![Ipld::Integer(1), Ipld::Bool(true)]);
assert_roundtrip(&newtype_enum, &expected_ipld);

let tuple_enum = Enum::TupleVariant(true, 1u8);
let expected_ipld = Ipld::List(vec![
Ipld::Integer(2),
Ipld::Bool(true),
Ipld::Integer(1i128),
]);
assert_roundtrip(&tuple_enum, &expected_ipld);
let struct_enum = Enum::StructVariant { x: true, y: 1u8 };
let expected_ipld = Ipld::List(vec![
Ipld::Integer(3),
Ipld::Bool(true),
Ipld::Integer(1i128),
]);
assert_roundtrip(&struct_enum, &expected_ipld);

let unit = ();
let expected_ipld = Ipld::List(vec![]);

assert_roundtrip(&unit, &expected_ipld);

let map: BTreeMap<String, u64> =
BTreeMap::from([("hello".into(), 1u64), ("world!".into(), 7u64)]);
let expected_ipld = Ipld::List(vec![
Ipld::List(vec![Ipld::String("hello".into()), Ipld::Integer(1i128)]),
Ipld::List(vec![Ipld::String("world!".into()), Ipld::Integer(7i128)]),
]);
assert_roundtrip(&map, &expected_ipld);
}

/// Test that deserializing arbitrary bytes are not accidently recognized as
/// CID.
#[test]
fn test_bytes_not_cid() {
let cid = Cid::try_from(
"bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily",
)
.unwrap();

let bytes_not_cid = Ipld::Bytes(cid.to_bytes());
let not_a_cid: Result<Cid, _> = from_ipld(bytes_not_cid);
assert!(not_a_cid.is_err());

// Make sure that a Ipld::Link deserializes correctly though.
let link = Ipld::Link(cid);
let a_cid: Cid = from_ipld(link).unwrap();
assert_eq!(a_cid, cid);
}
}
Loading