ipld_nostd/ipld/serde/
mod.rs

1//! Serde (de)serialization for [`crate::ipld::Ipld`].
2//!
3//! This implementation enables Serde to serialize to/deserialize from
4//! [`crate::ipld::Ipld`] values. The `Ipld` enum is similar to the `Value` enum
5//! in `serde_json` or `serde_cbor`.
6mod de;
7mod extract_links;
8mod ser;
9
10use {
11	alloc::string::{String, ToString},
12	core::fmt,
13};
14pub use {
15	de::from_ipld,
16	extract_links::ExtractLinks,
17	ser::{to_ipld, Serializer},
18};
19
20/// Error during Serde operations.
21#[derive(Clone, Debug)]
22pub struct SerdeError(String);
23
24impl fmt::Display for SerdeError {
25	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26		write!(f, "serde error: {}", self.0)
27	}
28}
29
30impl serde::de::Error for SerdeError {
31	fn custom<T: fmt::Display>(message: T) -> Self {
32		Self(message.to_string())
33	}
34}
35
36impl serde::ser::Error for SerdeError {
37	fn custom<T: fmt::Display>(message: T) -> Self {
38		Self(message.to_string())
39	}
40}
41
42impl serde::ser::StdError for SerdeError {}
43
44#[cfg(test)]
45mod tests {
46	use {
47		super::super::{
48			serde::{from_ipld, to_ipld},
49			Ipld,
50		},
51		crate::cid::{serde::CID_SERDE_PRIVATE_IDENTIFIER, Cid},
52		::alloc::{collections::BTreeMap, string::String, vec::Vec, *},
53		core::fmt,
54		serde::{de::DeserializeOwned, Serialize},
55		serde_derive::Deserialize,
56		serde_test::{assert_tokens, Token},
57	};
58
59	/// Utility for testing (de)serialization of [`Ipld`].
60	///
61	/// Checks if `data` and `ipld` match if they are encoded into each other.
62	fn assert_roundtrip<T>(data: &T, ipld: &Ipld)
63	where
64		T: Serialize + DeserializeOwned + PartialEq + fmt::Debug,
65	{
66		let encoded: Ipld = to_ipld(data).unwrap();
67		assert_eq!(&encoded, ipld);
68		let decoded: T = from_ipld(ipld.clone()).unwrap();
69		assert_eq!(&decoded, data);
70	}
71
72	#[derive(Debug, Deserialize, PartialEq, Serialize)]
73	struct Person {
74		name: String,
75		age: u8,
76		hobbies: Vec<String>,
77		is_cool: bool,
78		link: Cid,
79	}
80
81	impl Default for Person {
82		fn default() -> Self {
83			Self {
84				name: "Hello World!".into(),
85				age: 52,
86				hobbies: vec!["geography".into(), "programming".into()],
87				is_cool: true,
88				link: Cid::try_from(
89					"bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily",
90				)
91				.unwrap(),
92			}
93		}
94	}
95
96	#[test]
97	fn test_tokens() {
98		let person = Person::default();
99
100		assert_tokens(&person, &[
101			Token::Struct {
102				name: "Person",
103				len: 5,
104			},
105			Token::Str("name"),
106			Token::Str("Hello World!"),
107			Token::Str("age"),
108			Token::U8(52),
109			Token::Str("hobbies"),
110			Token::Seq { len: Some(2) },
111			Token::Str("geography"),
112			Token::Str("programming"),
113			Token::SeqEnd,
114			Token::Str("is_cool"),
115			Token::Bool(true),
116			Token::Str("link"),
117			Token::NewtypeStruct {
118				name: CID_SERDE_PRIVATE_IDENTIFIER,
119			},
120			Token::Bytes(&[
121				0x01, 0x71, 0x12, 0x20, 0x35, 0x4d, 0x45, 0x5f, 0xf3, 0xa6, 0x41, 0xb8,
122				0xca, 0xc2, 0x5c, 0x38, 0xa7, 0x7e, 0x64, 0xaa, 0x73, 0x5d, 0xc8, 0xa4,
123				0x89, 0x66, 0xa6, 0xf, 0x1a, 0x78, 0xca, 0xa1, 0x72, 0xa4, 0x88, 0x5e,
124			]),
125			Token::StructEnd,
126		]);
127	}
128
129	/// Test if converting to a struct from [`crate::ipld::Ipld`] and back works.
130	#[test]
131	fn test_ipld() {
132		let person = Person::default();
133
134		let expected_ipld = Ipld::Map({
135			BTreeMap::from([
136				("name".into(), Ipld::String("Hello World!".into())),
137				("age".into(), Ipld::Integer(52)),
138				(
139					"hobbies".into(),
140					Ipld::List(vec![
141						Ipld::String("geography".into()),
142						Ipld::String("programming".into()),
143					]),
144				),
145				("is_cool".into(), Ipld::Bool(true)),
146				("link".into(), Ipld::Link(person.link)),
147			])
148		});
149
150		assert_roundtrip(&person, &expected_ipld);
151	}
152
153	/// Test that deserializing arbitrary bytes are not accidentally recognized as
154	/// CID.
155	#[test]
156	fn test_bytes_not_cid() {
157		let cid = Cid::try_from(
158			"bafyreibvjvcv745gig4mvqs4hctx4zfkono4rjejm2ta6gtyzkqxfjeily",
159		)
160		.unwrap();
161
162		let bytes_not_cid = Ipld::Bytes(cid.to_bytes());
163		let not_a_cid: Result<Cid, _> = from_ipld(bytes_not_cid);
164		assert!(not_a_cid.is_err());
165
166		// Make sure that a Ipld::Link deserializes correctly though.
167		let link = Ipld::Link(cid);
168		let a_cid: Cid = from_ipld(link).unwrap();
169		assert_eq!(a_cid, cid);
170	}
171}