ipld_nostd/ipld/
mod.rs

1pub mod codec;
2pub mod convert;
3
4pub mod serde;
5
6mod macros;
7
8// This is a hack to get those types working in the `ipld!` macro with and
9// without `no_std`. The idea is from
10// https://stackoverflow.com/questions/71675411/refer-to-an-extern-crate-in-macro-expansion/71675639#71675639
11#[doc(hidden)]
12pub mod __private_do_not_use {
13	pub use alloc::{collections::BTreeMap, vec};
14}
15
16use {
17	crate::cid::Cid,
18	::alloc::{
19		borrow::ToOwned,
20		boxed::Box,
21		collections::BTreeMap,
22		string::{String, ToString},
23		vec::Vec,
24		*,
25	},
26	core::fmt,
27};
28
29/// Error when accessing IPLD List or Map elements.
30#[derive(Clone, Debug)]
31#[non_exhaustive]
32pub enum IndexError {
33	/// Error when key cannot be parsed into an integer.
34	ParseInteger(String),
35	/// Error when the input wasn't an IPLD List or Map.
36	WrongKind(IpldKind),
37}
38
39impl fmt::Display for IndexError {
40	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41		match self {
42			Self::ParseInteger(key) => {
43				write!(f, "cannot parse key into integer: {}", key)
44			}
45			Self::WrongKind(kind) => {
46				write!(f, "expected IPLD List or Map but found: {:?}", kind)
47			}
48		}
49	}
50}
51
52/// Ipld
53#[derive(Clone)]
54pub enum Ipld {
55	/// Represents the absence of a value or the value undefined.
56	Null,
57	/// Represents a boolean value.
58	Bool(bool),
59	/// Represents an integer.
60	Integer(i128),
61	/// Represents a floating point value.
62	Float(f64),
63	/// Represents an UTF-8 string.
64	String(String),
65	/// Represents a sequence of bytes.
66	Bytes(Vec<u8>),
67	/// Represents a list.
68	List(Vec<Ipld>),
69	/// Represents a map of strings.
70	Map(BTreeMap<String, Ipld>),
71	/// Represents a map of integers.
72	Link(Cid),
73}
74
75impl fmt::Debug for Ipld {
76	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77		if f.alternate() {
78			match self {
79				Self::Null => write!(f, "Null"),
80				Self::Bool(b) => write!(f, "Bool({:?})", b),
81				Self::Integer(i) => write!(f, "Integer({:?})", i),
82				Self::Float(i) => write!(f, "Float({:?})", i),
83				Self::String(s) => write!(f, "String({:?})", s),
84				Self::Bytes(b) => write!(f, "Bytes({:?})", b),
85				Self::List(l) => write!(f, "List({:#?})", l),
86				Self::Map(m) => write!(f, "Map({:#?})", m),
87				Self::Link(cid) => write!(f, "Link({})", cid),
88			}
89		} else {
90			match self {
91				Self::Null => write!(f, "null"),
92				Self::Bool(b) => write!(f, "{:?}", b),
93				Self::Integer(i) => write!(f, "{:?}", i),
94				Self::Float(i) => write!(f, "{:?}", i),
95				Self::String(s) => write!(f, "{:?}", s),
96				Self::Bytes(b) => write!(f, "{:?}", b),
97				Self::List(l) => write!(f, "{:?}", l),
98				Self::Map(m) => write!(f, "{:?}", m),
99				Self::Link(cid) => write!(f, "{}", cid),
100			}
101		}
102	}
103}
104
105/// NaN floats are forbidden in the IPLD Data Model, but we do not enforce it.
106/// So in case such a value is introduced accidentally, make sure that it still
107/// compares as equal. This allows us to implement `Eq` for `Ipld`.
108impl PartialEq for Ipld {
109	fn eq(&self, other: &Self) -> bool {
110		match (self, other) {
111			(Self::Null, Self::Null) => true,
112			(Self::Bool(self_value), Self::Bool(other_value)) => {
113				self_value == other_value
114			}
115			(Self::Integer(self_value), Self::Integer(other_value)) => {
116				self_value == other_value
117			}
118			(Self::Float(self_value), Self::Float(other_value)) => {
119				// Treat two NaNs as being equal.
120				self_value == other_value || self_value.is_nan() && other_value.is_nan()
121			}
122			(Self::String(self_value), Self::String(other_value)) => {
123				self_value == other_value
124			}
125			(Self::Bytes(self_value), Self::Bytes(other_value)) => {
126				self_value == other_value
127			}
128			(Self::List(self_value), Self::List(other_value)) => {
129				self_value == other_value
130			}
131			(Self::Map(self_value), Self::Map(other_value)) => {
132				self_value == other_value
133			}
134			(Self::Link(self_value), Self::Link(other_value)) => {
135				self_value == other_value
136			}
137			_ => false,
138		}
139	}
140}
141
142impl Eq for Ipld {}
143
144/// IPLD Kind information without the actual value.
145///
146/// Sometimes it's useful to know the kind of an Ipld object without the actual
147/// value, e.g. for error reporting. Those kinds can be a unity-only enum.
148#[derive(Clone, Debug)]
149pub enum IpldKind {
150	/// Null type.
151	Null,
152	/// Boolean type.
153	Bool,
154	/// Integer type.
155	Integer,
156	/// Float type.
157	Float,
158	/// String type.
159	String,
160	/// Bytes type.
161	Bytes,
162	/// List type.
163	List,
164	/// Map type.
165	Map,
166	/// Link type.
167	Link,
168}
169
170/// An index into IPLD.
171///
172/// It's used for accessing IPLD List and Map elements.
173pub enum IpldIndex<'a> {
174	/// An index into an ipld list.
175	List(usize),
176	/// An owned index into an ipld map.
177	Map(String),
178	/// An index into an ipld map.
179	MapRef(&'a str),
180}
181
182impl<'a> From<usize> for IpldIndex<'a> {
183	fn from(index: usize) -> Self {
184		Self::List(index)
185	}
186}
187
188impl<'a> From<String> for IpldIndex<'a> {
189	fn from(key: String) -> Self {
190		Self::Map(key)
191	}
192}
193
194impl<'a> From<&'a str> for IpldIndex<'a> {
195	fn from(key: &'a str) -> Self {
196		Self::MapRef(key)
197	}
198}
199
200impl<'a> TryFrom<IpldIndex<'a>> for usize {
201	type Error = IndexError;
202
203	fn try_from(index: IpldIndex<'a>) -> Result<Self, Self::Error> {
204		let parsed = match index {
205			IpldIndex::List(i) => i,
206			IpldIndex::Map(ref key) => key
207				.parse()
208				.map_err(|_| IndexError::ParseInteger(key.to_string()))?,
209			IpldIndex::MapRef(key) => key
210				.parse()
211				.map_err(|_| IndexError::ParseInteger(key.to_string()))?,
212		};
213		Ok(parsed)
214	}
215}
216
217impl<'a> From<IpldIndex<'a>> for String {
218	fn from(index: IpldIndex<'a>) -> Self {
219		match index {
220			IpldIndex::Map(ref key) => key.to_string(),
221			IpldIndex::MapRef(key) => key.to_string(),
222			IpldIndex::List(i) => i.to_string(),
223		}
224	}
225}
226
227impl Ipld {
228	/// Convert from an [`Ipld`] object into its kind without any associated
229	/// values.
230	///
231	/// This is intentionally not implemented via `From<Ipld>` to prevent
232	/// accidental conversions by making it more explicit.
233	pub fn kind(&self) -> IpldKind {
234		match self {
235			Ipld::Null => IpldKind::Null,
236			Ipld::Bool(_) => IpldKind::Bool,
237			Ipld::Integer(_) => IpldKind::Integer,
238			Ipld::Float(_) => IpldKind::Float,
239			Ipld::String(_) => IpldKind::String,
240			Ipld::Bytes(_) => IpldKind::Bytes,
241			Ipld::List(_) => IpldKind::List,
242			Ipld::Map(_) => IpldKind::Map,
243			Ipld::Link(_) => IpldKind::Link,
244		}
245	}
246
247	/// Destructs an ipld list or map
248	pub fn take<'a, T: Into<IpldIndex<'a>>>(
249		mut self,
250		index: T,
251	) -> Result<Option<Self>, IndexError> {
252		let index = index.into();
253		match &mut self {
254			Ipld::List(ref mut list) => {
255				let parsed_index = usize::try_from(index)?;
256				if parsed_index < list.len() {
257					Ok(Some(list.swap_remove(parsed_index)))
258				} else {
259					Ok(None)
260				}
261			}
262			Ipld::Map(ref mut map) => {
263				let key = String::from(index);
264				Ok(map.remove(&key))
265			}
266			other => Err(IndexError::WrongKind(other.kind())),
267		}
268	}
269
270	/// Indexes into an ipld list or map.
271	pub fn get<'a, T: Into<IpldIndex<'a>>>(
272		&self,
273		index: T,
274	) -> Result<Option<&Self>, IndexError> {
275		let index = index.into();
276		match self {
277			Ipld::List(list) => {
278				let parsed_index = usize::try_from(index)?;
279				Ok(list.get(parsed_index))
280			}
281			Ipld::Map(map) => {
282				let key = String::from(index);
283				Ok(map.get(&key))
284			}
285			other => Err(IndexError::WrongKind(other.kind())),
286		}
287	}
288
289	/// Returns an iterator.
290	pub fn iter(&self) -> IpldIter<'_> {
291		IpldIter {
292			stack: vec![Box::new(vec![self].into_iter())],
293		}
294	}
295
296	/// Returns the references to other blocks.
297	pub fn references<E: Extend<Cid>>(&self, set: &mut E) {
298		for ipld in self.iter() {
299			if let Ipld::Link(cid) = ipld {
300				set.extend(core::iter::once(cid.to_owned()));
301			}
302		}
303	}
304}
305
306/// Ipld iterator.
307pub struct IpldIter<'a> {
308	stack: Vec<Box<dyn Iterator<Item = &'a Ipld> + 'a>>,
309}
310
311impl<'a> Iterator for IpldIter<'a> {
312	type Item = &'a Ipld;
313
314	fn next(&mut self) -> Option<Self::Item> {
315		loop {
316			if let Some(iter) = self.stack.last_mut() {
317				if let Some(ipld) = iter.next() {
318					match ipld {
319						Ipld::List(list) => {
320							self.stack.push(Box::new(list.iter()));
321						}
322						Ipld::Map(map) => {
323							self.stack.push(Box::new(map.values()));
324						}
325						_ => {}
326					}
327					return Some(ipld);
328				} else {
329					self.stack.pop();
330				}
331			} else {
332				return None;
333			}
334		}
335	}
336}
337
338#[cfg(test)]
339mod tests {
340	use super::*;
341
342	#[test]
343	fn test_ipld_bool_from() {
344		assert_eq!(Ipld::Bool(true), Ipld::from(true));
345		assert_eq!(Ipld::Bool(false), Ipld::from(false));
346	}
347
348	#[test]
349	fn test_ipld_integer_from() {
350		assert_eq!(Ipld::Integer(1), Ipld::from(1i8));
351		assert_eq!(Ipld::Integer(1), Ipld::from(1i16));
352		assert_eq!(Ipld::Integer(1), Ipld::from(1i32));
353		assert_eq!(Ipld::Integer(1), Ipld::from(1i64));
354		assert_eq!(Ipld::Integer(1), Ipld::from(1i128));
355
356		// assert_eq!(Ipld::Integer(1), 1u8.to_ipld().to_owned());
357		assert_eq!(Ipld::Integer(1), Ipld::from(1u16));
358		assert_eq!(Ipld::Integer(1), Ipld::from(1u32));
359		assert_eq!(Ipld::Integer(1), Ipld::from(1u64));
360	}
361
362	#[test]
363	fn test_ipld_float_from() {
364		assert_eq!(Ipld::Float(1.0), Ipld::from(1.0f32));
365		assert_eq!(Ipld::Float(1.0), Ipld::from(1.0f64));
366	}
367
368	#[test]
369	fn test_ipld_string_from() {
370		assert_eq!(Ipld::String("a string".into()), Ipld::from("a string"));
371		assert_eq!(
372			Ipld::String("a string".into()),
373			Ipld::from("a string".to_string())
374		);
375	}
376
377	#[test]
378	fn test_ipld_bytes_from() {
379		assert_eq!(
380			Ipld::Bytes(vec![0, 1, 2, 3]),
381			Ipld::from(&[0u8, 1u8, 2u8, 3u8][..])
382		);
383		assert_eq!(
384			Ipld::Bytes(vec![0, 1, 2, 3]),
385			Ipld::from(vec![0u8, 1u8, 2u8, 3u8])
386		);
387	}
388
389	#[test]
390	fn test_ipld_link_from() {
391		let cid = Cid::try_from(
392			"bafkreie74tgmnxqwojhtumgh5dzfj46gi4mynlfr7dmm7duwzyvnpw7h7m",
393		)
394		.unwrap();
395		assert_eq!(Ipld::Link(cid), Ipld::from(cid));
396	}
397
398	#[test]
399	fn test_take() {
400		let ipld =
401			Ipld::List(vec![Ipld::Integer(0), Ipld::Integer(1), Ipld::Integer(2)]);
402		assert_eq!(ipld.clone().take(0).unwrap(), Some(Ipld::Integer(0)));
403		assert_eq!(ipld.clone().take(1).unwrap(), Some(Ipld::Integer(1)));
404		assert_eq!(ipld.take(2).unwrap(), Some(Ipld::Integer(2)));
405
406		let mut map = BTreeMap::new();
407		map.insert("a".to_string(), Ipld::Integer(0));
408		map.insert("b".to_string(), Ipld::Integer(1));
409		map.insert("c".to_string(), Ipld::Integer(2));
410		let ipld = Ipld::Map(map);
411		assert_eq!(ipld.take("a").unwrap(), Some(Ipld::Integer(0)));
412	}
413
414	#[test]
415	fn test_get() {
416		let ipld =
417			Ipld::List(vec![Ipld::Integer(0), Ipld::Integer(1), Ipld::Integer(2)]);
418		assert_eq!(ipld.get(0).unwrap(), Some(&Ipld::Integer(0)));
419		assert_eq!(ipld.get(1).unwrap(), Some(&Ipld::Integer(1)));
420		assert_eq!(ipld.get(2).unwrap(), Some(&Ipld::Integer(2)));
421
422		let mut map = BTreeMap::new();
423		map.insert("a".to_string(), Ipld::Integer(0));
424		map.insert("b".to_string(), Ipld::Integer(1));
425		map.insert("c".to_string(), Ipld::Integer(2));
426		let ipld = Ipld::Map(map);
427		assert_eq!(ipld.get("a").unwrap(), Some(&Ipld::Integer(0)));
428	}
429
430	// NaN floats are forbidden in the IPLD Data Model, but still make sure they
431	// are treated as equal in case they accidentally end up there.
432	#[test]
433	fn test_partial_eq_nan() {
434		let invalid_ipld = Ipld::Float(f64::NAN);
435		assert_eq!(invalid_ipld, invalid_ipld);
436	}
437}