From 665fe4f4f41f928b0a7251b7c2d7a4676108f660 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 22 Oct 2019 23:56:03 +0200 Subject: [PATCH] Move (de)serialization helpers to util module --- src/ignored_user_list.rs | 2 +- src/lib.rs | 66 +------------------------------------ src/push_rules.rs | 2 +- src/room/canonical_alias.rs | 2 +- src/room/create.rs | 2 +- src/room/name.rs | 2 +- src/room/server_acl.rs | 2 +- src/util.rs | 66 ++++++++++++++++++++++++++++++++++++- 8 files changed, 72 insertions(+), 72 deletions(-) diff --git a/src/ignored_user_list.rs b/src/ignored_user_list.rs index 1661d768..62e1bf4e 100644 --- a/src/ignored_user_list.rs +++ b/src/ignored_user_list.rs @@ -3,7 +3,7 @@ use ruma_identifiers::UserId; use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; -use crate::{vec_as_map_of_empty, Event as _, EventType, FromRaw}; +use crate::{util::vec_as_map_of_empty, Event as _, EventType, FromRaw}; /// A list of users to ignore. #[derive(Clone, Debug, PartialEq)] diff --git a/src/lib.rs b/src/lib.rs index 8dbccc4c..73c380f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,7 +123,7 @@ use std::{ use js_int::UInt; use ruma_identifiers::{EventId, RoomId, UserId}; use serde::{ - de::{DeserializeOwned, Error as SerdeError, IntoDeserializer, MapAccess, Visitor}, + de::{DeserializeOwned, Error as SerdeError, MapAccess, Visitor}, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer, }; @@ -853,70 +853,6 @@ impl<'de> Deserialize<'de> for Algorithm { } } -/// Serde deserialization decorator to map empty Strings to None, -/// and forward non-empty Strings to the Deserialize implementation for T. -/// Useful for the typical -/// "A room with an X event with an absent, null, or empty Y field -/// should be treated the same as a room with no such event." -/// formulation in the spec. -/// -/// To be used like this: -/// `#[serde(deserialize_with = "empty_string_as_none"]` -/// Relevant serde issue: https://github.com/serde-rs/serde/issues/1425 -fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, - T: serde::Deserialize<'de>, -{ - let opt = Option::::deserialize(de)?; - let opt = opt.as_ref().map(String::as_str); - match opt { - None | Some("") => Ok(None), - // If T = String, like in m.room.name, the second deserialize is actually superfluous. - // TODO: optimize that somehow? - Some(s) => T::deserialize(s.into_deserializer()).map(Some), - } -} - -/// Serde serialization and deserialization functions that map a `Vec` to a `HashMap`. -/// -/// The Matrix spec sometimes specifies lists as hash maps so the list entries can be expanded with -/// attributes without breaking compatibility. As that would be a breaking change for ruma's event -/// types anyway, we convert them to `Vec`s for simplicity, using this module. -/// -/// To be used as `#[serde(with = "vec_as_map_of_empty")]`. -mod vec_as_map_of_empty { - use super::Empty; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use std::{collections::HashMap, hash::Hash}; - - #[allow(clippy::ptr_arg)] - pub fn serialize(vec: &Vec, serializer: S) -> Result - where - S: Serializer, - T: Serialize + Hash + Eq, - { - vec.iter() - .map(|v| (v, Empty)) - .collect::>() - .serialize(serializer) - } - - pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - T: Deserialize<'de> + Hash + Eq, - { - HashMap::::deserialize(deserializer) - .map(|hashmap| hashmap.into_iter().map(|(k, _)| k).collect()) - } -} - -/// Used to default the `bool` fields to `true` during deserialization. -fn default_true() -> bool { - true -} - #[cfg(test)] mod tests { use serde_json::{from_str, to_string}; diff --git a/src/push_rules.rs b/src/push_rules.rs index a3f9b9c3..fa00a389 100644 --- a/src/push_rules.rs +++ b/src/push_rules.rs @@ -12,7 +12,7 @@ use serde::{ }; use serde_json::{from_value, Value}; -use super::{default_true, FromStrError}; +use crate::{util::default_true, FromStrError}; ruma_event! { /// Describes all push rules for a user. diff --git a/src/room/canonical_alias.rs b/src/room/canonical_alias.rs index 02ea676a..42208cc1 100644 --- a/src/room/canonical_alias.rs +++ b/src/room/canonical_alias.rs @@ -5,7 +5,7 @@ use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId}; use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; use serde_json::Value; -use crate::{empty_string_as_none, Event, EventType, FromRaw}; +use crate::{util::empty_string_as_none, Event, EventType, FromRaw}; /// Informs the room as to which alias is the canonical one. #[derive(Clone, Debug, PartialEq)] diff --git a/src/room/create.rs b/src/room/create.rs index 59efb291..6ec2f3ce 100644 --- a/src/room/create.rs +++ b/src/room/create.rs @@ -6,7 +6,7 @@ use ruma_events_macros::ruma_event; use ruma_identifiers::{EventId, RoomId, RoomVersionId, UserId}; use serde::{Deserialize, Serialize}; -use crate::default_true; +use crate::util::default_true; ruma_event! { /// This is the first event in a room and cannot be changed. It acts as the root of all other diff --git a/src/room/name.rs b/src/room/name.rs index a888a1b9..df2e4197 100644 --- a/src/room/name.rs +++ b/src/room/name.rs @@ -5,7 +5,7 @@ use ruma_identifiers::{EventId, RoomId, UserId}; use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; use serde_json::Value; -use crate::{empty_string_as_none, Event as _, EventType, FromRaw, InvalidInput}; +use crate::{util::empty_string_as_none, Event as _, EventType, FromRaw, InvalidInput}; /// A human-friendly room name designed to be displayed to the end-user. #[derive(Clone, Debug, PartialEq)] diff --git a/src/room/server_acl.rs b/src/room/server_acl.rs index 4869c562..5a1a054a 100644 --- a/src/room/server_acl.rs +++ b/src/room/server_acl.rs @@ -5,7 +5,7 @@ use ruma_identifiers::{EventId, RoomId, UserId}; use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; use serde_json::Value; -use crate::{default_true, Event as _, EventType, FromRaw}; +use crate::{util::default_true, Event as _, EventType, FromRaw}; /// An event to indicate which servers are permitted to participate in the room. #[derive(Clone, Debug, PartialEq)] diff --git a/src/util.rs b/src/util.rs index 953ee7ca..7305422a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,4 @@ -use serde::de::DeserializeOwned; +use serde::de::{Deserialize, DeserializeOwned, IntoDeserializer}; use crate::TryFromRaw; @@ -28,3 +28,67 @@ pub fn get_field( ) .map_err(serde_json_error_to_generic_de_error) } + +/// Serde deserialization decorator to map empty Strings to None, +/// and forward non-empty Strings to the Deserialize implementation for T. +/// Useful for the typical +/// "A room with an X event with an absent, null, or empty Y field +/// should be treated the same as a room with no such event." +/// formulation in the spec. +/// +/// To be used like this: +/// `#[serde(deserialize_with = "empty_string_as_none"]` +/// Relevant serde issue: https://github.com/serde-rs/serde/issues/1425 +pub fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, + T: serde::Deserialize<'de>, +{ + let opt = Option::::deserialize(de)?; + let opt = opt.as_ref().map(String::as_str); + match opt { + None | Some("") => Ok(None), + // If T = String, like in m.room.name, the second deserialize is actually superfluous. + // TODO: optimize that somehow? + Some(s) => T::deserialize(s.into_deserializer()).map(Some), + } +} + +/// Serde serialization and deserialization functions that map a `Vec` to a `HashMap`. +/// +/// The Matrix spec sometimes specifies lists as hash maps so the list entries can be expanded with +/// attributes without breaking compatibility. As that would be a breaking change for ruma's event +/// types anyway, we convert them to `Vec`s for simplicity, using this module. +/// +/// To be used as `#[serde(with = "vec_as_map_of_empty")]`. +pub mod vec_as_map_of_empty { + use crate::Empty; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use std::{collections::HashMap, hash::Hash}; + + #[allow(clippy::ptr_arg)] + pub fn serialize(vec: &Vec, serializer: S) -> Result + where + S: Serializer, + T: Serialize + Hash + Eq, + { + vec.iter() + .map(|v| (v, Empty)) + .collect::>() + .serialize(serializer) + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: Deserialize<'de> + Hash + Eq, + { + HashMap::::deserialize(deserializer) + .map(|hashmap| hashmap.into_iter().map(|(k, _)| k).collect()) + } +} + +/// Used to default the `bool` fields to `true` during deserialization. +pub fn default_true() -> bool { + true +}