diff --git a/rbx_types/CHANGELOG.md b/rbx_types/CHANGELOG.md index aa139fe2..4ac3d09d 100644 --- a/rbx_types/CHANGELOG.md +++ b/rbx_types/CHANGELOG.md @@ -4,9 +4,11 @@ * Implement `IntoIterator` for `&Attributes`. ([#386]) * Implement `Extend<(String, Variant)>` for `Attributes`. ([#386]) * Implement `clear` and `drain` for `Attributes`. ([#409]) +* Implement `Serialize` and `Deserialize` for `SharedString` ([#414]) [#386]: https://github.com/rojo-rbx/rbx-dom/pull/386 [#409]: https://github.com/rojo-rbx/rbx-dom/pull/409 +[#414]: https://github.com/rojo-rbx/rbx-dom/pull/414 ## 1.8.0 (2024-01-16) * Add `len` and `is_empty` methods to `Attributes` struct. ([#377]) diff --git a/rbx_types/src/shared_string.rs b/rbx_types/src/shared_string.rs index 57e34d14..c449bbee 100644 --- a/rbx_types/src/shared_string.rs +++ b/rbx_types/src/shared_string.rs @@ -148,29 +148,35 @@ impl fmt::Display for SharedStringHash { } #[cfg(feature = "serde")] -pub(crate) mod variant_serialization { +pub(crate) mod serde_impl { use super::*; - use serde::de::Error as _; - use serde::ser::Error as _; - use serde::{Deserializer, Serializer}; + use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; - pub fn serialize(_value: &SharedString, _serializer: S) -> Result - where - S: Serializer, - { - Err(S::Error::custom( - "SharedString cannot be serialized as part of a Variant", - )) + impl Serialize for SharedString { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + let encoded = base64::encode(self.data()); + + serializer.serialize_str(&encoded) + } else { + self.data().serialize(serializer) + } + } } - pub fn deserialize<'de, D>(_deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Err(D::Error::custom( - "SharedString cannot be deserialized as part of a Variant", - )) + impl<'de> Deserialize<'de> for SharedString { + fn deserialize>(deserializer: D) -> Result { + if deserializer.is_human_readable() { + let encoded = <&str>::deserialize(deserializer)?; + let buffer = base64::decode(encoded).map_err(D::Error::custom)?; + + Ok(SharedString::new(buffer)) + } else { + let buffer = >::deserialize(deserializer)?; + Ok(SharedString::new(buffer)) + } + } } } @@ -199,4 +205,41 @@ mod test { let _y = SharedString::new(vec![5, 6, 7, 1]); } } + + #[cfg(feature = "serde")] + #[test] + fn serde_human() { + let sstr = SharedString::new(b"a test string".to_vec()); + let serialized = serde_json::to_string(&sstr).unwrap(); + + assert_eq!(serialized, r#""YSB0ZXN0IHN0cmluZw==""#); + + let deserialized: SharedString = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(sstr, deserialized); + } + + #[cfg(feature = "serde")] + #[test] + fn serde_non_human() { + use std::{io::Write, mem}; + + let sstr = SharedString::new(b"a test string".to_vec()); + let data = sstr.data(); + let serialized = bincode::serialize(&sstr).unwrap(); + + // Write the length of the string as little-endian u64 followed by the + // bytes of the string. This is analoglous to how bincode does. + let mut expected = Vec::with_capacity(mem::size_of::() + data.len()); + expected + .write_all(&(data.len() as u64).to_le_bytes()) + .unwrap(); + expected.write_all(data).unwrap(); + + assert_eq!(serialized, expected); + + let deserialized: SharedString = bincode::deserialize(&serialized).unwrap(); + + assert_eq!(sstr, deserialized); + } } diff --git a/rbx_types/src/variant.rs b/rbx_types/src/variant.rs index 9dd3155b..597c8747 100644 --- a/rbx_types/src/variant.rs +++ b/rbx_types/src/variant.rs @@ -114,10 +114,6 @@ make_variant! { Ref(Ref), Region3(Region3), Region3int16(Region3int16), - #[cfg_attr( - feature = "serde", - serde(with = "crate::shared_string::variant_serialization"), - )] SharedString(SharedString), String(String), UDim(UDim),