From c0e751669fb64067b12dde8aec185d368479d939 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Apr 2020 19:47:59 +0200 Subject: [PATCH] Initial commit (import code from ruma-client-api) Co-authored-by: Isaiah Inuwa --- .gitignore | 9 +++ Cargo.toml | 20 ++++++ src/duration.rs | 4 ++ src/duration/opt_ms.rs | 102 ++++++++++++++++++++++++++++ src/duration/secs.rs | 69 +++++++++++++++++++ src/json_string.rs | 24 +++++++ src/lib.rs | 9 +++ src/time.rs | 4 ++ src/time/ms_since_unix_epoch.rs | 78 +++++++++++++++++++++ src/time/opt_ms_since_unix_epoch.rs | 100 +++++++++++++++++++++++++++ 10 files changed, 419 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/duration.rs create mode 100644 src/duration/opt_ms.rs create mode 100644 src/duration/secs.rs create mode 100644 src/json_string.rs create mode 100644 src/lib.rs create mode 100644 src/time.rs create mode 100644 src/time/ms_since_unix_epoch.rs create mode 100644 src/time/opt_ms_since_unix_epoch.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1e4c8083 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/target + + +#Added by cargo +# +#already existing elements were commented out + +#/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..1a4a25a8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ruma-serde" +description = "De-/serialization helpers for other ruma crates" +documentation = "https://docs.rs/ruma-serde" +license = "MIT" +authors = [ + "Jonas Platte ", + "Isaiah Inuwa ", +] +version = "0.1.0" +repository = "https://github.com/ruma/ruma-serde" +edition = "2018" + +[dependencies] +js_int = { version = "0.1.4", features = ["serde"] } +serde = "1.0.106" +serde_json = "1.0.51" + +[dev-dependencies] +serde = { version = "1.0.106", features = ["derive"] } diff --git a/src/duration.rs b/src/duration.rs new file mode 100644 index 00000000..d8c895fa --- /dev/null +++ b/src/duration.rs @@ -0,0 +1,4 @@ +//! De-/serialization functions for `std::time::Duration` objects + +pub mod opt_ms; +pub mod secs; diff --git a/src/duration/opt_ms.rs b/src/duration/opt_ms.rs new file mode 100644 index 00000000..1e8d6462 --- /dev/null +++ b/src/duration/opt_ms.rs @@ -0,0 +1,102 @@ +//! De-/serialization functions for `Option` objects represented as milliseconds. +//! Delegates to `js_int::UInt` to ensure integer size is within bounds. + +use std::{convert::TryFrom, time::Duration}; + +use js_int::UInt; +use serde::{ + de::{Deserialize, Deserializer}, + ser::{Error, Serialize, Serializer}, +}; + +/// Serialize an Option. +/// +/// Will fail if integer is greater than the maximum integer that can be +/// unambiguously represented by an f64. +pub fn serialize(opt_duration: &Option, serializer: S) -> Result +where + S: Serializer, +{ + match opt_duration { + Some(duration) => match UInt::try_from(duration.as_millis()) { + Ok(uint) => uint.serialize(serializer), + Err(err) => Err(S::Error::custom(err)), + }, + None => serializer.serialize_none(), + } +} + +/// Deserializes an Option. +/// +/// Will fail if integer is greater than the maximum integer that can be +/// unambiguously represented by an f64. +pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Option::::deserialize(deserializer)? + .map(|millis| Duration::from_millis(millis.into()))) +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use serde::{Deserialize, Serialize}; + use serde_json::json; + + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] + struct DurationTest { + #[serde(with = "super", default, skip_serializing_if = "Option::is_none")] + timeout: Option, + } + + #[test] + fn test_deserialize_some() { + let json = json!({ "timeout": 3000 }); + + assert_eq!( + serde_json::from_value::(json).unwrap(), + DurationTest { + timeout: Some(Duration::from_millis(3000)) + }, + ); + } + + #[test] + fn test_deserialize_none_by_absence() { + let json = json!({}); + + assert_eq!( + serde_json::from_value::(json).unwrap(), + DurationTest { timeout: None }, + ); + } + + #[test] + fn test_deserialize_none_by_null() { + let json = json!({ "timeout": null }); + + assert_eq!( + serde_json::from_value::(json).unwrap(), + DurationTest { timeout: None }, + ); + } + + #[test] + fn test_serialize_some() { + let request = DurationTest { + timeout: Some(Duration::new(2, 0)), + }; + assert_eq!( + serde_json::to_value(&request).unwrap(), + json!({ "timeout": 2000 }) + ); + } + + #[test] + fn test_serialize_none() { + let request = DurationTest { timeout: None }; + assert_eq!(serde_json::to_value(&request).unwrap(), json!({})); + } +} diff --git a/src/duration/secs.rs b/src/duration/secs.rs new file mode 100644 index 00000000..830eb4d5 --- /dev/null +++ b/src/duration/secs.rs @@ -0,0 +1,69 @@ +//! De-/serialization functions for `Option` objects represented as milliseconds. +//! Delegates to `js_int::UInt` to ensure integer size is within bounds. + +use std::{convert::TryFrom, time::Duration}; + +use js_int::UInt; +use serde::{ + de::{Deserialize, Deserializer}, + ser::{Error, Serialize, Serializer}, +}; + +/// Serializes a Duration to an integer representing seconds. +/// +/// Will fail if integer is greater than the maximum integer that can be +/// unambiguously represented by an f64. +pub fn serialize(duration: &Duration, serializer: S) -> Result +where + S: Serializer, +{ + match UInt::try_from(duration.as_secs()) { + Ok(uint) => uint.serialize(serializer), + Err(err) => Err(S::Error::custom(err)), + } +} + +/// Deserializes an integer representing seconds into a Duration. +/// +/// Will fail if integer is greater than the maximum integer that can be +/// unambiguously represented by an f64. +pub fn deserialize<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + UInt::deserialize(deserializer).map(|secs| Duration::from_secs(secs.into())) +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use serde::{Deserialize, Serialize}; + use serde_json::json; + + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] + struct DurationTest { + #[serde(with = "super")] + timeout: Duration, + } + + #[test] + fn test_deserialize() { + let json = json!({ "timeout": 3 }); + + assert_eq!( + serde_json::from_value::(json).unwrap(), + DurationTest { + timeout: Duration::from_secs(3) + }, + ); + } + + #[test] + fn test_serialize() { + let test = DurationTest { + timeout: Duration::from_millis(7000), + }; + assert_eq!(serde_json::to_value(test).unwrap(), json!({ "timeout": 7 }),); + } +} diff --git a/src/json_string.rs b/src/json_string.rs new file mode 100644 index 00000000..e5fabe70 --- /dev/null +++ b/src/json_string.rs @@ -0,0 +1,24 @@ +//! De-/serialization functions to and from json strings, allows the type to be used as a query string. + +use serde::{ + de::{Deserialize, DeserializeOwned, Deserializer, Error as _}, + ser::{Error as _, Serialize, Serializer}, +}; + +pub fn serialize(filter: T, serializer: S) -> Result +where + T: Serialize, + S: Serializer, +{ + let json = serde_json::to_string(&filter).map_err(S::Error::custom)?; + serializer.serialize_str(&json) +} + +pub fn deserialize<'de, T, D>(deserializer: D) -> Result +where + T: DeserializeOwned, + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + serde_json::from_str(&s).map_err(D::Error::custom) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..555909aa --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +//! De-/serialization helpers for other ruma crates + +pub mod duration; +pub mod json_string; +pub mod time; + +pub fn is_default(val: &T) -> bool { + val == &T::default() +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 00000000..e60bb839 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,4 @@ +//! De-/serialization functions for `std::time::SystemTime` objects + +pub mod ms_since_unix_epoch; +pub mod opt_ms_since_unix_epoch; diff --git a/src/time/ms_since_unix_epoch.rs b/src/time/ms_since_unix_epoch.rs new file mode 100644 index 00000000..ea1b79cc --- /dev/null +++ b/src/time/ms_since_unix_epoch.rs @@ -0,0 +1,78 @@ +//! De-/serialization functions for `std::time::SystemTime` objects represented as milliseconds +//! since the UNIX epoch. Delegates to `js_int::UInt` to ensure integer size is within bounds. + +use std::{ + convert::TryFrom, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use js_int::UInt; +use serde::{ + de::{Deserialize, Deserializer}, + ser::{Error, Serialize, Serializer}, +}; + +/// Serialize a SystemTime. +/// +/// Will fail if integer is greater than the maximum integer that can be unambiguously represented +/// by an f64. +pub fn serialize(time: &SystemTime, serializer: S) -> Result +where + S: Serializer, +{ + // If this unwrap fails, the system this is executed is completely broken. + let time_since_epoch = time.duration_since(UNIX_EPOCH).unwrap(); + match UInt::try_from(time_since_epoch.as_millis()) { + Ok(uint) => uint.serialize(serializer), + Err(err) => Err(S::Error::custom(err)), + } +} + +/// Deserializes a SystemTime. +/// +/// Will fail if integer is greater than the maximum integer that can be unambiguously represented +/// by an f64. +pub fn deserialize<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let millis = UInt::deserialize(deserializer)?; + Ok(UNIX_EPOCH + Duration::from_millis(millis.into())) +} + +#[cfg(test)] +mod tests { + use std::time::{Duration, SystemTime, UNIX_EPOCH}; + + use serde::{Deserialize, Serialize}; + use serde_json::json; + + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] + struct SystemTimeTest { + #[serde(with = "super")] + timestamp: SystemTime, + } + + #[test] + fn test_deserialize() { + let json = json!({ "timestamp": 3000 }); + + assert_eq!( + serde_json::from_value::(json).unwrap(), + SystemTimeTest { + timestamp: UNIX_EPOCH + Duration::from_millis(3000), + }, + ); + } + + #[test] + fn test_serialize() { + let request = SystemTimeTest { + timestamp: UNIX_EPOCH + Duration::new(2, 0), + }; + assert_eq!( + serde_json::to_value(&request).unwrap(), + json!({ "timestamp": 2000 }) + ); + } +} diff --git a/src/time/opt_ms_since_unix_epoch.rs b/src/time/opt_ms_since_unix_epoch.rs new file mode 100644 index 00000000..787455ac --- /dev/null +++ b/src/time/opt_ms_since_unix_epoch.rs @@ -0,0 +1,100 @@ +//! De-/serialization functions for `Option` objects represented as +//! milliseconds since the UNIX epoch. Delegates to `js_int::UInt` to ensure integer size is within +//! bounds. + +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use js_int::UInt; +use serde::{ + de::{Deserialize, Deserializer}, + ser::{Serialize, Serializer}, +}; + +/// Serialize an `Option`. +/// +/// Will fail if integer is greater than the maximum integer that can be unambiguously represented +/// by an f64. +pub fn serialize(opt_time: &Option, serializer: S) -> Result +where + S: Serializer, +{ + match opt_time { + Some(time) => super::ms_since_unix_epoch::serialize(time, serializer), + None => Option::::serialize(&None, serializer), + } +} + +/// Deserializes an `Option`. +/// +/// Will fail if integer is greater than the maximum integer that can be unambiguously represented +/// by an f64. +pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Option::::deserialize(deserializer)? + .map(|millis| UNIX_EPOCH + Duration::from_millis(millis.into()))) +} + +#[cfg(test)] +mod tests { + use std::time::{Duration, SystemTime, UNIX_EPOCH}; + + use serde::{Deserialize, Serialize}; + use serde_json::json; + + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] + struct SystemTimeTest { + #[serde(with = "super", default, skip_serializing_if = "Option::is_none")] + timestamp: Option, + } + + #[test] + fn test_deserialize_some() { + let json = json!({ "timestamp": 3000 }); + + assert_eq!( + serde_json::from_value::(json).unwrap(), + SystemTimeTest { + timestamp: Some(UNIX_EPOCH + Duration::from_millis(3000)) + }, + ); + } + + #[test] + fn test_deserialize_none_by_absence() { + let json = json!({}); + + assert_eq!( + serde_json::from_value::(json).unwrap(), + SystemTimeTest { timestamp: None }, + ); + } + + #[test] + fn test_deserialize_none_by_null() { + let json = json!({ "timestamp": null }); + + assert_eq!( + serde_json::from_value::(json).unwrap(), + SystemTimeTest { timestamp: None }, + ); + } + + #[test] + fn test_serialize_some() { + let request = SystemTimeTest { + timestamp: Some(UNIX_EPOCH + Duration::new(2, 0)), + }; + assert_eq!( + serde_json::to_value(&request).unwrap(), + json!({ "timestamp": 2000 }) + ); + } + + #[test] + fn test_serialize_none() { + let request = SystemTimeTest { timestamp: None }; + assert_eq!(serde_json::to_value(&request).unwrap(), json!({})); + } +}