Skip to main content

jacquard_common/types/value/
convert.rs

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/// Error used for converting from and into [`crate::types::value::Data`].
23#[derive(Clone, Debug, thiserror::Error, miette::Diagnostic)]
24#[non_exhaustive]
25pub enum ConversionError {
26    /// Error when the Atproto data type wasn't the one we expected.
27    #[error("kind error: expected {expected:?} but found {found:?}")]
28    WrongAtprotoType {
29        /// The expected type.
30        expected: DataModelType,
31        /// The actual type.
32        found: DataModelType,
33    },
34    /// Error when the given Atproto data type cannot be converted into a certain value type.
35    #[error("conversion error: cannot convert {from:?} into {into:?}")]
36    FromAtprotoData {
37        /// The Atproto data type trying to convert from.
38        from: DataModelType,
39        /// The type trying to convert into.
40        into: TypeId,
41    },
42    /// Error when converting from RawData containing invalid data
43    #[error("invalid raw data: {message}")]
44    InvalidRawData {
45        /// Description of what was invalid
46        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
359/// Convert RawData to validated Data with type inference
360impl<'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                // Apply string type inference
379                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                // Check for special blob structure
392                if let Some(RawData::String(type_str)) = map.get("$type") {
393                    if parsing::infer_from_type(type_str) == DataModelType::Blob {
394                        // Try to parse as blob
395                        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                // Regular object - convert recursively with type inference based on keys
428                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}