diff --git a/Cargo.toml b/Cargo.toml index f5e3bf9..fe1dd54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "duration-str" -version = "0.3.9" +version = "0.4.0" authors = ["baoyachi "] edition = "2018" description = "duration string parser" diff --git a/README.md b/README.md index 30685e6..c4041fc 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,43 @@ fn main() { } ``` +* option filed deserialize +```rust +use duration_str::deserialize_option_duration; +use serde::*; +use std::time::Duration; + +#[derive(Debug, Deserialize, PartialEq)] +struct Config { + #[serde(default, deserialize_with = "deserialize_option_duration")] + time_ticker: Option, + name: String, +} + +fn main() { + let json = r#"{"time_ticker":null,"name":"foo"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + + assert_eq!( + config, + Config { + time_ticker: None, + name: "foo".into() + } + ); + + let json = r#"{"name":"foo"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!( + config, + Config { + time_ticker: None, + name: "foo".into() + } + ); +} +``` Also you can use `deserialize_duration_chrono` function ```rust diff --git a/src/lib.rs b/src/lib.rs index e872f88..8412bff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -564,9 +564,58 @@ macro_rules! des_duration { }; } +#[cfg(feature = "serde")] +macro_rules! des_option_duration { + ($name:ident,$duration_type:ident,$fn_name:ident,$parse:ident) => { + struct $name; + impl<'de> serde::de::Visitor<'de> for $name { + type Value = Option<$duration_type>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("expect duration string,e.g:'1min+30'") + } + + fn visit_some(self, d: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::Deserialize; + let s: Option = Option::deserialize(d)?; + if let Some(s) = s { + let duration = $parse(s).map_err(serde::de::Error::custom)?; + return Ok(Some(duration)); + } + Ok(None) + } + + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + } + + pub fn $fn_name<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_option($name) + } + }; +} + #[cfg(feature = "serde")] des_duration!(DurationStd, Duration, deserialize_duration, parse_std); +#[cfg(feature = "serde")] +des_option_duration!( + OptionDurationStd, + Duration, + deserialize_option_duration, + parse_std +); + #[cfg(all(feature = "chrono", feature = "serde"))] des_duration!( DurationChrono, @@ -575,6 +624,14 @@ des_duration!( parse_chrono ); +#[cfg(all(feature = "chrono", feature = "serde"))] +des_option_duration!( + OptionDurationChrono, + CDuration, + deserialize_option_duration_chrono, + parse_chrono +); + #[cfg(test)] mod tests { use super::*; @@ -743,6 +800,84 @@ mod chrono_tests { ); } + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_option_duration_chrono() { + use chrono::Duration; + use serde::*; + #[derive(Debug, Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_option_duration_chrono")] + time_ticker: Option, + } + let json = r#"{"time_ticker":"1y+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!( + config.time_ticker, + Some(Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30)) + ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_duration() { + use serde::*; + #[derive(Debug, Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_duration")] + time_ticker: Duration, + } + let json = r#"{"time_ticker":"1min+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!(config.time_ticker, Duration::from_secs(90)); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_option_duration() { + use serde::*; + #[derive(Debug, Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_option_duration")] + time_ticker: Option, + } + let json = r#"{"time_ticker":"1min+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!(config.time_ticker, Some(Duration::from_secs(90))); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_option_duration2() { + use serde::*; + #[derive(Debug, Deserialize, PartialEq)] + struct Config { + #[serde(default, deserialize_with = "deserialize_option_duration")] + time_ticker: Option, + name: String, + } + let json = r#"{"time_ticker":null,"name":"foo"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + + assert_eq!( + config, + Config { + time_ticker: None, + name: "foo".into() + } + ); + + let json = r#"{"name":"foo"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!( + config, + Config { + time_ticker: None, + name: "foo".into() + } + ); + } + #[test] fn test_after_naive_date_time() { let date = Utc::now().naive_utc().date();