ipld_core/
convert.rs

1//! Conversion to and from ipld.
2use alloc::{
3    borrow::ToOwned,
4    boxed::Box,
5    collections::BTreeMap,
6    string::{String, ToString},
7    vec::Vec,
8};
9use core::{any::TypeId, fmt};
10
11use crate::{
12    cid::Cid,
13    ipld::{Ipld, IpldKind},
14};
15
16/// Error used for converting from and into [`crate::ipld::Ipld`].
17#[derive(Clone, Debug)]
18#[non_exhaustive]
19pub enum ConversionError {
20    /// Error when the IPLD kind wasn't the one we expected.
21    WrongIpldKind {
22        /// The expected type.
23        expected: IpldKind,
24        /// The actual type.
25        found: IpldKind,
26    },
27    /// Error when the given Ipld kind cannot be converted into a certain value type.
28    FromIpld {
29        /// The IPLD kind trying to convert from.
30        from: IpldKind,
31        /// The type trying to convert into.
32        into: TypeId,
33    },
34}
35
36impl fmt::Display for ConversionError {
37    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
38        match self {
39            Self::WrongIpldKind { expected, found } => {
40                write!(
41                    formatter,
42                    "kind error: expected {:?} but found {:?}",
43                    expected, found
44                )
45            }
46            Self::FromIpld { from, into } => {
47                write!(
48                    formatter,
49                    "conversion error: cannot convert {:?} into {:?}",
50                    from, into
51                )
52            }
53        }
54    }
55}
56
57#[cfg(feature = "std")]
58impl std::error::Error for ConversionError {}
59
60impl TryFrom<Ipld> for () {
61    type Error = ConversionError;
62
63    fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
64        match ipld {
65            Ipld::Null => Ok(()),
66            _ => Err(ConversionError::WrongIpldKind {
67                expected: IpldKind::Null,
68                found: ipld.kind(),
69            }),
70        }
71    }
72}
73
74macro_rules! derive_try_from_ipld_option {
75    ($enum:ident, $ty:ty) => {
76        impl TryFrom<Ipld> for Option<$ty> {
77            type Error = ConversionError;
78
79            fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
80                match ipld {
81                    Ipld::Null => Ok(None),
82                    Ipld::$enum(value) => Ok(Some(value.try_into().map_err(|_| {
83                        ConversionError::FromIpld {
84                            from: IpldKind::$enum,
85                            into: TypeId::of::<$ty>(),
86                        }
87                    })?)),
88                    _ => Err(ConversionError::WrongIpldKind {
89                        expected: IpldKind::$enum,
90                        found: ipld.kind(),
91                    }),
92                }
93            }
94        }
95    };
96}
97
98macro_rules! derive_try_from_ipld {
99    ($enum:ident, $ty:ty) => {
100        impl TryFrom<Ipld> for $ty {
101            type Error = ConversionError;
102
103            fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
104                match ipld {
105                    Ipld::$enum(value) => {
106                        Ok(value.try_into().map_err(|_| ConversionError::FromIpld {
107                            from: IpldKind::$enum,
108                            into: TypeId::of::<$ty>(),
109                        })?)
110                    }
111
112                    _ => Err(ConversionError::WrongIpldKind {
113                        expected: IpldKind::$enum,
114                        found: ipld.kind(),
115                    }),
116                }
117            }
118        }
119    };
120}
121
122macro_rules! derive_into_ipld_prim {
123    ($enum:ident, $ty:ty, $fn:ident) => {
124        impl From<$ty> for Ipld {
125            fn from(t: $ty) -> Self {
126                Ipld::$enum(t.$fn() as _)
127            }
128        }
129    };
130}
131
132macro_rules! derive_into_ipld {
133    ($enum:ident, $ty:ty, $($fn:ident),*) => {
134        impl From<$ty> for Ipld {
135            fn from(t: $ty) -> Self {
136                Ipld::$enum(t$(.$fn())*)
137            }
138        }
139    };
140}
141
142derive_into_ipld!(Bool, bool, clone);
143derive_into_ipld_prim!(Integer, i8, clone);
144derive_into_ipld_prim!(Integer, i16, clone);
145derive_into_ipld_prim!(Integer, i32, clone);
146derive_into_ipld_prim!(Integer, i64, clone);
147derive_into_ipld_prim!(Integer, i128, clone);
148derive_into_ipld_prim!(Integer, isize, clone);
149derive_into_ipld_prim!(Integer, u8, clone);
150derive_into_ipld_prim!(Integer, u16, clone);
151derive_into_ipld_prim!(Integer, u32, clone);
152derive_into_ipld_prim!(Integer, u64, clone);
153derive_into_ipld_prim!(Integer, usize, clone);
154derive_into_ipld_prim!(Float, f32, clone);
155derive_into_ipld_prim!(Float, f64, clone);
156derive_into_ipld!(String, String, into);
157derive_into_ipld!(String, &str, to_string);
158derive_into_ipld!(Bytes, Box<[u8]>, into_vec);
159derive_into_ipld!(Bytes, Vec<u8>, into);
160derive_into_ipld!(Bytes, &[u8], to_vec);
161derive_into_ipld!(List, Vec<Ipld>, into);
162derive_into_ipld!(Map, BTreeMap<String, Ipld>, to_owned);
163derive_into_ipld!(Link, Cid, clone);
164derive_into_ipld!(Link, &Cid, to_owned);
165
166derive_try_from_ipld!(Bool, bool);
167derive_try_from_ipld!(Integer, i8);
168derive_try_from_ipld!(Integer, i16);
169derive_try_from_ipld!(Integer, i32);
170derive_try_from_ipld!(Integer, i64);
171derive_try_from_ipld!(Integer, i128);
172derive_try_from_ipld!(Integer, isize);
173derive_try_from_ipld!(Integer, u8);
174derive_try_from_ipld!(Integer, u16);
175derive_try_from_ipld!(Integer, u32);
176derive_try_from_ipld!(Integer, u64);
177derive_try_from_ipld!(Integer, u128);
178derive_try_from_ipld!(Integer, usize);
179
180//derive_from_ipld!(Float, f32); // User explicit conversion is prefered. Would implicitly lossily convert from f64.
181
182derive_try_from_ipld!(Float, f64);
183derive_try_from_ipld!(String, String);
184derive_try_from_ipld!(Bytes, Vec<u8>);
185derive_try_from_ipld!(List, Vec<Ipld>);
186derive_try_from_ipld!(Map, BTreeMap<String, Ipld>);
187derive_try_from_ipld!(Link, Cid);
188
189derive_try_from_ipld_option!(Bool, bool);
190derive_try_from_ipld_option!(Integer, i8);
191derive_try_from_ipld_option!(Integer, i16);
192derive_try_from_ipld_option!(Integer, i32);
193derive_try_from_ipld_option!(Integer, i64);
194derive_try_from_ipld_option!(Integer, i128);
195derive_try_from_ipld_option!(Integer, isize);
196derive_try_from_ipld_option!(Integer, u8);
197derive_try_from_ipld_option!(Integer, u16);
198derive_try_from_ipld_option!(Integer, u32);
199derive_try_from_ipld_option!(Integer, u64);
200derive_try_from_ipld_option!(Integer, u128);
201derive_try_from_ipld_option!(Integer, usize);
202
203//derive_from_ipld_option!(Float, f32); // User explicit conversion is prefered. Would implicitly lossily convert from f64.
204
205derive_try_from_ipld_option!(Float, f64);
206derive_try_from_ipld_option!(String, String);
207derive_try_from_ipld_option!(Bytes, Vec<u8>);
208derive_try_from_ipld_option!(List, Vec<Ipld>);
209derive_try_from_ipld_option!(Map, BTreeMap<String, Ipld>);
210derive_try_from_ipld_option!(Link, Cid);
211
212#[cfg(test)]
213mod tests {
214    use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
215
216    use cid::Cid;
217
218    use crate::ipld::Ipld;
219
220    #[test]
221    #[should_panic]
222    fn try_into_wrong_type() {
223        let _boolean: bool = Ipld::Integer(u8::MAX as i128).try_into().unwrap();
224    }
225
226    #[test]
227    #[should_panic]
228    fn try_into_wrong_range() {
229        let int: u128 = Ipld::Integer(-1i128).try_into().unwrap();
230        assert_eq!(int, u128::MIN);
231    }
232
233    #[test]
234    fn try_into_bool() {
235        let boolean: bool = Ipld::Bool(true).try_into().unwrap();
236        assert!(boolean);
237
238        let boolean: Option<bool> = Ipld::Null.try_into().unwrap();
239        assert_eq!(boolean, Option::None)
240    }
241
242    #[test]
243    fn try_into_ints() {
244        let int: u8 = Ipld::Integer(u8::MAX as i128).try_into().unwrap();
245        assert_eq!(int, u8::MAX);
246
247        let int: u16 = Ipld::Integer(u16::MAX as i128).try_into().unwrap();
248        assert_eq!(int, u16::MAX);
249
250        let int: u32 = Ipld::Integer(u32::MAX as i128).try_into().unwrap();
251        assert_eq!(int, u32::MAX);
252
253        let int: u64 = Ipld::Integer(u64::MAX as i128).try_into().unwrap();
254        assert_eq!(int, u64::MAX);
255
256        let int: usize = Ipld::Integer(usize::MAX as i128).try_into().unwrap();
257        assert_eq!(int, usize::MAX);
258
259        let int: u128 = Ipld::Integer(i128::MAX).try_into().unwrap();
260        assert_eq!(int, i128::MAX as u128);
261
262        let int: i8 = Ipld::Integer(i8::MIN as i128).try_into().unwrap();
263        assert_eq!(int, i8::MIN);
264
265        let int: i16 = Ipld::Integer(i16::MIN as i128).try_into().unwrap();
266        assert_eq!(int, i16::MIN);
267
268        let int: i32 = Ipld::Integer(i32::MIN as i128).try_into().unwrap();
269        assert_eq!(int, i32::MIN);
270
271        let int: i64 = Ipld::Integer(i64::MIN as i128).try_into().unwrap();
272        assert_eq!(int, i64::MIN);
273
274        let int: isize = Ipld::Integer(isize::MIN as i128).try_into().unwrap();
275        assert_eq!(int, isize::MIN);
276
277        let int: i128 = Ipld::Integer(i128::MIN).try_into().unwrap();
278        assert_eq!(int, i128::MIN);
279
280        let int: Option<i32> = Ipld::Null.try_into().unwrap();
281        assert_eq!(int, Option::None)
282    }
283
284    #[test]
285    fn try_into_floats() {
286        /* let float: f32 = Ipld::Float(f32::MAX as f64).try_into().unwrap();
287        assert_eq!(float, f32::MAX); */
288
289        let float: f64 = Ipld::Float(f64::MAX).try_into().unwrap();
290        assert_eq!(float, f64::MAX);
291
292        let float: Option<f64> = Ipld::Null.try_into().unwrap();
293        assert_eq!(float, Option::None)
294    }
295
296    #[test]
297    fn try_into_string() {
298        let lyrics: String = "I'm blue babedi babeda".into();
299        let string: String = Ipld::String(lyrics.clone()).try_into().unwrap();
300        assert_eq!(string, lyrics);
301
302        let option: Option<String> = Ipld::Null.try_into().unwrap();
303        assert_eq!(option, Option::None)
304    }
305
306    #[test]
307    fn try_into_vec() {
308        let data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
309        let bytes: Vec<u8> = Ipld::Bytes(data.clone()).try_into().unwrap();
310        assert_eq!(bytes, data);
311
312        let option: Option<Vec<u8>> = Ipld::Null.try_into().unwrap();
313        assert_eq!(option, Option::None)
314    }
315
316    #[test]
317    fn try_into_list() {
318        let ints = vec![Ipld::Integer(0), Ipld::Integer(1), Ipld::Integer(2)];
319        let list: Vec<Ipld> = Ipld::List(ints.clone()).try_into().unwrap();
320        assert_eq!(ints, list);
321
322        let option: Option<Vec<Ipld>> = Ipld::Null.try_into().unwrap();
323        assert_eq!(option, Option::None)
324    }
325
326    #[test]
327    fn try_into_map() {
328        let mut numbs = BTreeMap::new();
329        numbs.insert("zero".into(), Ipld::Integer(0));
330        numbs.insert("one".into(), Ipld::Integer(1));
331        numbs.insert("two".into(), Ipld::Integer(2));
332        let map: BTreeMap<String, Ipld> = Ipld::Map(numbs.clone()).try_into().unwrap();
333        assert_eq!(numbs, map);
334
335        let option: Option<BTreeMap<String, Ipld>> = Ipld::Null.try_into().unwrap();
336        assert_eq!(option, Option::None)
337    }
338
339    #[test]
340    fn try_into_cid() {
341        let cid = Cid::default();
342        let link: Cid = Ipld::Link(cid).try_into().unwrap();
343        assert_eq!(cid, link);
344
345        let option: Option<Cid> = Ipld::Null.try_into().unwrap();
346        assert_eq!(option, Option::None)
347    }
348}