use std::{collections::HashMap, convert::TryFrom}; use crate::TryFromKdlNodeValueError; #[derive(Debug, Clone, PartialEq)] pub struct KdlNode { pub name: String, pub values: Vec, pub properties: HashMap, pub children: Vec, } #[derive(Debug, Clone, PartialEq)] pub enum KdlValue { Int(i64), Float(f64), String(String), Boolean(bool), Null, } // Support conversions from base types into KdlNodeValue impl From for KdlValue { fn from(v: i64) -> Self { Self::Int(v) } } impl From for KdlValue { fn from(v: f64) -> Self { Self::Float(v) } } impl From for KdlValue { fn from(v: String) -> Self { Self::String(v) } } impl From<&str> for KdlValue { fn from(v: &str) -> Self { Self::String(v.to_owned()) } } impl From for KdlValue { fn from(v: bool) -> Self { Self::Boolean(v) } } impl From> for KdlValue where T: Into, { fn from(v: Option) -> Self { v.map_or(KdlValue::Null, |v| v.into()) } } // Support reverse conversions using TryFrom // Synthesizes a TryFrom impl for both the base type and an Option variant. // // We need the Option variant because we can't write a blanket impl due to the existing // impl TryFrom for T where U: Into // even though KdlNodeValue does not implement Into>. macro_rules! impl_try_from { (<$($lt:lifetime)?> $source:ty => $typ:ty, $($good:pat => $value:expr),+; $($bad:ident),+) => { impl<$($lt)?> TryFrom<$source> for $typ { type Error = TryFromKdlNodeValueError; fn try_from(value: $source) -> Result { match value { $( $good => Ok($value), )+ $( KdlValue::$bad(_) => Err(TryFromKdlNodeValueError { expected: stringify!($typ), variant: stringify!($bad) }), )+ KdlValue::Null => Err(TryFromKdlNodeValueError { expected: stringify!($typ), variant: "Null" }), } } } impl<$($lt)?> TryFrom<$source> for Option<$typ> { type Error = TryFromKdlNodeValueError; fn try_from(value: $source) -> Result { match value { $( $good => Ok(Some($value)), )+ $( KdlValue::$bad(_) => Err(TryFromKdlNodeValueError { expected: concat!("Option::<", stringify!($typ), ">"), variant: stringify!($bad) }), )+ KdlValue::Null => Ok(None), } } } }; (& $($lt:lifetime)?, $typ:ty, $($tt:tt)*) => { impl_try_from!(<$($lt)?> & $($lt)? KdlValue => $typ, $($tt)*); }; ($typ:ty, $($tt:tt)*) => { impl_try_from!(<> KdlValue => $typ, $($tt)*); }; } impl_try_from!(i64, KdlValue::Int(v) => v; Float, String, Boolean); impl_try_from!(&, i64, KdlValue::Int(v) => *v; Float, String, Boolean); impl_try_from!(f64, KdlValue::Float(v) => v; Int, String, Boolean); impl_try_from!(&, f64, KdlValue::Float(v) => *v; Int, String, Boolean); impl_try_from!(String, KdlValue::String(v) => v; Int, Float, Boolean); impl_try_from!(&'a, &'a str, KdlValue::String(v) => &v[..]; Int, Float, Boolean); impl_try_from!(bool, KdlValue::Boolean(v) => v; Int, Float, String); impl_try_from!(&, bool, KdlValue::Boolean(v) => *v; Int, Float, String); #[cfg(test)] mod tests { use super::*; #[test] fn from() { assert_eq!(KdlValue::from(1), KdlValue::Int(1)); assert_eq!(KdlValue::from(1.5), KdlValue::Float(1.5)); assert_eq!( KdlValue::from("foo".to_owned()), KdlValue::String("foo".to_owned()) ); assert_eq!(KdlValue::from("bar"), KdlValue::String("bar".to_owned())); assert_eq!(KdlValue::from(true), KdlValue::Boolean(true)); assert_eq!(KdlValue::from(None::), KdlValue::Null); assert_eq!(KdlValue::from(Some(1)), KdlValue::Int(1)); } #[test] fn try_from_success() { assert_eq!(i64::try_from(KdlValue::Int(1)), Ok(1)); assert_eq!(i64::try_from(&KdlValue::Int(1)), Ok(1)); assert_eq!(f64::try_from(KdlValue::Float(1.5)), Ok(1.5)); assert_eq!(f64::try_from(&KdlValue::Float(1.5)), Ok(1.5)); assert_eq!( String::try_from(KdlValue::String("foo".to_owned())), Ok("foo".to_owned()) ); assert_eq!( <&str as TryFrom<_>>::try_from(&KdlValue::String("foo".to_owned())), Ok("foo") ); assert_eq!(bool::try_from(KdlValue::Boolean(true)), Ok(true)); assert_eq!(bool::try_from(&KdlValue::Boolean(true)), Ok(true)); assert_eq!(Option::::try_from(KdlValue::Int(1)), Ok(Some(1))); assert_eq!(Option::::try_from(KdlValue::Null), Ok(None)); } #[test] fn try_from_failure() { // We don't expose the internal format of the error type, so let's just test the message // for a couple of cases. assert_eq!( format!("{}", i64::try_from(KdlValue::Float(1.5)).unwrap_err()), "Failed to convert from KdlNodeValue::Float to i64." ); assert_eq!( format!( "{}", Option::::try_from(KdlValue::Float(1.5)).unwrap_err() ), "Failed to convert from KdlNodeValue::Float to Option::." ); } }