A better Rust ATProto crate

made parsing from json/cbor fallible to properly disallow floats. could instead null out floats? idk.

Orual 83d7296b b5ef4bce

Changed files
+49 -39
crates
jacquard-common
src
types
+49 -39
crates/jacquard-common/src/types/value.rs
··· 28 Blob(Blob<'s>), 29 } 30 31 impl<'s> Data<'s> { 32 - pub fn from_json(json: &'s serde_json::Value) -> Self { 33 - if let Some(value) = json.as_bool() { 34 Self::Boolean(value) 35 } else if let Some(value) = json.as_i64() { 36 Self::Integer(value) 37 } else if let Some(value) = json.as_str() { 38 Self::String(AtprotoStr::new(value)) 39 } else if let Some(value) = json.as_array() { 40 - Self::Array(Array::from_json(value)) 41 } else if let Some(value) = json.as_object() { 42 - Object::from_json(value) 43 - } else if let Some(num) = json.as_number() { 44 - // deliberately permissive here, just in case. 45 - Self::String(AtprotoStr::new_owned(num.to_smolstr())) 46 } else { 47 Self::Null 48 - } 49 } 50 51 - pub fn from_cbor(cbor: &'s Ipld) -> Self { 52 - match cbor { 53 Ipld::Null => Data::Null, 54 Ipld::Bool(bool) => Data::Boolean(*bool), 55 Ipld::Integer(int) => Data::Integer(*int as i64), 56 - Ipld::Float(_) => todo!(), 57 Ipld::String(string) => Self::String(AtprotoStr::new(string)), 58 Ipld::Bytes(items) => Self::Bytes(Bytes::copy_from_slice(items.as_slice())), 59 - Ipld::List(iplds) => Self::Array(Array::from_cbor(iplds)), 60 - Ipld::Map(btree_map) => Object::from_cbor(btree_map), 61 Ipld::Link(cid) => Self::CidLink(Cid::ipld(*cid)), 62 - } 63 } 64 } 65 ··· 67 pub struct Array<'s>(pub Vec<Data<'s>>); 68 69 impl<'s> Array<'s> { 70 - pub fn from_json(json: &'s Vec<serde_json::Value>) -> Self { 71 let mut array = Vec::with_capacity(json.len()); 72 for item in json { 73 - array.push(Data::from_json(item)); 74 } 75 - Self(array) 76 } 77 - pub fn from_cbor(cbor: &'s Vec<Ipld>) -> Self { 78 let mut array = Vec::with_capacity(cbor.len()); 79 for item in cbor { 80 - array.push(Data::from_cbor(item)); 81 } 82 - Self(array) 83 } 84 } 85 ··· 87 pub struct Object<'s>(pub BTreeMap<SmolStr, Data<'s>>); 88 89 impl<'s> Object<'s> { 90 - pub fn from_json(json: &'s serde_json::Map<String, serde_json::Value>) -> Data<'s> { 91 if let Some(type_field) = json.get("$type").and_then(|v| v.as_str()) { 92 if infer_from_type(type_field) == DataModelType::Blob { 93 if let Some(blob) = json_to_blob(json) { 94 - return Data::Blob(blob); 95 } 96 } 97 } ··· 99 100 for (key, value) in json { 101 if key == "$type" { 102 - map.insert(key.to_smolstr(), Data::from_json(value)); 103 } 104 match string_key_type_guess(key) { 105 DataModelType::Null if value.is_null() => { ··· 119 if let Some(value) = value.get("$link").and_then(|v| v.as_str()) { 120 map.insert(key.to_smolstr(), Data::CidLink(Cid::Str(value.into()))); 121 } else { 122 - map.insert(key.to_smolstr(), Object::from_json(value)); 123 } 124 } else { 125 - map.insert(key.to_smolstr(), Data::from_json(value)); 126 } 127 } 128 DataModelType::Blob if value.is_object() => { 129 map.insert( 130 key.to_smolstr(), 131 - Object::from_json(value.as_object().unwrap()), 132 ); 133 } 134 DataModelType::Array if value.is_array() => { 135 map.insert( 136 key.to_smolstr(), 137 - Data::Array(Array::from_json(value.as_array().unwrap())), 138 ); 139 } 140 DataModelType::Object if value.is_object() => { 141 map.insert( 142 key.to_smolstr(), 143 - Object::from_json(value.as_object().unwrap()), 144 ); 145 } 146 DataModelType::String(string_type) if value.is_string() => { 147 insert_string(&mut map, key, value.as_str().unwrap(), string_type); 148 } 149 _ => { 150 - map.insert(key.to_smolstr(), Data::from_json(value)); 151 } 152 } 153 } 154 155 - Data::Object(Object(map)) 156 } 157 158 - pub fn from_cbor(cbor: &'s BTreeMap<String, Ipld>) -> Data<'s> { 159 if let Some(Ipld::String(type_field)) = cbor.get("$type") { 160 if infer_from_type(type_field) == DataModelType::Blob { 161 if let Some(blob) = cbor_to_blob(cbor) { 162 - return Data::Blob(blob); 163 } 164 } 165 } ··· 167 168 for (key, value) in cbor { 169 if key == "$type" { 170 - map.insert(key.to_smolstr(), Data::from_cbor(value)); 171 } 172 match (string_key_type_guess(key), value) { 173 (DataModelType::Null, Ipld::Null) => { ··· 183 map.insert(key.to_smolstr(), Data::Bytes(Bytes::copy_from_slice(value))); 184 } 185 (DataModelType::Blob, Ipld::Map(value)) => { 186 - map.insert(key.to_smolstr(), Object::from_cbor(value)); 187 } 188 (DataModelType::Array, Ipld::List(value)) => { 189 - map.insert(key.to_smolstr(), Data::Array(Array::from_cbor(value))); 190 } 191 (DataModelType::Object, Ipld::Map(value)) => { 192 - map.insert(key.to_smolstr(), Object::from_cbor(value)); 193 } 194 (DataModelType::String(string_type), Ipld::String(value)) => { 195 insert_string(&mut map, key, value, string_type); 196 } 197 _ => { 198 - map.insert(key.to_smolstr(), Data::from_cbor(value)); 199 } 200 } 201 } 202 203 - Data::Object(Object(map)) 204 } 205 } 206 ··· 209 key: &'s str, 210 value: &'s str, 211 string_type: LexiconStringType, 212 - ) { 213 match string_type { 214 LexiconStringType::Datetime => { 215 if let Ok(datetime) = Datetime::from_str(value) { ··· 334 map.insert(key.to_smolstr(), Data::String(parse_string(value))); 335 } 336 } 337 } 338 339 /// smarter parsing to avoid trying as many posibilities.
··· 28 Blob(Blob<'s>), 29 } 30 31 + #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)] 32 + pub enum AtDataError { 33 + #[error("floating point numbers not allowed in AT protocol data")] 34 + FloatNotAllowed, 35 + } 36 + 37 impl<'s> Data<'s> { 38 + pub fn from_json(json: &'s serde_json::Value) -> Result<Self, AtDataError> { 39 + Ok(if let Some(value) = json.as_bool() { 40 Self::Boolean(value) 41 } else if let Some(value) = json.as_i64() { 42 Self::Integer(value) 43 } else if let Some(value) = json.as_str() { 44 Self::String(AtprotoStr::new(value)) 45 } else if let Some(value) = json.as_array() { 46 + Self::Array(Array::from_json(value)?) 47 } else if let Some(value) = json.as_object() { 48 + Object::from_json(value)? 49 + } else if json.is_f64() { 50 + return Err(AtDataError::FloatNotAllowed); 51 } else { 52 Self::Null 53 + }) 54 } 55 56 + pub fn from_cbor(cbor: &'s Ipld) -> Result<Self, AtDataError> { 57 + Ok(match cbor { 58 Ipld::Null => Data::Null, 59 Ipld::Bool(bool) => Data::Boolean(*bool), 60 Ipld::Integer(int) => Data::Integer(*int as i64), 61 + Ipld::Float(_) => { 62 + return Err(AtDataError::FloatNotAllowed); 63 + } 64 Ipld::String(string) => Self::String(AtprotoStr::new(string)), 65 Ipld::Bytes(items) => Self::Bytes(Bytes::copy_from_slice(items.as_slice())), 66 + Ipld::List(iplds) => Self::Array(Array::from_cbor(iplds)?), 67 + Ipld::Map(btree_map) => Object::from_cbor(btree_map)?, 68 Ipld::Link(cid) => Self::CidLink(Cid::ipld(*cid)), 69 + }) 70 } 71 } 72 ··· 74 pub struct Array<'s>(pub Vec<Data<'s>>); 75 76 impl<'s> Array<'s> { 77 + pub fn from_json(json: &'s Vec<serde_json::Value>) -> Result<Self, AtDataError> { 78 let mut array = Vec::with_capacity(json.len()); 79 for item in json { 80 + array.push(Data::from_json(item)?); 81 } 82 + Ok(Self(array)) 83 } 84 + pub fn from_cbor(cbor: &'s Vec<Ipld>) -> Result<Self, AtDataError> { 85 let mut array = Vec::with_capacity(cbor.len()); 86 for item in cbor { 87 + array.push(Data::from_cbor(item)?); 88 } 89 + Ok(Self(array)) 90 } 91 } 92 ··· 94 pub struct Object<'s>(pub BTreeMap<SmolStr, Data<'s>>); 95 96 impl<'s> Object<'s> { 97 + pub fn from_json( 98 + json: &'s serde_json::Map<String, serde_json::Value>, 99 + ) -> Result<Data<'s>, AtDataError> { 100 if let Some(type_field) = json.get("$type").and_then(|v| v.as_str()) { 101 if infer_from_type(type_field) == DataModelType::Blob { 102 if let Some(blob) = json_to_blob(json) { 103 + return Ok(Data::Blob(blob)); 104 } 105 } 106 } ··· 108 109 for (key, value) in json { 110 if key == "$type" { 111 + map.insert(key.to_smolstr(), Data::from_json(value)?); 112 } 113 match string_key_type_guess(key) { 114 DataModelType::Null if value.is_null() => { ··· 128 if let Some(value) = value.get("$link").and_then(|v| v.as_str()) { 129 map.insert(key.to_smolstr(), Data::CidLink(Cid::Str(value.into()))); 130 } else { 131 + map.insert(key.to_smolstr(), Object::from_json(value)?); 132 } 133 } else { 134 + map.insert(key.to_smolstr(), Data::from_json(value)?); 135 } 136 } 137 DataModelType::Blob if value.is_object() => { 138 map.insert( 139 key.to_smolstr(), 140 + Object::from_json(value.as_object().unwrap())?, 141 ); 142 } 143 DataModelType::Array if value.is_array() => { 144 map.insert( 145 key.to_smolstr(), 146 + Data::Array(Array::from_json(value.as_array().unwrap())?), 147 ); 148 } 149 DataModelType::Object if value.is_object() => { 150 map.insert( 151 key.to_smolstr(), 152 + Object::from_json(value.as_object().unwrap())?, 153 ); 154 } 155 DataModelType::String(string_type) if value.is_string() => { 156 insert_string(&mut map, key, value.as_str().unwrap(), string_type); 157 } 158 _ => { 159 + map.insert(key.to_smolstr(), Data::from_json(value)?); 160 } 161 } 162 } 163 164 + Ok(Data::Object(Object(map))) 165 } 166 167 + pub fn from_cbor(cbor: &'s BTreeMap<String, Ipld>) -> Result<Data<'s>, AtDataError> { 168 if let Some(Ipld::String(type_field)) = cbor.get("$type") { 169 if infer_from_type(type_field) == DataModelType::Blob { 170 if let Some(blob) = cbor_to_blob(cbor) { 171 + return Ok(Data::Blob(blob)); 172 } 173 } 174 } ··· 176 177 for (key, value) in cbor { 178 if key == "$type" { 179 + map.insert(key.to_smolstr(), Data::from_cbor(value)?); 180 } 181 match (string_key_type_guess(key), value) { 182 (DataModelType::Null, Ipld::Null) => { ··· 192 map.insert(key.to_smolstr(), Data::Bytes(Bytes::copy_from_slice(value))); 193 } 194 (DataModelType::Blob, Ipld::Map(value)) => { 195 + map.insert(key.to_smolstr(), Object::from_cbor(value)?); 196 } 197 (DataModelType::Array, Ipld::List(value)) => { 198 + map.insert(key.to_smolstr(), Data::Array(Array::from_cbor(value)?)); 199 } 200 (DataModelType::Object, Ipld::Map(value)) => { 201 + map.insert(key.to_smolstr(), Object::from_cbor(value)?); 202 } 203 (DataModelType::String(string_type), Ipld::String(value)) => { 204 insert_string(&mut map, key, value, string_type); 205 } 206 _ => { 207 + map.insert(key.to_smolstr(), Data::from_cbor(value)?); 208 } 209 } 210 } 211 212 + Ok(Data::Object(Object(map))) 213 } 214 } 215 ··· 218 key: &'s str, 219 value: &'s str, 220 string_type: LexiconStringType, 221 + ) -> Result<(), AtDataError> { 222 match string_type { 223 LexiconStringType::Datetime => { 224 if let Ok(datetime) = Datetime::from_str(value) { ··· 343 map.insert(key.to_smolstr(), Data::String(parse_string(value))); 344 } 345 } 346 + Ok(()) 347 } 348 349 /// smarter parsing to avoid trying as many posibilities.