use crate::types::cid::CidLink;
use crate::types::{
DataModelType,
cid::Cid,
string::AtprotoStr,
value::{Array, Data, Object, RawData, parsing},
};
use crate::{Bos, CowStr};
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 core::convert::Infallible;
use core::str::FromStr;
use serde::Serialize;
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<S> TryFrom<Data<S>> for ()
where
S: AsRef<str> + Bos<str>,
{
type Error = ConversionError;
fn try_from(ipld: Data<S>) -> 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<S: 'static> TryFrom<Data<S>> for Option<$ty>
where
S: AsRef<str> + Bos<str>,
{
type Error = ConversionError;
fn try_from(ipld: Data<S>) -> 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<S: 'static> TryFrom<Data<S>> for $ty
where
S: AsRef<str> + Bos<str>,
{
type Error = ConversionError;
fn try_from(ipld: Data<S>) -> 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>
where
S: AsRef<str> + Bos<str>,
{
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>
where
S: AsRef<str> + Bos<str>, {
fn from(t: $ty) -> Self {
Data::$enum(t$(.$fn())*)
}
}
};
}
impl<S> From<String> for Data<S>
where
S: AsRef<str> + Bos<str> + From<String>,
{
fn from(t: String) -> Self {
Data::String(AtprotoStr::new(t.into()))
}
}
impl<'a, S> From<&'a str> for Data<S>
where
S: AsRef<str> + Bos<str> + From<&'a str>,
{
fn from(t: &'a str) -> Self {
Data::String(AtprotoStr::new(S::from(t)))
}
}
impl<S> From<&[u8]> for Data<S>
where
S: AsRef<str> + Bos<str>,
{
fn from(t: &[u8]) -> Self {
Data::Bytes(Bytes::copy_from_slice(t))
}
}
impl<'s, S> From<CowStr<'s>> for Data<S>
where
S: AsRef<str> + Bos<str> + FromStr<Err = Infallible>,
{
fn from(t: CowStr<'s>) -> Self {
Data::String(AtprotoStr::new(
S::from_str(t.as_ref()).unwrap_or_else(|_| unreachable!()),
))
}
}
impl<S> From<SmolStr> for Data<S>
where
S: AsRef<str> + Bos<str> + From<SmolStr>,
{
fn from(t: SmolStr) -> Self {
Data::String(AtprotoStr::new(S::from(t)))
}
}
impl<'s, S> From<Cow<'s, str>> for Data<S>
where
S: AsRef<str> + Bos<str> + FromStr<Err = Infallible>,
{
fn from(t: Cow<'s, str>) -> Self {
Data::String(AtprotoStr::new(
S::from_str(t.as_ref()).unwrap_or_else(|_| unreachable!()),
))
}
}
impl<S> TryFrom<Data<S>> for Option<String>
where
S: AsRef<str> + Bos<str> + Clone + Serialize,
{
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
where
S: AsRef<str> + Bos<str> + TryFrom<String> + Clone + Serialize,
{
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>
where
S: AsRef<str> + Bos<str>,
{
fn from(value: Vec<Data<S>>) -> Self {
Array(value)
}
}
impl<S> From<BTreeMap<SmolStr, Data<S>>> for Object<S>
where
S: AsRef<str> + Bos<str>,
{
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>,);
derive_into_atproto!(Object, Object<S>,);
derive_into_atproto!(CidLink, Cid<S>,);
derive_into_atproto!(Blob, crate::types::blob::Blob<S>,);
derive_into_atproto!(String, AtprotoStr<S>,);
impl<S> From<CidLink<S>> for Data<S>
where
S: AsRef<str> + Bos<str>,
{
fn from(t: CidLink<S>) -> Self {
Data::CidLink(t.0)
}
}
impl<S> From<crate::types::blob::BlobRef<S>> for Data<S>
where
S: AsRef<str> + Bos<str>,
{
fn from(t: crate::types::blob::BlobRef<S>) -> Self {
Data::Blob(t.into())
}
}
impl<S> From<&Cid<S>> for Data<S>
where
S: AsRef<str> + Bos<str> + Clone,
{
fn from(t: &Cid<S>) -> Self {
Data::CidLink(t.clone())
}
}
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!(Array, Array<S>);
derive_try_from_atproto!(Object, Object<S>);
derive_try_from_atproto!(CidLink, Cid<S>);
derive_try_from_atproto!(Blob, crate::types::blob::Blob<S>);
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<S>);
derive_try_from_atproto_option!(Object, Object<S>);
derive_try_from_atproto_option!(CidLink, Cid<S>);
derive_try_from_atproto_option!(Blob, crate::types::blob::Blob<S>);
impl<'s, S> TryFrom<RawData<'s>> for Data<S>
where
S: Bos<str> + AsRef<str> + From<CowStr<'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) => match i64::try_from(u) {
Ok(i) => Ok(Data::Integer(i)),
Err(_) => Err(ConversionError::InvalidRawData {
message: "unsigned integer is too large for AT Protocol integer".to_string(),
}),
},
RawData::String(s) => {
Ok(Data::String(parsing::parse_string(S::from(s))))
}
RawData::Bytes(b) => Ok(Data::Bytes(b)),
RawData::CidLink(cid) => Ok(Data::CidLink(cid.convert())),
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) => usize::try_from(*u).map_err(|_| {
ConversionError::InvalidRawData {
message: "blob size is too large".to_string(),
}
})?,
RawData::SignedInt(i) => usize::try_from(*i).map_err(|_| {
ConversionError::InvalidRawData {
message: "blob size must be non-negative".to_string(),
}
})?,
_ => {
return Err(ConversionError::InvalidRawData {
message: "blob size must be integer".to_string(),
});
}
};
return Ok(Data::Blob(crate::types::blob::Blob {
r#ref: CidLink(cid.clone().convert()),
mime_type: crate::types::blob::MimeType::new(S::from(mime.clone())),
size: size_val,
}));
}
}
}
let mut validated = BTreeMap::new();
for (key, value) in map {
let data_value: Data<S> = value.try_into()?;
validated.insert(key, data_value);
}
Ok(Data::Object(Object(validated)))
}
RawData::Blob(blob) => Ok(Data::Blob(blob.convert())),
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(),
}),
}
}
}