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