From f664a0132d76acf810cbb6982aa42f6c5c283463 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 30 Apr 2020 17:23:50 +0200 Subject: [PATCH] Import some things from ruma-events --- src/empty.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 src/empty.rs diff --git a/src/empty.rs b/src/empty.rs new file mode 100644 index 00000000..11c71a15 --- /dev/null +++ b/src/empty.rs @@ -0,0 +1,89 @@ +use std::fmt::{self, Formatter}; + +use serde::{ + de::{Deserialize, Deserializer, MapAccess, Visitor}, + ser::{Serialize, SerializeMap, Serializer}, +}; + +/// A meaningless value that serializes to an empty JSON object. +/// +/// This type is used in a few places where the Matrix specification requires an empty JSON object, +/// but it's wasteful to represent it as a `BTreeMap` in Rust code. +#[derive(Clone, Debug, PartialEq)] +pub struct Empty; + +impl Serialize for Empty { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_map(Some(0))?.end() + } +} + +impl<'de> Deserialize<'de> for Empty { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct EmptyMapVisitor; + + impl<'de> Visitor<'de> for EmptyMapVisitor { + type Value = Empty; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "an object/map") + } + + fn visit_map(self, _map: A) -> Result + where + A: MapAccess<'de>, + { + Ok(Empty) + } + } + + deserializer.deserialize_map(EmptyMapVisitor) + } +} + +/// Serde serialization and deserialization functions that map a `Vec` to a +/// `BTreeMap`. +/// +/// 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 std::collections::BTreeMap; + + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::Empty; + + #[allow(clippy::ptr_arg)] + pub fn serialize( + vec: &Vec, + serializer: S, + ) -> Result + where + S: Serializer, + T: Serialize + Eq + Ord, + { + 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> + Eq + Ord, + { + BTreeMap::::deserialize(deserializer) + .map(|hashmap| hashmap.into_iter().map(|(k, _)| k).collect()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 88ff7356..aff31deb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,65 @@ //! De-/serialization helpers for other ruma crates +use serde::de::{Deserialize, IntoDeserializer}; + pub mod duration; +pub mod empty; pub mod json_string; pub mod time; pub mod urlencoded; +pub use empty::vec_as_map_of_empty; + +/// Check whether a value is equal to its default value. pub fn is_default(val: &T) -> bool { val == &T::default() } + +/// Simply returns `true`. +/// +/// Useful for `#[serde(default = ...)]`. +pub fn default_true() -> bool { + true +} + +/// 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)?; + // TODO: Switch to and remove this attribute `opt.as_deref()` once MSRV is >= 1.40 + #[allow(clippy::option_as_ref_deref, clippy::unknown_clippy_lints)] + 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), + } +} + +#[cfg(test)] +use std::fmt::Debug; + +#[cfg(test)] +use serde::{de::DeserializeOwned, Serialize}; + +#[cfg(test)] +pub fn serde_json_eq(de: T, se: serde_json::Value) +where + T: Clone + Debug + PartialEq + Serialize + DeserializeOwned, +{ + assert_eq!(se, serde_json::to_value(de.clone()).unwrap()); + assert_eq!(de, serde_json::from_value(se).unwrap()); +}