use std::borrow::Cow; use anyhow::{Result, anyhow, bail}; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use crate::{Id, SStr, data::{DataAny, DataKey}, path::PathBufDyn, strand::{IntoStrand, StrandBuf}, url::{Url, UrlBuf, UrlCow}}; // --- Data #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(untagged)] pub enum Data { #[default] Nil, Boolean(bool), Integer(i64), Number(f64), String(SStr), List(Vec), Dict(HashMap), Id(Id), #[serde(skip_deserializing)] Url(UrlBuf), #[serde(skip)] Path(PathBufDyn), #[serde(skip)] Bytes(Vec), #[serde(skip)] Any(Box), } impl From<()> for Data { fn from(_: ()) -> Self { Self::Nil } } impl From for Data { fn from(value: bool) -> Self { Self::Boolean(value) } } impl From for Data { fn from(value: i32) -> Self { Self::Integer(value as i64) } } impl From for Data { fn from(value: i64) -> Self { Self::Integer(value) } } impl From for Data { fn from(value: f64) -> Self { Self::Number(value) } } impl From for Data { fn from(value: usize) -> Self { Self::Id(value.into()) } } impl From for Data { fn from(value: String) -> Self { Self::String(Cow::Owned(value)) } } impl From for Data { fn from(value: SStr) -> Self { Self::String(value) } } impl From for Data { fn from(value: Id) -> Self { Self::Id(value) } } impl From> for Data { fn from(value: Url) -> Self { Self::Url(value.into()) } } impl From for Data { fn from(value: UrlBuf) -> Self { Self::Url(value) } } impl From<&UrlBuf> for Data { fn from(value: &UrlBuf) -> Self { Self::Url(value.clone()) } } impl From<&str> for Data { fn from(value: &str) -> Self { Self::String(Cow::Owned(value.to_owned())) } } impl FromIterator for Data where T: Into, { fn from_iter>(iter: I) -> Self { Self::List(iter.into_iter().map(Into::into).collect()) } } impl TryFrom<&Data> for bool { type Error = anyhow::Error; fn try_from(value: &Data) -> Result { match value { Data::Boolean(b) => Ok(*b), Data::String(s) if s == "no" => Ok(false), Data::String(s) if s == "yes" => Ok(true), _ => bail!("not a boolean"), } } } impl<'a> TryFrom<&'a Data> for &'a str { type Error = anyhow::Error; fn try_from(value: &'a Data) -> Result { match value { Data::String(s) => Ok(s), _ => bail!("not a string"), } } } impl TryFrom for SStr { type Error = anyhow::Error; fn try_from(value: Data) -> Result { value.into_sstr().ok_or_else(|| anyhow!("not a string")) } } impl<'a> TryFrom<&'a Data> for Cow<'a, str> { type Error = anyhow::Error; fn try_from(value: &'a Data) -> Result { match value { Data::String(s) => Ok(Cow::Borrowed(s)), _ => bail!("not a string"), } } } impl TryFrom<&Data> for String { type Error = anyhow::Error; fn try_from(value: &Data) -> Result { value.try_into().map(|s: &str| s.to_owned()) } } impl TryFrom for String { type Error = anyhow::Error; fn try_from(value: Data) -> Result { SStr::try_from(value).map(|s| s.into_owned()) } } impl TryFrom<&Data> for HashMap { type Error = anyhow::Error; fn try_from(_: &Data) -> Result { bail!("cannot take ownership of dict from &Data"); } } impl TryFrom for HashMap { type Error = anyhow::Error; fn try_from(value: Data) -> Result { match value { Data::Dict(d) => Ok(d), _ => bail!("not a dict"), } } } impl TryFrom for UrlCow<'static> { type Error = anyhow::Error; fn try_from(value: Data) -> Result { match value { Data::String(s) => s.try_into(), Data::Url(u) => Ok(u.into()), Data::Bytes(b) => b.try_into(), _ => bail!("not a URL"), } } } impl<'a> TryFrom<&'a Data> for UrlCow<'a> { type Error = anyhow::Error; fn try_from(value: &'a Data) -> Result { match value { Data::String(s) => Self::try_from(&**s), Data::Url(u) => Ok(u.into()), Data::Bytes(b) => b.as_slice().try_into(), _ => bail!("not a URL"), } } } impl<'a> TryFrom<&'a Data> for &'a [u8] { type Error = anyhow::Error; fn try_from(value: &'a Data) -> Result { match value { Data::Bytes(b) => Ok(b), _ => bail!("not bytes"), } } } impl TryFrom for StrandBuf { type Error = anyhow::Error; fn try_from(value: Data) -> Result { Ok(match value { Data::String(s) => s.into_owned().into(), Data::Path(p) => p.into_strand(), Data::Bytes(b) => Self::Bytes(b), _ => bail!("cannot convert to StrandBuf"), }) } } impl TryFrom<&Data> for StrandBuf { type Error = anyhow::Error; fn try_from(value: &Data) -> Result { Ok(match value { Data::String(s) => s.to_string().into(), Data::Path(p) => p.into_strand(), Data::Bytes(b) => Self::Bytes(b.clone()), _ => bail!("cannot convert to StrandBuf"), }) } } impl PartialEq for Data { fn eq(&self, other: &bool) -> bool { self.try_into().is_ok_and(|b| *other == b) } } impl Data { pub fn as_str(&self) -> Option<&str> { match self { Self::String(s) => Some(s), _ => None, } } pub fn as_any(&self) -> Option<&T> { match self { Self::Any(b) => (**b).as_any().downcast_ref::(), _ => None, } } pub fn into_sstr(self) -> Option { match self { Self::String(s) => Some(s), _ => None, } } pub fn into_any(self) -> Option { match self { Self::Any(b) => b.into_any().downcast::().ok().map(|b| *b), _ => None, } } // FIXME: find a better name pub fn into_any2(self) -> Result { if let Self::Any(b) = self && let Ok(t) = b.into_any().downcast::() { Ok(*t) } else { bail!("Failed to downcast Data into {}", std::any::type_name::()) } } } impl_into_integer!(Data, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, crate::Id); impl_into_number!(Data, f32, f64);