From ea778a0fdccf41c59d0fa59607717c5fbdf4c99e Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 28 Nov 2020 02:01:12 +0100 Subject: [PATCH] serde: Move string-related helpers into a new module --- ruma-serde/src/lib.rs | 84 ++------------------------------------- ruma-serde/src/strings.rs | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 80 deletions(-) create mode 100644 ruma-serde/src/strings.rs diff --git a/ruma-serde/src/lib.rs b/ruma-serde/src/lib.rs index 07f0efa6..9e64f9c8 100644 --- a/ruma-serde/src/lib.rs +++ b/ruma-serde/src/lib.rs @@ -1,15 +1,5 @@ //! De-/serialization helpers for other ruma crates -use js_int::Int; -use serde::{ - de::{Error, IntoDeserializer}, - Deserialize, -}; -use std::{ - collections::BTreeMap, - convert::{TryFrom, TryInto}, -}; - pub mod can_be_empty; mod canonical_json; mod cow; @@ -17,6 +7,7 @@ pub mod duration; pub mod empty; pub mod json_string; pub mod single_element_seq; +mod strings; pub mod test; pub mod time; pub mod urlencoded; @@ -29,6 +20,9 @@ pub use canonical_json::{ }; pub use cow::deserialize_cow_str; pub use empty::vec_as_map_of_empty; +pub use strings::{ + btreemap_int_or_string_to_int_values, empty_string_as_none, int_or_string_to_int, +}; /// Check whether a value is equal to its default value. pub fn is_default(val: &T) -> bool { @@ -49,73 +43,3 @@ pub fn default_true() -> bool { pub fn is_true(b: &bool) -> bool { *b } - -/// 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: -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)?; - match opt.as_deref() { - 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), - } -} - -// Helper type for deserialize_int_or_string_to_int -#[derive(Deserialize)] -#[serde(untagged)] -enum IntOrString<'a> { - Num(Int), - Str(&'a str), -} - -impl TryFrom> for Int { - type Error = js_int::ParseIntError; - - fn try_from(input: IntOrString) -> Result { - match input { - IntOrString::Num(n) => Ok(n), - IntOrString::Str(string) => string.parse(), - } - } -} - -/// Take either an integer number or a string and deserialize to an integer number. -/// -/// To be used like this: -/// `#[serde(deserialize_with = "int_or_string_to_int")]` -pub fn int_or_string_to_int<'de, D>(de: D) -> Result -where - D: serde::Deserializer<'de>, -{ - IntOrString::deserialize(de)?.try_into().map_err(D::Error::custom) -} - -/// Take a BTreeMap with values of either an integer number or a string and deserialize -/// those to integer numbers. -/// -/// To be used like this: -/// `#[serde(deserialize_with = "btreemap_int_or_string_to_int_values")]` -pub fn btreemap_int_or_string_to_int_values<'de, D, T>(de: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, - T: serde::Deserialize<'de> + Ord, -{ - BTreeMap::::deserialize(de)? - .into_iter() - .map(|(k, v)| v.try_into().map(|n| (k, n)).map_err(D::Error::custom)) - .collect() -} diff --git a/ruma-serde/src/strings.rs b/ruma-serde/src/strings.rs new file mode 100644 index 00000000..30d6c17b --- /dev/null +++ b/ruma-serde/src/strings.rs @@ -0,0 +1,80 @@ +use std::{ + collections::BTreeMap, + convert::{TryFrom, TryInto}, +}; + +use js_int::Int; +use serde::{ + de::{Deserializer, Error as _, IntoDeserializer as _}, + Deserialize, +}; + +/// 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: +pub fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de>, +{ + let opt = Option::::deserialize(de)?; + match opt.as_deref() { + 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), + } +} + +// Helper type for deserialize_int_or_string_to_int +#[derive(Deserialize)] +#[serde(untagged)] +enum IntOrString<'a> { + Num(Int), + Str(&'a str), +} + +impl TryFrom> for Int { + type Error = js_int::ParseIntError; + + fn try_from(input: IntOrString) -> Result { + match input { + IntOrString::Num(n) => Ok(n), + IntOrString::Str(string) => string.parse(), + } + } +} + +/// Take either an integer number or a string and deserialize to an integer number. +/// +/// To be used like this: +/// `#[serde(deserialize_with = "int_or_string_to_int")]` +pub fn int_or_string_to_int<'de, D>(de: D) -> Result +where + D: Deserializer<'de>, +{ + IntOrString::deserialize(de)?.try_into().map_err(D::Error::custom) +} + +/// Take a BTreeMap with values of either an integer number or a string and deserialize +/// those to integer numbers. +/// +/// To be used like this: +/// `#[serde(deserialize_with = "btreemap_int_or_string_to_int_values")]` +pub fn btreemap_int_or_string_to_int_values<'de, D, T>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + Ord, +{ + BTreeMap::::deserialize(de)? + .into_iter() + .map(|(k, v)| v.try_into().map(|n| (k, n)).map_err(D::Error::custom)) + .collect() +}