From f2a78babbd0e773dc88788f281c81e33f66a78cc Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 26 Oct 2020 01:52:33 +0100 Subject: [PATCH] Avoid creating owned strings in deserialization where not necessary --- ruma-client-api/src/error/kind_serde.rs | 2 +- ruma-client-api/src/r0/to_device.rs | 7 +- .../push/condition/room_member_count_is.rs | 2 +- ruma-identifiers/Cargo.toml | 1 + ruma-identifiers/src/lib.rs | 4 +- ruma-serde/src/cow.rs | 71 +++++++++++++++++++ ruma-serde/src/json_string.rs | 4 +- ruma-serde/src/lib.rs | 2 + 8 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 ruma-serde/src/cow.rs diff --git a/ruma-client-api/src/error/kind_serde.rs b/ruma-client-api/src/error/kind_serde.rs index b2deef11..7c5ca0fa 100644 --- a/ruma-client-api/src/error/kind_serde.rs +++ b/ruma-client-api/src/error/kind_serde.rs @@ -249,7 +249,7 @@ impl<'de> Deserialize<'de> for ErrCode { where D: Deserializer<'de>, { - let s = Cow::<'de, str>::deserialize(deserializer)?; + let s = ruma_serde::deserialize_cow_str(deserializer)?; Ok(s.into()) } } diff --git a/ruma-client-api/src/r0/to_device.rs b/ruma-client-api/src/r0/to_device.rs index b96b8081..f25fcee2 100644 --- a/ruma-client-api/src/r0/to_device.rs +++ b/ruma-client-api/src/r0/to_device.rs @@ -62,10 +62,9 @@ impl<'de> Deserialize<'de> for DeviceIdOrAllDevices { where D: Deserializer<'de>, { - let value = &String::deserialize(deserializer)?; - - DeviceIdOrAllDevices::try_from(&value[..]).map_err(|_| { - de::Error::invalid_value(Unexpected::Str(&value), &"a valid device identifier or '*'") + let s = ruma_serde::deserialize_cow_str(deserializer)?; + DeviceIdOrAllDevices::try_from(s.as_ref()).map_err(|_| { + de::Error::invalid_value(Unexpected::Str(&s), &"a valid device identifier or '*'") }) } } diff --git a/ruma-common/src/push/condition/room_member_count_is.rs b/ruma-common/src/push/condition/room_member_count_is.rs index a29d87ce..743e3b09 100644 --- a/ruma-common/src/push/condition/room_member_count_is.rs +++ b/ruma-common/src/push/condition/room_member_count_is.rs @@ -151,7 +151,7 @@ impl<'de> Deserialize<'de> for RoomMemberCountIs { where D: Deserializer<'de>, { - let s = String::deserialize(deserializer)?; + let s = ruma_serde::deserialize_cow_str(deserializer)?; FromStr::from_str(&s).map_err(serde::de::Error::custom) } } diff --git a/ruma-identifiers/Cargo.toml b/ruma-identifiers/Cargo.toml index 0d2cac36..37e9f014 100644 --- a/ruma-identifiers/Cargo.toml +++ b/ruma-identifiers/Cargo.toml @@ -28,6 +28,7 @@ either = { version = "1.5.3", optional = true } rand = { version = "0.7.3", optional = true } ruma-identifiers-macros = { version = "=0.17.4", path = "../ruma-identifiers-macros" } ruma-identifiers-validation = { version = "0.1.1", path = "../ruma-identifiers-validation", default-features = false } +ruma-serde = { version = "0.2.3", path = "../ruma-serde" } # Renamed so we can have a serde feature. serde1 = { package = "serde", version = "1.0.114", optional = true, features = ["derive"] } strum = { version = "0.19.2", features = ["derive"] } diff --git a/ruma-identifiers/src/lib.rs b/ruma-identifiers/src/lib.rs index d72cf903..5884c5e4 100644 --- a/ruma-identifiers/src/lib.rs +++ b/ruma-identifiers/src/lib.rs @@ -17,7 +17,7 @@ extern crate serde1 as serde; use std::convert::TryFrom; #[cfg(feature = "serde")] -use serde::de::{self, Deserialize as _, Deserializer, Unexpected}; +use serde::de::{self, Deserializer, Unexpected}; #[doc(inline)] pub use crate::{ @@ -81,7 +81,7 @@ where D: Deserializer<'de>, T: for<'a> std::convert::TryFrom<&'a str>, { - std::borrow::Cow::<'_, str>::deserialize(deserializer).and_then(|v| { + ruma_serde::deserialize_cow_str(deserializer).and_then(|v| { T::try_from(&v).map_err(|_| de::Error::invalid_value(Unexpected::Str(&v), &expected_str)) }) } diff --git a/ruma-serde/src/cow.rs b/ruma-serde/src/cow.rs new file mode 100644 index 00000000..74978e01 --- /dev/null +++ b/ruma-serde/src/cow.rs @@ -0,0 +1,71 @@ +use std::{borrow::Cow, str}; + +use serde::de::{self, Deserializer, Unexpected, Visitor}; + +pub fn deserialize_cow_str<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + deserializer.deserialize_string(CowStrVisitor) +} + +struct CowStrVisitor; + +impl<'de> Visitor<'de> for CowStrVisitor { + type Value = Cow<'de, str>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string") + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: de::Error, + { + Ok(Cow::Borrowed(v)) + } + + fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result + where + E: de::Error, + { + match str::from_utf8(v) { + Ok(s) => Ok(Cow::Borrowed(s)), + Err(_) => Err(de::Error::invalid_value(Unexpected::Bytes(v), &self)), + } + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + Ok(Cow::Owned(v.to_owned())) + } + + fn visit_string(self, v: String) -> Result + where + E: de::Error, + { + Ok(Cow::Owned(v)) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + match str::from_utf8(v) { + Ok(s) => Ok(Cow::Owned(s.to_owned())), + Err(_) => Err(de::Error::invalid_value(Unexpected::Bytes(v), &self)), + } + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: de::Error, + { + match String::from_utf8(v) { + Ok(s) => Ok(Cow::Owned(s)), + Err(e) => Err(de::Error::invalid_value(Unexpected::Bytes(&e.into_bytes()), &self)), + } + } +} diff --git a/ruma-serde/src/json_string.rs b/ruma-serde/src/json_string.rs index f4d707d1..c95cb838 100644 --- a/ruma-serde/src/json_string.rs +++ b/ruma-serde/src/json_string.rs @@ -2,7 +2,7 @@ //! string. use serde::{ - de::{Deserialize, DeserializeOwned, Deserializer, Error as _}, + de::{DeserializeOwned, Deserializer, Error as _}, ser::{Error as _, Serialize, Serializer}, }; @@ -20,6 +20,6 @@ where T: DeserializeOwned, D: Deserializer<'de>, { - let s = String::deserialize(deserializer)?; + let s = crate::deserialize_cow_str(deserializer)?; serde_json::from_str(&s).map_err(D::Error::custom) } diff --git a/ruma-serde/src/lib.rs b/ruma-serde/src/lib.rs index 0979c13e..5e0acba5 100644 --- a/ruma-serde/src/lib.rs +++ b/ruma-serde/src/lib.rs @@ -4,6 +4,7 @@ use serde::de::{Deserialize, IntoDeserializer}; pub mod can_be_empty; mod canonical_json; +mod cow; pub mod duration; pub mod empty; pub mod json_string; @@ -16,6 +17,7 @@ pub use can_be_empty::{is_empty, CanBeEmpty}; pub use canonical_json::{ to_string as to_canonical_json_string, value::CanonicalJsonValue, Error as CanonicalJsonError, }; +pub use cow::deserialize_cow_str; pub use empty::vec_as_map_of_empty; /// Check whether a value is equal to its default value.