A better Rust ATProto crate

fix for nullable fields

Orual ba9249d4 76e7ad63

Changed files
+166 -12
crates
jacquard-common
jacquard-lexicon
src
codegen
+89 -4
crates/jacquard-common/src/opt_serde_bytes_helper.rs
··· 1 1 //! Custom serde helpers for bytes::Bytes using serde_bytes 2 2 3 + use base64::{ 4 + Engine, 5 + prelude::{BASE64_STANDARD, BASE64_STANDARD_NO_PAD, BASE64_URL_SAFE, BASE64_URL_SAFE_NO_PAD}, 6 + }; 3 7 use bytes::Bytes; 4 - use serde::{Deserializer, Serializer}; 8 + use core::fmt; 9 + use serde::{ 10 + Deserializer, Serializer, 11 + de::{self, MapAccess, Visitor}, 12 + }; 5 13 6 14 /// Serialize Bytes as a CBOR byte string 7 15 pub fn serialize<S>(bytes: &Option<Bytes>, serializer: S) -> Result<S::Ok, S::Error> ··· 9 17 S: Serializer, 10 18 { 11 19 if let Some(bytes) = bytes { 12 - serde_bytes::serialize(bytes.as_ref(), serializer) 20 + if serializer.is_human_readable() { 21 + // JSON: {"$bytes": "base64 string"} 22 + use serde::ser::SerializeMap; 23 + let mut map = serializer.serialize_map(Some(1))?; 24 + map.serialize_entry("$bytes", &BASE64_STANDARD.encode(bytes))?; 25 + map.end() 26 + } else { 27 + // CBOR: raw bytes 28 + serde_bytes::serialize(bytes.as_ref(), serializer) 29 + } 13 30 } else { 14 31 serializer.serialize_none() 15 32 } ··· 20 37 where 21 38 D: Deserializer<'de>, 22 39 { 23 - let vec: Option<Vec<u8>> = serde_bytes::deserialize(deserializer)?; 24 - Ok(vec.map(Bytes::from)) 40 + if deserializer.is_human_readable() { 41 + Ok(deserializer.deserialize_map(OptBytesVisitor)?) 42 + } else { 43 + let vec: Option<Vec<u8>> = serde_bytes::deserialize(deserializer)?; 44 + Ok(vec.map(Bytes::from)) 45 + } 46 + } 47 + 48 + struct OptBytesVisitor; 49 + 50 + impl<'de> Visitor<'de> for OptBytesVisitor { 51 + type Value = Option<Bytes>; 52 + 53 + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 54 + formatter.write_str("a base64-encoded string") 55 + } 56 + 57 + fn visit_none<E>(self) -> Result<Self::Value, E> 58 + where 59 + E: de::Error, 60 + { 61 + Ok(None) 62 + } 63 + 64 + fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error> 65 + where 66 + D: Deserializer<'de>, 67 + { 68 + let vec: Vec<u8> = serde_bytes::deserialize(deserializer)?; 69 + Ok(Some(Bytes::from(vec))) 70 + } 71 + 72 + fn visit_unit<E>(self) -> Result<Self::Value, E> { 73 + Ok(None) 74 + } 75 + 76 + fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 77 + where 78 + A: MapAccess<'de>, 79 + { 80 + let mut bytes = None; 81 + 82 + while let Some(key) = map.next_key()? { 83 + match key { 84 + "$bytes" => { 85 + if bytes.is_some() { 86 + return Err(de::Error::duplicate_field("$bytes")); 87 + } 88 + let bytes_str: String = map.next_value()?; 89 + // First one should just work. rest are insurance. 90 + bytes = if let Ok(bytes) = BASE64_STANDARD.decode(&bytes_str) { 91 + Some(Bytes::from_owner(bytes)) 92 + } else if let Ok(bytes) = BASE64_STANDARD_NO_PAD.decode(&bytes_str) { 93 + Some(Bytes::from_owner(bytes)) 94 + } else if let Ok(bytes) = BASE64_URL_SAFE.decode(&bytes_str) { 95 + Some(Bytes::from_owner(bytes)) 96 + } else if let Ok(bytes) = BASE64_URL_SAFE_NO_PAD.decode(&bytes_str) { 97 + Some(Bytes::from_owner(bytes)) 98 + } else { 99 + return Err(de::Error::custom("invalid base64 string")); 100 + } 101 + } 102 + _ => { 103 + return Err(de::Error::unknown_field(key, &["$bytes"])); 104 + } 105 + } 106 + } 107 + 108 + Ok(bytes) 109 + } 25 110 }
+71 -4
crates/jacquard-common/src/serde_bytes_helper.rs
··· 1 1 //! Custom serde helpers for bytes::Bytes using serde_bytes 2 2 3 + use core::fmt; 4 + 5 + use base64::{ 6 + Engine, 7 + prelude::{BASE64_STANDARD, BASE64_STANDARD_NO_PAD, BASE64_URL_SAFE, BASE64_URL_SAFE_NO_PAD}, 8 + }; 3 9 use bytes::Bytes; 4 - use serde::{Deserializer, Serializer}; 10 + use serde::{ 11 + Deserializer, Serializer, 12 + de::{self, MapAccess, Visitor}, 13 + }; 5 14 6 15 /// Serialize Bytes as a CBOR byte string 7 16 pub fn serialize<S>(bytes: &Bytes, serializer: S) -> Result<S::Ok, S::Error> 8 17 where 9 18 S: Serializer, 10 19 { 11 - serde_bytes::serialize(bytes.as_ref(), serializer) 20 + if serializer.is_human_readable() { 21 + // JSON: {"$bytes": "base64 string"} 22 + use serde::ser::SerializeMap; 23 + let mut map = serializer.serialize_map(Some(1))?; 24 + map.serialize_entry("$bytes", &BASE64_STANDARD.encode(bytes))?; 25 + map.end() 26 + } else { 27 + // CBOR: raw bytes 28 + serde_bytes::serialize(bytes.as_ref(), serializer) 29 + } 12 30 } 13 31 14 32 /// Deserialize Bytes from a CBOR byte string ··· 16 34 where 17 35 D: Deserializer<'de>, 18 36 { 19 - let vec: Vec<u8> = serde_bytes::deserialize(deserializer)?; 20 - Ok(Bytes::from(vec)) 37 + if deserializer.is_human_readable() { 38 + Ok(deserializer.deserialize_map(BytesVisitor)?) 39 + } else { 40 + let vec: Vec<u8> = serde_bytes::deserialize(deserializer)?; 41 + Ok(Bytes::from(vec)) 42 + } 43 + } 44 + 45 + struct BytesVisitor; 46 + 47 + impl<'de> Visitor<'de> for BytesVisitor { 48 + type Value = Bytes; 49 + 50 + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 51 + formatter.write_str("a base64-encoded string") 52 + } 53 + 54 + fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 55 + where 56 + A: MapAccess<'de>, 57 + { 58 + let mut bytes = None; 59 + 60 + while let Some(key) = map.next_key()? { 61 + match key { 62 + "$bytes" => { 63 + if bytes.is_some() { 64 + return Err(de::Error::duplicate_field("$bytes")); 65 + } 66 + let bytes_str: String = map.next_value()?; 67 + // First one should just work. rest are insurance. 68 + bytes = if let Ok(bytes) = BASE64_STANDARD.decode(&bytes_str) { 69 + Some(Bytes::from_owner(bytes)) 70 + } else if let Ok(bytes) = BASE64_STANDARD_NO_PAD.decode(&bytes_str) { 71 + Some(Bytes::from_owner(bytes)) 72 + } else if let Ok(bytes) = BASE64_URL_SAFE.decode(&bytes_str) { 73 + Some(Bytes::from_owner(bytes)) 74 + } else if let Ok(bytes) = BASE64_URL_SAFE_NO_PAD.decode(&bytes_str) { 75 + Some(Bytes::from_owner(bytes)) 76 + } else { 77 + return Err(de::Error::custom("invalid base64 string")); 78 + } 79 + } 80 + _ => { 81 + return Err(de::Error::unknown_field(key, &["$bytes"])); 82 + } 83 + } 84 + } 85 + 86 + bytes.ok_or_else(|| de::Error::missing_field("$bytes")) 87 + } 21 88 }
+6 -4
crates/jacquard-lexicon/src/codegen/structs.rs
··· 294 294 nsid: &str, 295 295 parent_type_name: &str, 296 296 obj: &LexObject<'static>, 297 - is_builder: bool, 297 + _is_builder: bool, 298 298 ) -> Result<TokenStream> { 299 299 let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]); 300 + let nullable = obj.nullable.as_ref().map(|n| n.as_slice()).unwrap_or(&[]); 300 301 301 302 let mut fields = Vec::new(); 302 303 for (field_name, field_type) in &obj.properties { 303 304 let is_required = required.contains(field_name); 305 + let is_nullable = nullable.contains(field_name); 304 306 let field_tokens = self.generate_field( 305 307 nsid, 306 308 parent_type_name, 307 309 field_name, 308 310 field_type, 309 311 is_required, 310 - is_builder, 312 + is_nullable, 311 313 )?; 312 314 fields.push(field_tokens); 313 315 } ··· 323 325 field_name: &str, 324 326 field_type: &LexObjectProperty<'static>, 325 327 is_required: bool, 326 - _is_builder: bool, 328 + is_nullable: bool, 327 329 ) -> Result<TokenStream> { 328 330 if field_name.is_empty() { 329 331 eprintln!( ··· 337 339 self.property_to_rust_type(nsid, parent_type_name, field_name, field_type)?; 338 340 let needs_lifetime = self.property_needs_lifetime(field_type); 339 341 340 - let rust_type = if is_required { 342 + let rust_type = if is_required && !is_nullable { 341 343 rust_type 342 344 } else { 343 345 quote! { std::option::Option<#rust_type> }