use crate::types::cid::CidLink;
use crate::types::{
DataModelType,
cid::Cid,
string::AtprotoStr,
value::{Array, Data, Object, RawData, parsing},
};
use crate::{CowStr, IntoStatic};
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::string::ToString;
use alloc::vec::Vec;
use bytes::Bytes;
use core::any::TypeId;
use smol_str::SmolStr;
use std::borrow::Cow;
#[derive(Clone, Debug, thiserror::Error, miette::Diagnostic)]
#[non_exhaustive]
pub enum ConversionError {
#[error("kind error: expected {expected:?} but found {found:?}")]
WrongAtprotoType {
expected: DataModelType,
found: DataModelType,
},
#[error("conversion error: cannot convert {from:?} into {into:?}")]
FromAtprotoData {
from: DataModelType,
into: TypeId,
},
#[error("invalid raw data: {message}")]
InvalidRawData {
message: String,
},
}
impl TryFrom<Data<'_>> for () {
type Error = ConversionError;
fn try_from(ipld: Data) -> Result<Self, Self::Error> {
match ipld {
Data::Null => Ok(()),
_ => Err(ConversionError::WrongAtprotoType {
expected: DataModelType::Null,
found: ipld.data_type(),
}),
}
}
}
macro_rules! derive_try_from_atproto_option {
($enum:ident, $ty:ty) => {
impl TryFrom<Data<'static>> for Option<$ty> {
type Error = ConversionError;
fn try_from(ipld: Data<'static>) -> Result<Self, Self::Error> {
match ipld {
Data::Null => Ok(None),
Data::$enum(value) => Ok(Some(value.try_into().map_err(|_| {
ConversionError::FromAtprotoData {
from: DataModelType::$enum,
into: TypeId::of::<$ty>(),
}
})?)),
_ => Err(ConversionError::WrongAtprotoType {
expected: DataModelType::$enum,
found: ipld.data_type(),
}),
}
}
}
};
}
macro_rules! derive_try_from_atproto {
($enum:ident, $ty:ty) => {
impl TryFrom<Data<'static>> for $ty {
type Error = ConversionError;
fn try_from(ipld: Data<'static>) -> Result<Self, Self::Error> {
match ipld {
Data::$enum(value) => {
Ok(value
.try_into()
.map_err(|_| ConversionError::FromAtprotoData {
from: DataModelType::$enum,
into: TypeId::of::<$ty>(),
})?)
}
_ => Err(ConversionError::WrongAtprotoType {
expected: DataModelType::$enum,
found: ipld.data_type(),
}),
}
}
}
};
}
macro_rules! derive_into_atproto_prim {
($enum:ident, $ty:ty, $fn:ident) => {
impl<'s> From<$ty> for Data<'s> {
fn from(t: $ty) -> Self {
Data::$enum(t.$fn() as _)
}
}
};
}
macro_rules! derive_into_atproto {
($enum:ident, $ty:ty, $($fn:ident),*) => {
impl<'s> From<$ty> for Data<'s> {
fn from(t: $ty) -> Self {
Data::$enum(t$(.$fn())*)
}
}
};
}
impl From<String> for Data<'_> {
fn from(t: String) -> Self {
Data::String(AtprotoStr::new_owned(t))
}
}
impl<'a> From<&'a str> for Data<'a> {
fn from(t: &'a str) -> Self {
Data::String(AtprotoStr::new(t))
}
}
impl From<&[u8]> for Data<'_> {
fn from(t: &[u8]) -> Self {
Data::Bytes(Bytes::copy_from_slice(t))
}
}
impl<'s> From<CowStr<'s>> for Data<'s> {
fn from(t: CowStr<'s>) -> Self {
match t {
CowStr::Borrowed(s) => Data::String(AtprotoStr::new(s)),
CowStr::Owned(s) => Data::String(AtprotoStr::new_owned(s)),
}
}
}
impl From<SmolStr> for Data<'_> {
fn from(t: SmolStr) -> Self {
Data::String(AtprotoStr::new_owned(t))
}
}
impl<'s> From<Cow<'s, str>> for Data<'s> {
fn from(t: Cow<'s, str>) -> Self {
match t {
Cow::Borrowed(s) => Data::String(AtprotoStr::new(s)),
Cow::Owned(s) => Data::String(AtprotoStr::new_owned(s)),
}
}
}
impl<'s> TryFrom<Data<'s>> for Option<String> {
type Error = ConversionError;
fn try_from(ipld: Data<'s>) -> Result<Self, Self::Error> {
match ipld {
Data::Null => Ok(None),
Data::String(value) => Ok(Some(value.try_into().map_err(|_| {
ConversionError::FromAtprotoData {
from: DataModelType::String(crate::types::LexiconStringType::String),
into: TypeId::of::<String>(),
}
})?)),
_ => Err(ConversionError::WrongAtprotoType {
expected: DataModelType::String(crate::types::LexiconStringType::String),
found: ipld.data_type(),
}),
}
}
}
impl<'s> TryFrom<Data<'s>> for String {
type Error = ConversionError;
fn try_from(ipld: Data<'s>) -> Result<Self, Self::Error> {
match ipld {
Data::String(value) => {
Ok(value
.try_into()
.map_err(|_| ConversionError::FromAtprotoData {
from: DataModelType::String(crate::types::LexiconStringType::String),
into: TypeId::of::<String>(),
})?)
}
_ => Err(ConversionError::WrongAtprotoType {
expected: DataModelType::String(crate::types::LexiconStringType::String),
found: ipld.data_type(),
}),
}
}
}
impl<'s> From<Vec<Data<'s>>> for Array<'s> {
fn from(value: Vec<Data<'s>>) -> Self {
Array(value)
}
}
impl<'s> From<BTreeMap<SmolStr, Data<'s>>> for Object<'s> {
fn from(value: BTreeMap<SmolStr, Data<'s>>) -> Self {
Object(value)
}
}
derive_into_atproto!(Boolean, bool, clone);
derive_into_atproto_prim!(Integer, i8, clone);
derive_into_atproto_prim!(Integer, i16, clone);
derive_into_atproto_prim!(Integer, i32, clone);
derive_into_atproto_prim!(Integer, i64, clone);
derive_into_atproto_prim!(Integer, i128, clone);
derive_into_atproto_prim!(Integer, isize, clone);
derive_into_atproto_prim!(Integer, u8, clone);
derive_into_atproto_prim!(Integer, u16, clone);
derive_into_atproto_prim!(Integer, u32, clone);
derive_into_atproto_prim!(Integer, u64, clone);
derive_into_atproto_prim!(Integer, usize, clone);
derive_into_atproto!(Bytes, Box<[u8]>, into);
derive_into_atproto!(Bytes, Vec<u8>, into);
derive_into_atproto!(Array, Array<'s>, into);
derive_into_atproto!(Object, Object<'s>, to_owned);
derive_into_atproto!(CidLink, Cid<'s>, clone);
derive_into_atproto!(CidLink, &Cid<'s>, to_owned);
derive_try_from_atproto!(Boolean, bool);
derive_try_from_atproto!(Integer, i8);
derive_try_from_atproto!(Integer, i16);
derive_try_from_atproto!(Integer, i32);
derive_try_from_atproto!(Integer, i64);
derive_try_from_atproto!(Integer, i128);
derive_try_from_atproto!(Integer, isize);
derive_try_from_atproto!(Integer, u8);
derive_try_from_atproto!(Integer, u16);
derive_try_from_atproto!(Integer, u32);
derive_try_from_atproto!(Integer, u64);
derive_try_from_atproto!(Integer, u128);
derive_try_from_atproto!(Integer, usize);
derive_try_from_atproto!(Bytes, Vec<u8>);
derive_try_from_atproto!(Object, Object<'static>);
derive_try_from_atproto!(CidLink, Cid<'static>);
derive_try_from_atproto_option!(Boolean, bool);
derive_try_from_atproto_option!(Integer, i8);
derive_try_from_atproto_option!(Integer, i16);
derive_try_from_atproto_option!(Integer, i32);
derive_try_from_atproto_option!(Integer, i64);
derive_try_from_atproto_option!(Integer, i128);
derive_try_from_atproto_option!(Integer, isize);
derive_try_from_atproto_option!(Integer, u8);
derive_try_from_atproto_option!(Integer, u16);
derive_try_from_atproto_option!(Integer, u32);
derive_try_from_atproto_option!(Integer, u64);
derive_try_from_atproto_option!(Integer, u128);
derive_try_from_atproto_option!(Integer, usize);
derive_try_from_atproto_option!(Bytes, Vec<u8>);
derive_try_from_atproto_option!(Array, Array<'static>);
derive_try_from_atproto_option!(Object, Object<'static>);
derive_try_from_atproto_option!(CidLink, Cid<'static>);
impl<'s> TryFrom<RawData<'s>> for Data<'s> {
type Error = ConversionError;
fn try_from(raw: RawData<'s>) -> Result<Self, Self::Error> {
match raw {
RawData::Null => Ok(Data::Null),
RawData::Boolean(b) => Ok(Data::Boolean(b)),
RawData::SignedInt(i) => Ok(Data::Integer(i)),
RawData::UnsignedInt(u) => {
Ok(Data::Integer((u % (i64::MAX as u64)) as i64))
}
RawData::String(s) => {
Ok(Data::String(parsing::parse_string(&s).into_static()))
}
RawData::Bytes(b) => Ok(Data::Bytes(b)),
RawData::CidLink(cid) => Ok(Data::CidLink(cid)),
RawData::Array(arr) => {
let mut validated = Vec::with_capacity(arr.len());
for item in arr {
validated.push(item.try_into()?);
}
Ok(Data::Array(Array(validated)))
}
RawData::Object(map) => {
if let Some(RawData::String(type_str)) = map.get("$type") {
if parsing::infer_from_type(type_str) == DataModelType::Blob {
if let (
Some(RawData::CidLink(cid)),
Some(RawData::String(mime)),
Some(size),
) = (map.get("ref"), map.get("mimeType"), map.get("size"))
{
let size_val = match size {
RawData::UnsignedInt(u) => *u as usize,
RawData::SignedInt(i) => *i as usize,
_ => {
return Err(ConversionError::InvalidRawData {
message: "blob size must be integer".to_string(),
});
}
};
return Ok(Data::Blob(crate::types::blob::Blob {
r#ref: CidLink::str(cid).into_static(),
mime_type: crate::types::blob::MimeType::from(mime.clone()),
size: size_val,
}));
}
}
}
let mut validated = BTreeMap::new();
for (key, value) in map {
let data_value: Data = value.try_into()?;
validated.insert(key, data_value);
}
Ok(Data::Object(Object(validated)))
}
RawData::Blob(blob) => Ok(Data::Blob(blob)),
RawData::InvalidBlob(_) => Err(ConversionError::InvalidRawData {
message: "invalid blob structure".to_string(),
}),
RawData::InvalidNumber(_) => Err(ConversionError::InvalidRawData {
message: "invalid number (likely float)".to_string(),
}),
RawData::InvalidData(_) => Err(ConversionError::InvalidRawData {
message: "invalid data".to_string(),
}),
}
}
}