diff options
author | D. Scott Boggs <scott@tams.tech> | 2024-04-09 13:05:43 -0400 |
---|---|---|
committer | D. Scott Boggs <scott@tams.tech> | 2024-04-09 13:05:43 -0400 |
commit | fe39d8481adc8daf251efdbf827dbf724e88e06d (patch) | |
tree | 909c73cf4c3dbd89ebf1c53f67bc033f9573ca98 | |
parent | 8d0cb43c54b59871bc824c193f453b610eb8b1ce (diff) |
Add the ability to serialize a duration as seconds
-rw-r--r-- | entities/src/helpers.rs | 135 | ||||
-rw-r--r-- | entities/src/lib.rs | 1 |
2 files changed, 136 insertions, 0 deletions
diff --git a/entities/src/helpers.rs b/entities/src/helpers.rs new file mode 100644 index 0000000..90f5816 --- /dev/null +++ b/entities/src/helpers.rs @@ -0,0 +1,135 @@ +pub(crate) mod serde_opt_duration_as_seconds { + use std::time::Duration; + + use serde::de; + + pub(crate) fn serialize<S>( + duration: &Option<Duration>, + serializer: S, + ) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + if let Some(duration) = duration { + serializer.serialize_u64(duration.as_secs()) + } else { + serializer.serialize_none() + } + } + + pub(crate) fn deserialize<'de, D>( + deserializer: D, + ) -> Result<Option<Duration>, <D as serde::Deserializer<'de>>::Error> + where + D: serde::Deserializer<'de>, + { + use serde::de::Visitor; + + struct DurationVisitor; + + impl<'v> Visitor<'v> for DurationVisitor { + type Value = Option<Duration>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "unsigned integer") + } + + fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> + where + E: serde::de::Error, + { + Ok(Some(Duration::from_secs(v))) + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: serde::de::Error, + { + if v.is_empty() { + Ok(None) + } else { + v.parse() + .map(|n| Some(Duration::from_secs(n))) + .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(v), &self)) + } + } + fn visit_none<E>(self) -> Result<Self::Value, E> + where + E: de::Error, + { + Ok(None) + } + } + deserializer.deserialize_any(DurationVisitor) + } +} + +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + use std::time::Duration; + + use super::*; + + #[derive(Debug, Serialize, Deserialize)] + struct TestDuration { + #[serde( + with = "serde_opt_duration_as_seconds", + skip_serializing_if = "Option::is_none", + default + )] + dur: Option<Duration>, + } + + impl Default for TestDuration { + fn default() -> Self { + TestDuration { + dur: Some(Duration::from_secs(10)), + } + } + } + + impl TestDuration { + fn empty() -> Self { + Self { dur: None } + } + } + + #[test] + fn test_serialize_duration() { + let it = TestDuration::default(); + let serialized = serde_json::to_string(&it).expect("serialize"); + assert_eq!(serialized, r#"{"dur":10}"#); + } + + #[test] + fn test_serialize_empty_duration() { + let it = TestDuration::empty(); + let ser = serde_json::to_string(&it).expect("serialize"); + assert_eq!("{}", ser); + } + + #[test] + fn test_deserialize_duration() { + let text = r#"{"dur": 10}"#; + let duration: TestDuration = serde_json::from_str(text).expect("deserialize"); + assert_eq!(duration.dur.unwrap().as_secs(), 10); + let text = r#"{"dur": "10"}"#; + let duration: TestDuration = serde_json::from_str(text).expect("deserialize"); + assert_eq!(duration.dur.unwrap().as_secs(), 10); + } + + #[test] + fn test_deserialize_empty_duration() { + let text = r#"{"dur": ""}"#; + let duration: TestDuration = serde_json::from_str(text).expect("deserialize"); + assert!(duration.dur.is_none()); + } + + #[test] + fn test_deserialize_null_duration() { + let text = r#"{}"#; + let duration: TestDuration = serde_json::from_str(text).expect("deserialize"); + assert!(duration.dur.is_none()); + } +} diff --git a/entities/src/lib.rs b/entities/src/lib.rs index 80693ea..86a52d8 100644 --- a/entities/src/lib.rs +++ b/entities/src/lib.rs @@ -4,6 +4,7 @@ use serde::Serialize; /// Error types for this crate pub mod error; +mod helpers; pub use error::Error; /// Data structures for ser/de of account-related resources |