1use crate::types::cid::CidLink;
2use crate::types::{
3 DataModelType,
4 cid::Cid,
5 string::AtprotoStr,
6 value::{Array, Data, Object, RawData, parsing},
7};
8use crate::{Bos, CowStr};
9use alloc::boxed::Box;
10use alloc::collections::BTreeMap;
11use alloc::string::String;
12use alloc::string::ToString;
13use alloc::vec::Vec;
14use bytes::Bytes;
15use core::any::TypeId;
16use core::convert::Infallible;
17use core::str::FromStr;
18use serde::Serialize;
19use smol_str::SmolStr;
20use std::borrow::Cow;
21
22#[derive(Clone, Debug, thiserror::Error, miette::Diagnostic)]
24#[non_exhaustive]
25pub enum ConversionError {
26 #[error("kind error: expected {expected:?} but found {found:?}")]
28 WrongAtprotoType {
29 expected: DataModelType,
31 found: DataModelType,
33 },
34 #[error("conversion error: cannot convert {from:?} into {into:?}")]
36 FromAtprotoData {
37 from: DataModelType,
39 into: TypeId,
41 },
42 #[error("invalid raw data: {message}")]
44 InvalidRawData {
45 message: String,
47 },
48}
49
50impl<S> TryFrom<Data<S>> for ()
51where
52 S: AsRef<str> + Bos<str>,
53{
54 type Error = ConversionError;
55
56 fn try_from(ipld: Data<S>) -> Result<Self, Self::Error> {
57 match ipld {
58 Data::Null => Ok(()),
59 _ => Err(ConversionError::WrongAtprotoType {
60 expected: DataModelType::Null,
61 found: ipld.data_type(),
62 }),
63 }
64 }
65}
66
67macro_rules! derive_try_from_atproto_option {
68 ($enum:ident, $ty:ty) => {
69 impl<S: 'static> TryFrom<Data<S>> for Option<$ty>
70 where
71 S: AsRef<str> + Bos<str>,
72 {
73 type Error = ConversionError;
74
75 fn try_from(ipld: Data<S>) -> Result<Self, Self::Error> {
76 match ipld {
77 Data::Null => Ok(None),
78 Data::$enum(value) => Ok(Some(value.try_into().map_err(|_| {
79 ConversionError::FromAtprotoData {
80 from: DataModelType::$enum,
81 into: TypeId::of::<$ty>(),
82 }
83 })?)),
84 _ => Err(ConversionError::WrongAtprotoType {
85 expected: DataModelType::$enum,
86 found: ipld.data_type(),
87 }),
88 }
89 }
90 }
91 };
92}
93
94macro_rules! derive_try_from_atproto {
95 ($enum:ident, $ty:ty) => {
96 impl<S: 'static> TryFrom<Data<S>> for $ty
97 where
98 S: AsRef<str> + Bos<str>,
99 {
100 type Error = ConversionError;
101
102 fn try_from(ipld: Data<S>) -> Result<Self, Self::Error> {
103 match ipld {
104 Data::$enum(value) => {
105 Ok(value
106 .try_into()
107 .map_err(|_| ConversionError::FromAtprotoData {
108 from: DataModelType::$enum,
109 into: TypeId::of::<$ty>(),
110 })?)
111 }
112
113 _ => Err(ConversionError::WrongAtprotoType {
114 expected: DataModelType::$enum,
115 found: ipld.data_type(),
116 }),
117 }
118 }
119 }
120 };
121}
122
123macro_rules! derive_into_atproto_prim {
124 ($enum:ident, $ty:ty, $fn:ident) => {
125 impl<S> From<$ty> for Data<S>
126 where
127 S: AsRef<str> + Bos<str>,
128 {
129 fn from(t: $ty) -> Self {
130 Data::$enum(t.$fn() as _)
131 }
132 }
133 };
134}
135
136macro_rules! derive_into_atproto {
137 ($enum:ident, $ty:ty, $($fn:ident),*) => {
138 impl<S> From<$ty> for Data<S>
139 where
140 S: AsRef<str> + Bos<str>, {
141 fn from(t: $ty) -> Self {
142 Data::$enum(t$(.$fn())*)
143 }
144 }
145 };
146}
147
148impl<S> From<String> for Data<S>
149where
150 S: AsRef<str> + Bos<str> + From<String>,
151{
152 fn from(t: String) -> Self {
153 Data::String(AtprotoStr::new(t.into()))
154 }
155}
156
157impl<'a, S> From<&'a str> for Data<S>
158where
159 S: AsRef<str> + Bos<str> + From<&'a str>,
160{
161 fn from(t: &'a str) -> Self {
162 Data::String(AtprotoStr::new(S::from(t)))
163 }
164}
165
166impl<S> From<&[u8]> for Data<S>
167where
168 S: AsRef<str> + Bos<str>,
169{
170 fn from(t: &[u8]) -> Self {
171 Data::Bytes(Bytes::copy_from_slice(t))
172 }
173}
174
175impl<'s, S> From<CowStr<'s>> for Data<S>
176where
177 S: AsRef<str> + Bos<str> + FromStr<Err = Infallible>,
178{
179 fn from(t: CowStr<'s>) -> Self {
180 Data::String(AtprotoStr::new(
181 S::from_str(t.as_ref()).unwrap_or_else(|_| unreachable!()),
182 ))
183 }
184}
185
186impl<S> From<SmolStr> for Data<S>
187where
188 S: AsRef<str> + Bos<str> + From<SmolStr>,
189{
190 fn from(t: SmolStr) -> Self {
191 Data::String(AtprotoStr::new(S::from(t)))
192 }
193}
194
195impl<'s, S> From<Cow<'s, str>> for Data<S>
196where
197 S: AsRef<str> + Bos<str> + FromStr<Err = Infallible>,
198{
199 fn from(t: Cow<'s, str>) -> Self {
200 Data::String(AtprotoStr::new(
201 S::from_str(t.as_ref()).unwrap_or_else(|_| unreachable!()),
202 ))
203 }
204}
205
206impl<S> TryFrom<Data<S>> for Option<String>
207where
208 S: AsRef<str> + Bos<str> + Clone + Serialize,
209{
210 type Error = ConversionError;
211
212 fn try_from(ipld: Data<S>) -> Result<Self, Self::Error> {
213 match ipld {
214 Data::Null => Ok(None),
215 Data::String(value) => Ok(Some(value.try_into().map_err(|_| {
216 ConversionError::FromAtprotoData {
217 from: DataModelType::String(crate::types::LexiconStringType::String),
218 into: TypeId::of::<String>(),
219 }
220 })?)),
221 _ => Err(ConversionError::WrongAtprotoType {
222 expected: DataModelType::String(crate::types::LexiconStringType::String),
223 found: ipld.data_type(),
224 }),
225 }
226 }
227}
228
229impl<S> TryFrom<Data<S>> for String
230where
231 S: AsRef<str> + Bos<str> + TryFrom<String> + Clone + Serialize,
232{
233 type Error = ConversionError;
234
235 fn try_from(ipld: Data<S>) -> Result<Self, Self::Error> {
236 match ipld {
237 Data::String(value) => {
238 Ok(value
239 .try_into()
240 .map_err(|_| ConversionError::FromAtprotoData {
241 from: DataModelType::String(crate::types::LexiconStringType::String),
242 into: TypeId::of::<String>(),
243 })?)
244 }
245
246 _ => Err(ConversionError::WrongAtprotoType {
247 expected: DataModelType::String(crate::types::LexiconStringType::String),
248 found: ipld.data_type(),
249 }),
250 }
251 }
252}
253
254impl<S> From<Vec<Data<S>>> for Array<S>
255where
256 S: AsRef<str> + Bos<str>,
257{
258 fn from(value: Vec<Data<S>>) -> Self {
259 Array(value)
260 }
261}
262
263impl<S> From<BTreeMap<SmolStr, Data<S>>> for Object<S>
264where
265 S: AsRef<str> + Bos<str>,
266{
267 fn from(value: BTreeMap<SmolStr, Data<S>>) -> Self {
268 Object(value)
269 }
270}
271
272derive_into_atproto!(Boolean, bool, clone);
273derive_into_atproto_prim!(Integer, i8, clone);
274derive_into_atproto_prim!(Integer, i16, clone);
275derive_into_atproto_prim!(Integer, i32, clone);
276derive_into_atproto_prim!(Integer, i64, clone);
277derive_into_atproto_prim!(Integer, i128, clone);
278derive_into_atproto_prim!(Integer, isize, clone);
279derive_into_atproto_prim!(Integer, u8, clone);
280derive_into_atproto_prim!(Integer, u16, clone);
281derive_into_atproto_prim!(Integer, u32, clone);
282derive_into_atproto_prim!(Integer, u64, clone);
283derive_into_atproto_prim!(Integer, usize, clone);
284derive_into_atproto!(Bytes, Box<[u8]>, into);
285derive_into_atproto!(Bytes, Vec<u8>, into);
286derive_into_atproto!(Array, Array<S>,);
287derive_into_atproto!(Object, Object<S>,);
288
289derive_into_atproto!(CidLink, Cid<S>,);
290derive_into_atproto!(Blob, crate::types::blob::Blob<S>,);
291derive_into_atproto!(String, AtprotoStr<S>,);
292
293impl<S> From<CidLink<S>> for Data<S>
294where
295 S: AsRef<str> + Bos<str>,
296{
297 fn from(t: CidLink<S>) -> Self {
298 Data::CidLink(t.0)
299 }
300}
301
302impl<S> From<crate::types::blob::BlobRef<S>> for Data<S>
303where
304 S: AsRef<str> + Bos<str>,
305{
306 fn from(t: crate::types::blob::BlobRef<S>) -> Self {
307 Data::Blob(t.into())
308 }
309}
310
311impl<S> From<&Cid<S>> for Data<S>
312where
313 S: AsRef<str> + Bos<str> + Clone,
314{
315 fn from(t: &Cid<S>) -> Self {
316 Data::CidLink(t.clone())
317 }
318}
319
320derive_try_from_atproto!(Boolean, bool);
321derive_try_from_atproto!(Integer, i8);
322derive_try_from_atproto!(Integer, i16);
323derive_try_from_atproto!(Integer, i32);
324derive_try_from_atproto!(Integer, i64);
325derive_try_from_atproto!(Integer, i128);
326derive_try_from_atproto!(Integer, isize);
327derive_try_from_atproto!(Integer, u8);
328derive_try_from_atproto!(Integer, u16);
329derive_try_from_atproto!(Integer, u32);
330derive_try_from_atproto!(Integer, u64);
331derive_try_from_atproto!(Integer, u128);
332derive_try_from_atproto!(Integer, usize);
333derive_try_from_atproto!(Bytes, Vec<u8>);
334derive_try_from_atproto!(Array, Array<S>);
335derive_try_from_atproto!(Object, Object<S>);
336derive_try_from_atproto!(CidLink, Cid<S>);
337derive_try_from_atproto!(Blob, crate::types::blob::Blob<S>);
338
339derive_try_from_atproto_option!(Boolean, bool);
340derive_try_from_atproto_option!(Integer, i8);
341derive_try_from_atproto_option!(Integer, i16);
342derive_try_from_atproto_option!(Integer, i32);
343derive_try_from_atproto_option!(Integer, i64);
344derive_try_from_atproto_option!(Integer, i128);
345derive_try_from_atproto_option!(Integer, isize);
346derive_try_from_atproto_option!(Integer, u8);
347derive_try_from_atproto_option!(Integer, u16);
348derive_try_from_atproto_option!(Integer, u32);
349derive_try_from_atproto_option!(Integer, u64);
350derive_try_from_atproto_option!(Integer, u128);
351derive_try_from_atproto_option!(Integer, usize);
352
353derive_try_from_atproto_option!(Bytes, Vec<u8>);
354derive_try_from_atproto_option!(Array, Array<S>);
355derive_try_from_atproto_option!(Object, Object<S>);
356derive_try_from_atproto_option!(CidLink, Cid<S>);
357derive_try_from_atproto_option!(Blob, crate::types::blob::Blob<S>);
358
359impl<'s, S> TryFrom<RawData<'s>> for Data<S>
361where
362 S: Bos<str> + AsRef<str> + From<CowStr<'s>>,
363{
364 type Error = ConversionError;
365
366 fn try_from(raw: RawData<'s>) -> Result<Self, Self::Error> {
367 match raw {
368 RawData::Null => Ok(Data::Null),
369 RawData::Boolean(b) => Ok(Data::Boolean(b)),
370 RawData::SignedInt(i) => Ok(Data::Integer(i)),
371 RawData::UnsignedInt(u) => match i64::try_from(u) {
372 Ok(i) => Ok(Data::Integer(i)),
373 Err(_) => Err(ConversionError::InvalidRawData {
374 message: "unsigned integer is too large for AT Protocol integer".to_string(),
375 }),
376 },
377 RawData::String(s) => {
378 Ok(Data::String(parsing::parse_string(S::from(s))))
380 }
381 RawData::Bytes(b) => Ok(Data::Bytes(b)),
382 RawData::CidLink(cid) => Ok(Data::CidLink(cid.convert())),
383 RawData::Array(arr) => {
384 let mut validated = Vec::with_capacity(arr.len());
385 for item in arr {
386 validated.push(item.try_into()?);
387 }
388 Ok(Data::Array(Array(validated)))
389 }
390 RawData::Object(map) => {
391 if let Some(RawData::String(type_str)) = map.get("$type") {
393 if parsing::infer_from_type(type_str) == DataModelType::Blob {
394 if let (
396 Some(RawData::CidLink(cid)),
397 Some(RawData::String(mime)),
398 Some(size),
399 ) = (map.get("ref"), map.get("mimeType"), map.get("size"))
400 {
401 let size_val = match size {
402 RawData::UnsignedInt(u) => usize::try_from(*u).map_err(|_| {
403 ConversionError::InvalidRawData {
404 message: "blob size is too large".to_string(),
405 }
406 })?,
407 RawData::SignedInt(i) => usize::try_from(*i).map_err(|_| {
408 ConversionError::InvalidRawData {
409 message: "blob size must be non-negative".to_string(),
410 }
411 })?,
412 _ => {
413 return Err(ConversionError::InvalidRawData {
414 message: "blob size must be integer".to_string(),
415 });
416 }
417 };
418 return Ok(Data::Blob(crate::types::blob::Blob {
419 r#ref: CidLink(cid.clone().convert()),
420 mime_type: crate::types::blob::MimeType::new(S::from(mime.clone())),
421 size: size_val,
422 }));
423 }
424 }
425 }
426
427 let mut validated = BTreeMap::new();
429 for (key, value) in map {
430 let data_value: Data<S> = value.try_into()?;
431 validated.insert(key, data_value);
432 }
433 Ok(Data::Object(Object(validated)))
434 }
435 RawData::Blob(blob) => Ok(Data::Blob(blob.convert())),
436 RawData::InvalidBlob(_) => Err(ConversionError::InvalidRawData {
437 message: "invalid blob structure".to_string(),
438 }),
439 RawData::InvalidNumber(_) => Err(ConversionError::InvalidRawData {
440 message: "invalid number (likely float)".to_string(),
441 }),
442 RawData::InvalidData(_) => Err(ConversionError::InvalidRawData {
443 message: "invalid data".to_string(),
444 }),
445 }
446 }
447}