use alloc::{
borrow::ToOwned,
boxed::Box,
collections::BTreeMap,
string::{String, ToString},
vec::Vec,
};
use core::{any::TypeId, fmt};
use crate::{
cid::Cid,
ipld::{Ipld, IpldKind},
};
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ConversionError {
WrongIpldKind {
expected: IpldKind,
found: IpldKind,
},
FromIpld {
from: IpldKind,
into: TypeId,
},
}
impl fmt::Display for ConversionError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::WrongIpldKind { expected, found } => {
write!(
formatter,
"kind error: expected {:?} but found {:?}",
expected, found
)
}
Self::FromIpld { from, into } => {
write!(
formatter,
"conversion error: cannot convert {:?} into {:?}",
from, into
)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ConversionError {}
impl TryFrom<Ipld> for () {
type Error = ConversionError;
fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
match ipld {
Ipld::Null => Ok(()),
_ => Err(ConversionError::WrongIpldKind {
expected: IpldKind::Null,
found: ipld.kind(),
}),
}
}
}
macro_rules! derive_try_from_ipld_option {
($enum:ident, $ty:ty) => {
impl TryFrom<Ipld> for Option<$ty> {
type Error = ConversionError;
fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
match ipld {
Ipld::Null => Ok(None),
Ipld::$enum(value) => Ok(Some(value.try_into().map_err(|_| {
ConversionError::FromIpld {
from: IpldKind::$enum,
into: TypeId::of::<$ty>(),
}
})?)),
_ => Err(ConversionError::WrongIpldKind {
expected: IpldKind::$enum,
found: ipld.kind(),
}),
}
}
}
};
}
macro_rules! derive_try_from_ipld {
($enum:ident, $ty:ty) => {
impl TryFrom<Ipld> for $ty {
type Error = ConversionError;
fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
match ipld {
Ipld::$enum(value) => {
Ok(value.try_into().map_err(|_| ConversionError::FromIpld {
from: IpldKind::$enum,
into: TypeId::of::<$ty>(),
})?)
}
_ => Err(ConversionError::WrongIpldKind {
expected: IpldKind::$enum,
found: ipld.kind(),
}),
}
}
}
};
}
macro_rules! derive_into_ipld_prim {
($enum:ident, $ty:ty, $fn:ident) => {
impl From<$ty> for Ipld {
fn from(t: $ty) -> Self {
Ipld::$enum(t.$fn() as _)
}
}
};
}
macro_rules! derive_into_ipld {
($enum:ident, $ty:ty, $($fn:ident),*) => {
impl From<$ty> for Ipld {
fn from(t: $ty) -> Self {
Ipld::$enum(t$(.$fn())*)
}
}
};
}
derive_into_ipld!(Bool, bool, clone);
derive_into_ipld_prim!(Integer, i8, clone);
derive_into_ipld_prim!(Integer, i16, clone);
derive_into_ipld_prim!(Integer, i32, clone);
derive_into_ipld_prim!(Integer, i64, clone);
derive_into_ipld_prim!(Integer, i128, clone);
derive_into_ipld_prim!(Integer, isize, clone);
derive_into_ipld_prim!(Integer, u8, clone);
derive_into_ipld_prim!(Integer, u16, clone);
derive_into_ipld_prim!(Integer, u32, clone);
derive_into_ipld_prim!(Integer, u64, clone);
derive_into_ipld_prim!(Integer, usize, clone);
derive_into_ipld_prim!(Float, f32, clone);
derive_into_ipld_prim!(Float, f64, clone);
derive_into_ipld!(String, String, into);
derive_into_ipld!(String, &str, to_string);
derive_into_ipld!(Bytes, Box<[u8]>, into_vec);
derive_into_ipld!(Bytes, Vec<u8>, into);
derive_into_ipld!(Bytes, &[u8], to_vec);
derive_into_ipld!(List, Vec<Ipld>, into);
derive_into_ipld!(Map, BTreeMap<String, Ipld>, to_owned);
derive_into_ipld!(Link, Cid, clone);
derive_into_ipld!(Link, &Cid, to_owned);
derive_try_from_ipld!(Bool, bool);
derive_try_from_ipld!(Integer, i8);
derive_try_from_ipld!(Integer, i16);
derive_try_from_ipld!(Integer, i32);
derive_try_from_ipld!(Integer, i64);
derive_try_from_ipld!(Integer, i128);
derive_try_from_ipld!(Integer, isize);
derive_try_from_ipld!(Integer, u8);
derive_try_from_ipld!(Integer, u16);
derive_try_from_ipld!(Integer, u32);
derive_try_from_ipld!(Integer, u64);
derive_try_from_ipld!(Integer, u128);
derive_try_from_ipld!(Integer, usize);
derive_try_from_ipld!(Float, f64);
derive_try_from_ipld!(String, String);
derive_try_from_ipld!(Bytes, Vec<u8>);
derive_try_from_ipld!(List, Vec<Ipld>);
derive_try_from_ipld!(Map, BTreeMap<String, Ipld>);
derive_try_from_ipld!(Link, Cid);
derive_try_from_ipld_option!(Bool, bool);
derive_try_from_ipld_option!(Integer, i8);
derive_try_from_ipld_option!(Integer, i16);
derive_try_from_ipld_option!(Integer, i32);
derive_try_from_ipld_option!(Integer, i64);
derive_try_from_ipld_option!(Integer, i128);
derive_try_from_ipld_option!(Integer, isize);
derive_try_from_ipld_option!(Integer, u8);
derive_try_from_ipld_option!(Integer, u16);
derive_try_from_ipld_option!(Integer, u32);
derive_try_from_ipld_option!(Integer, u64);
derive_try_from_ipld_option!(Integer, u128);
derive_try_from_ipld_option!(Integer, usize);
derive_try_from_ipld_option!(Float, f64);
derive_try_from_ipld_option!(String, String);
derive_try_from_ipld_option!(Bytes, Vec<u8>);
derive_try_from_ipld_option!(List, Vec<Ipld>);
derive_try_from_ipld_option!(Map, BTreeMap<String, Ipld>);
derive_try_from_ipld_option!(Link, Cid);
#[cfg(test)]
mod tests {
use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
use cid::Cid;
use crate::ipld::Ipld;
#[test]
#[should_panic]
fn try_into_wrong_type() {
let _boolean: bool = Ipld::Integer(u8::MAX as i128).try_into().unwrap();
}
#[test]
#[should_panic]
fn try_into_wrong_range() {
let int: u128 = Ipld::Integer(-1i128).try_into().unwrap();
assert_eq!(int, u128::MIN);
}
#[test]
fn try_into_bool() {
let boolean: bool = Ipld::Bool(true).try_into().unwrap();
assert!(boolean);
let boolean: Option<bool> = Ipld::Null.try_into().unwrap();
assert_eq!(boolean, Option::None)
}
#[test]
fn try_into_ints() {
let int: u8 = Ipld::Integer(u8::MAX as i128).try_into().unwrap();
assert_eq!(int, u8::MAX);
let int: u16 = Ipld::Integer(u16::MAX as i128).try_into().unwrap();
assert_eq!(int, u16::MAX);
let int: u32 = Ipld::Integer(u32::MAX as i128).try_into().unwrap();
assert_eq!(int, u32::MAX);
let int: u64 = Ipld::Integer(u64::MAX as i128).try_into().unwrap();
assert_eq!(int, u64::MAX);
let int: usize = Ipld::Integer(usize::MAX as i128).try_into().unwrap();
assert_eq!(int, usize::MAX);
let int: u128 = Ipld::Integer(i128::MAX).try_into().unwrap();
assert_eq!(int, i128::MAX as u128);
let int: i8 = Ipld::Integer(i8::MIN as i128).try_into().unwrap();
assert_eq!(int, i8::MIN);
let int: i16 = Ipld::Integer(i16::MIN as i128).try_into().unwrap();
assert_eq!(int, i16::MIN);
let int: i32 = Ipld::Integer(i32::MIN as i128).try_into().unwrap();
assert_eq!(int, i32::MIN);
let int: i64 = Ipld::Integer(i64::MIN as i128).try_into().unwrap();
assert_eq!(int, i64::MIN);
let int: isize = Ipld::Integer(isize::MIN as i128).try_into().unwrap();
assert_eq!(int, isize::MIN);
let int: i128 = Ipld::Integer(i128::MIN).try_into().unwrap();
assert_eq!(int, i128::MIN);
let int: Option<i32> = Ipld::Null.try_into().unwrap();
assert_eq!(int, Option::None)
}
#[test]
fn try_into_floats() {
let float: f64 = Ipld::Float(f64::MAX).try_into().unwrap();
assert_eq!(float, f64::MAX);
let float: Option<f64> = Ipld::Null.try_into().unwrap();
assert_eq!(float, Option::None)
}
#[test]
fn try_into_string() {
let lyrics: String = "I'm blue babedi babeda".into();
let string: String = Ipld::String(lyrics.clone()).try_into().unwrap();
assert_eq!(string, lyrics);
let option: Option<String> = Ipld::Null.try_into().unwrap();
assert_eq!(option, Option::None)
}
#[test]
fn try_into_vec() {
let data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let bytes: Vec<u8> = Ipld::Bytes(data.clone()).try_into().unwrap();
assert_eq!(bytes, data);
let option: Option<Vec<u8>> = Ipld::Null.try_into().unwrap();
assert_eq!(option, Option::None)
}
#[test]
fn try_into_list() {
let ints = vec![Ipld::Integer(0), Ipld::Integer(1), Ipld::Integer(2)];
let list: Vec<Ipld> = Ipld::List(ints.clone()).try_into().unwrap();
assert_eq!(ints, list);
let option: Option<Vec<Ipld>> = Ipld::Null.try_into().unwrap();
assert_eq!(option, Option::None)
}
#[test]
fn try_into_map() {
let mut numbs = BTreeMap::new();
numbs.insert("zero".into(), Ipld::Integer(0));
numbs.insert("one".into(), Ipld::Integer(1));
numbs.insert("two".into(), Ipld::Integer(2));
let map: BTreeMap<String, Ipld> = Ipld::Map(numbs.clone()).try_into().unwrap();
assert_eq!(numbs, map);
let option: Option<BTreeMap<String, Ipld>> = Ipld::Null.try_into().unwrap();
assert_eq!(option, Option::None)
}
#[test]
fn try_into_cid() {
let cid = Cid::default();
let link: Cid = Ipld::Link(cid).try_into().unwrap();
assert_eq!(cid, link);
let option: Option<Cid> = Ipld::Null.try_into().unwrap();
assert_eq!(option, Option::None)
}
}