1use crate::{
2 IntoStatic,
3 types::{DataModelType, LexiconStringType, UriType, blob::Blob, string::*},
4};
5use bytes::Bytes;
6use ipld_core::ipld::Ipld;
7use smol_str::{SmolStr, ToSmolStr};
8use std::collections::BTreeMap;
9
10pub mod convert;
12pub mod parsing;
14pub mod serde_impl;
16
17#[cfg(test)]
18mod tests;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum Data<'s> {
29 Null,
31 Boolean(bool),
33 Integer(i64),
35 String(AtprotoStr<'s>),
37 Bytes(Bytes),
39 CidLink(Cid<'s>),
41 Array(Array<'s>),
43 Object(Object<'s>),
45 Blob(Blob<'s>),
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
51pub enum AtDataError {
52 #[error("floating point numbers not allowed in AT protocol data")]
54 FloatNotAllowed,
55}
56
57impl<'s> Data<'s> {
58 pub fn data_type(&self) -> DataModelType {
60 match self {
61 Data::Null => DataModelType::Null,
62 Data::Boolean(_) => DataModelType::Boolean,
63 Data::Integer(_) => DataModelType::Integer,
64 Data::String(s) => match s {
65 AtprotoStr::Datetime(_) => DataModelType::String(LexiconStringType::Datetime),
66 AtprotoStr::Language(_) => DataModelType::String(LexiconStringType::Language),
67 AtprotoStr::Tid(_) => DataModelType::String(LexiconStringType::Tid),
68 AtprotoStr::Nsid(_) => DataModelType::String(LexiconStringType::Nsid),
69 AtprotoStr::Did(_) => DataModelType::String(LexiconStringType::Did),
70 AtprotoStr::Handle(_) => DataModelType::String(LexiconStringType::Handle),
71 AtprotoStr::AtIdentifier(_) => {
72 DataModelType::String(LexiconStringType::AtIdentifier)
73 }
74 AtprotoStr::AtUri(_) => DataModelType::String(LexiconStringType::AtUri),
75 AtprotoStr::Uri(uri) => match uri {
76 Uri::Did(_) => DataModelType::String(LexiconStringType::Uri(UriType::Did)),
77 Uri::At(_) => DataModelType::String(LexiconStringType::Uri(UriType::At)),
78 Uri::Https(_) => DataModelType::String(LexiconStringType::Uri(UriType::Https)),
79 Uri::Wss(_) => DataModelType::String(LexiconStringType::Uri(UriType::Wss)),
80 Uri::Cid(_) => DataModelType::String(LexiconStringType::Uri(UriType::Cid)),
81 Uri::Any(_) => DataModelType::String(LexiconStringType::Uri(UriType::Any)),
82 },
83 AtprotoStr::Cid(_) => DataModelType::String(LexiconStringType::Cid),
84 AtprotoStr::RecordKey(_) => DataModelType::String(LexiconStringType::RecordKey),
85 AtprotoStr::String(_) => DataModelType::String(LexiconStringType::String),
86 },
87 Data::Bytes(_) => DataModelType::Bytes,
88 Data::CidLink(_) => DataModelType::CidLink,
89 Data::Array(_) => DataModelType::Array,
90 Data::Object(_) => DataModelType::Object,
91 Data::Blob(_) => DataModelType::Blob,
92 }
93 }
94 pub fn from_json(json: &'s serde_json::Value) -> Result<Self, AtDataError> {
96 Ok(if let Some(value) = json.as_bool() {
97 Self::Boolean(value)
98 } else if let Some(value) = json.as_i64() {
99 Self::Integer(value)
100 } else if let Some(value) = json.as_str() {
101 Self::String(parsing::parse_string(value))
102 } else if let Some(value) = json.as_array() {
103 Self::Array(Array::from_json(value)?)
104 } else if let Some(value) = json.as_object() {
105 Object::from_json(value)?
106 } else if json.is_f64() {
107 return Err(AtDataError::FloatNotAllowed);
108 } else {
109 Self::Null
110 })
111 }
112
113 pub fn from_cbor(cbor: &'s Ipld) -> Result<Self, AtDataError> {
115 Ok(match cbor {
116 Ipld::Null => Data::Null,
117 Ipld::Bool(bool) => Data::Boolean(*bool),
118 Ipld::Integer(int) => Data::Integer(*int as i64),
119 Ipld::Float(_) => {
120 return Err(AtDataError::FloatNotAllowed);
121 }
122 Ipld::String(string) => Self::String(parsing::parse_string(string)),
123 Ipld::Bytes(items) => Self::Bytes(Bytes::copy_from_slice(items.as_slice())),
124 Ipld::List(iplds) => Self::Array(Array::from_cbor(iplds)?),
125 Ipld::Map(btree_map) => Object::from_cbor(btree_map)?,
126 Ipld::Link(cid) => Self::CidLink(Cid::ipld(*cid)),
127 })
128 }
129}
130
131impl IntoStatic for Data<'_> {
132 type Output = Data<'static>;
133 fn into_static(self) -> Data<'static> {
134 match self {
135 Data::Null => Data::Null,
136 Data::Boolean(bool) => Data::Boolean(bool),
137 Data::Integer(int) => Data::Integer(int),
138 Data::String(string) => Data::String(string.into_static()),
139 Data::Bytes(bytes) => Data::Bytes(bytes),
140 Data::Array(array) => Data::Array(array.into_static()),
141 Data::Object(object) => Data::Object(object.into_static()),
142 Data::CidLink(cid) => Data::CidLink(cid.into_static()),
143 Data::Blob(blob) => Data::Blob(blob.into_static()),
144 }
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct Array<'s>(pub Vec<Data<'s>>);
151
152impl IntoStatic for Array<'_> {
153 type Output = Array<'static>;
154 fn into_static(self) -> Array<'static> {
155 Array(self.0.into_static())
156 }
157}
158
159impl<'s> Array<'s> {
160 pub fn from_json(json: &'s Vec<serde_json::Value>) -> Result<Self, AtDataError> {
162 let mut array = Vec::with_capacity(json.len());
163 for item in json {
164 array.push(Data::from_json(item)?);
165 }
166 Ok(Self(array))
167 }
168 pub fn from_cbor(cbor: &'s Vec<Ipld>) -> Result<Self, AtDataError> {
170 let mut array = Vec::with_capacity(cbor.len());
171 for item in cbor {
172 array.push(Data::from_cbor(item)?);
173 }
174 Ok(Self(array))
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct Object<'s>(pub BTreeMap<SmolStr, Data<'s>>);
181
182impl IntoStatic for Object<'_> {
183 type Output = Object<'static>;
184 fn into_static(self) -> Object<'static> {
185 Object(self.0.into_static())
186 }
187}
188
189impl<'s> Object<'s> {
190 pub fn from_json(
194 json: &'s serde_json::Map<String, serde_json::Value>,
195 ) -> Result<Data<'s>, AtDataError> {
196 if let Some(type_field) = json.get("$type").and_then(|v| v.as_str()) {
197 if parsing::infer_from_type(type_field) == DataModelType::Blob {
198 if let Some(blob) = parsing::json_to_blob(json) {
199 return Ok(Data::Blob(blob));
200 }
201 }
202 }
203 let mut map = BTreeMap::new();
204
205 for (key, value) in json {
206 if key == "$type" {
207 map.insert(key.to_smolstr(), Data::from_json(value)?);
208 }
209 match parsing::string_key_type_guess(key) {
210 DataModelType::Null if value.is_null() => {
211 map.insert(key.to_smolstr(), Data::Null);
212 }
213 DataModelType::Boolean if value.is_boolean() => {
214 map.insert(key.to_smolstr(), Data::Boolean(value.as_bool().unwrap()));
215 }
216 DataModelType::Integer if value.is_i64() => {
217 map.insert(key.to_smolstr(), Data::Integer(value.as_i64().unwrap()));
218 }
219 DataModelType::Bytes if value.is_string() => {
220 map.insert(
221 key.to_smolstr(),
222 parsing::decode_bytes(value.as_str().unwrap()),
223 );
224 }
225 DataModelType::CidLink => {
226 if let Some(value) = value.as_object() {
227 if let Some(value) = value.get("$link").and_then(|v| v.as_str()) {
228 map.insert(key.to_smolstr(), Data::CidLink(Cid::Str(value.into())));
229 } else {
230 map.insert(key.to_smolstr(), Object::from_json(value)?);
231 }
232 } else {
233 map.insert(key.to_smolstr(), Data::from_json(value)?);
234 }
235 }
236 DataModelType::Blob if value.is_object() => {
237 map.insert(
238 key.to_smolstr(),
239 Object::from_json(value.as_object().unwrap())?,
240 );
241 }
242 DataModelType::Array if value.is_array() => {
243 map.insert(
244 key.to_smolstr(),
245 Data::Array(Array::from_json(value.as_array().unwrap())?),
246 );
247 }
248 DataModelType::Object if value.is_object() => {
249 map.insert(
250 key.to_smolstr(),
251 Object::from_json(value.as_object().unwrap())?,
252 );
253 }
254 DataModelType::String(string_type) if value.is_string() => {
255 parsing::insert_string(&mut map, key, value.as_str().unwrap(), string_type)?;
256 }
257 _ => {
258 map.insert(key.to_smolstr(), Data::from_json(value)?);
259 }
260 }
261 }
262
263 Ok(Data::Object(Object(map)))
264 }
265
266 pub fn from_cbor(cbor: &'s BTreeMap<String, Ipld>) -> Result<Data<'s>, AtDataError> {
270 if let Some(Ipld::String(type_field)) = cbor.get("$type") {
271 if parsing::infer_from_type(type_field) == DataModelType::Blob {
272 if let Some(blob) = parsing::cbor_to_blob(cbor) {
273 return Ok(Data::Blob(blob));
274 }
275 }
276 }
277 let mut map = BTreeMap::new();
278
279 for (key, value) in cbor {
280 if key == "$type" {
281 map.insert(key.to_smolstr(), Data::from_cbor(value)?);
282 }
283 match (parsing::string_key_type_guess(key), value) {
284 (DataModelType::Null, Ipld::Null) => {
285 map.insert(key.to_smolstr(), Data::Null);
286 }
287 (DataModelType::Boolean, Ipld::Bool(value)) => {
288 map.insert(key.to_smolstr(), Data::Boolean(*value));
289 }
290 (DataModelType::Integer, Ipld::Integer(int)) => {
291 map.insert(key.to_smolstr(), Data::Integer(*int as i64));
292 }
293 (DataModelType::Bytes, Ipld::Bytes(value)) => {
294 map.insert(key.to_smolstr(), Data::Bytes(Bytes::copy_from_slice(value)));
295 }
296 (DataModelType::Blob, Ipld::Map(value)) => {
297 map.insert(key.to_smolstr(), Object::from_cbor(value)?);
298 }
299 (DataModelType::Array, Ipld::List(value)) => {
300 map.insert(key.to_smolstr(), Data::Array(Array::from_cbor(value)?));
301 }
302 (DataModelType::Object, Ipld::Map(value)) => {
303 map.insert(key.to_smolstr(), Object::from_cbor(value)?);
304 }
305 (DataModelType::String(string_type), Ipld::String(value)) => {
306 parsing::insert_string(&mut map, key, value, string_type)?;
307 }
308 _ => {
309 map.insert(key.to_smolstr(), Data::from_cbor(value)?);
310 }
311 }
312 }
313
314 Ok(Data::Object(Object(map)))
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
324pub enum RawData<'s> {
325 Null,
327 Boolean(bool),
329 SignedInt(i64),
331 UnsignedInt(u64),
333 String(CowStr<'s>),
335 Bytes(Bytes),
337 CidLink(Cid<'s>),
339 Array(Vec<RawData<'s>>),
341 Object(BTreeMap<SmolStr, RawData<'s>>),
343 Blob(Blob<'s>),
345 InvalidBlob(Box<RawData<'s>>),
347 InvalidNumber(Bytes),
349 InvalidData(Bytes),
351}