Skip to main content

duration_str/
serde.rs

1#[cfg(all(feature = "chrono", feature = "serde"))]
2use crate::parse_chrono;
3use crate::parse_std;
4#[cfg(all(feature = "time", feature = "serde"))]
5use crate::parse_time;
6use std::time::Duration;
7
8#[cfg(all(feature = "chrono", feature = "serde"))]
9use chrono::Duration as CDuration;
10
11#[cfg(all(feature = "time", feature = "serde"))]
12use time::Duration as TDuration;
13
14/// Trait for types that can be deserialized from a duration string.
15#[cfg(feature = "serde")]
16pub trait DeserializeDuration<'de>: Sized {
17    /// Deserialize this type from a duration string.
18    fn deserialize_duration<D>(deserializer: D) -> Result<Self, D::Error>
19    where
20        D: serde::Deserializer<'de>;
21}
22
23/// Internal macro to implement DeserializeDuration for Duration and Option<Duration>
24#[cfg(feature = "serde")]
25macro_rules! impl_deserialize_duration {
26    ($duration_type:ty, $parse:ident) => {
27        impl<'de> DeserializeDuration<'de> for $duration_type {
28            fn deserialize_duration<D>(deserializer: D) -> Result<Self, D::Error>
29            where
30                D: serde::Deserializer<'de>,
31            {
32                struct DurationVisitor;
33                impl<'de> serde::de::Visitor<'de> for DurationVisitor {
34                    type Value = $duration_type;
35
36                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
37                        formatter.write_str("expect duration string, e.g: '1min+30'")
38                    }
39
40                    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
41                    where
42                        E: serde::de::Error,
43                    {
44                        let duration = $parse(s).map_err(serde::de::Error::custom)?;
45                        Ok(duration)
46                    }
47                }
48
49                deserializer.deserialize_any(DurationVisitor)
50            }
51        }
52    };
53}
54
55/// Internal macro to implement DeserializeDuration for Option<Duration>
56#[cfg(feature = "serde")]
57macro_rules! impl_deserialize_option_duration {
58    ($duration_type:ty, $parse:ident) => {
59        impl<'de> DeserializeDuration<'de> for Option<$duration_type> {
60            fn deserialize_duration<D>(deserializer: D) -> Result<Self, D::Error>
61            where
62                D: serde::Deserializer<'de>,
63            {
64                struct OptionDurationVisitor;
65                impl<'de> serde::de::Visitor<'de> for OptionDurationVisitor {
66                    type Value = Option<$duration_type>;
67
68                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
69                        formatter.write_str("expect duration string, null, or missing field")
70                    }
71
72                    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
73                    where
74                        E: serde::de::Error,
75                    {
76                        if s.is_empty() {
77                            return Ok(None);
78                        }
79                        let duration = $parse(s).map_err(serde::de::Error::custom)?;
80                        Ok(Some(duration))
81                    }
82
83                    fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
84                    where
85                        D: serde::Deserializer<'de>,
86                    {
87                        use serde::Deserialize;
88                        let s: String = String::deserialize(d)?;
89                        if s.is_empty() {
90                            return Ok(None);
91                        }
92                        let duration = $parse(s).map_err(serde::de::Error::custom)?;
93                        Ok(Some(duration))
94                    }
95
96                    fn visit_unit<E>(self) -> Result<Self::Value, E>
97                    where
98                        E: serde::de::Error,
99                    {
100                        Ok(None)
101                    }
102
103                    fn visit_none<E>(self) -> Result<Self::Value, E>
104                    where
105                        E: serde::de::Error,
106                    {
107                        Ok(None)
108                    }
109                }
110
111                deserializer.deserialize_option(OptionDurationVisitor)
112            }
113        }
114    };
115}
116
117/// Deserialize duration string to Duration or Option<Duration>.
118///
119/// This function works with both required and optional fields:
120///
121/// ```ignore
122/// // For required Duration field
123/// #[serde(deserialize_with = "deserialize_duration")]
124/// time_ticker: Duration,
125///
126/// // For optional Duration field
127/// #[serde(default, deserialize_with = "deserialize_duration")]
128/// time_ticker: Option<Duration>,
129/// ```
130#[cfg(feature = "serde")]
131pub fn deserialize_duration<'de, D, T>(deserializer: D) -> Result<T, D::Error>
132where
133    D: serde::Deserializer<'de>,
134    T: DeserializeDuration<'de>,
135{
136    T::deserialize_duration(deserializer)
137}
138
139// ==================== Implementations for std::time::Duration ====================
140
141#[cfg(feature = "serde")]
142impl_deserialize_duration!(Duration, parse_std);
143
144#[cfg(feature = "serde")]
145impl_deserialize_option_duration!(Duration, parse_std);
146
147// ==================== Implementations for chrono::Duration ====================
148
149#[cfg(all(feature = "chrono", feature = "serde"))]
150impl_deserialize_duration!(CDuration, parse_chrono);
151
152#[cfg(all(feature = "chrono", feature = "serde"))]
153impl_deserialize_option_duration!(CDuration, parse_chrono);
154
155// ==================== Implementations for time::Duration ====================
156
157#[cfg(all(feature = "time", feature = "serde"))]
158impl_deserialize_duration!(TDuration, parse_time);
159
160#[cfg(all(feature = "time", feature = "serde"))]
161impl_deserialize_option_duration!(TDuration, parse_time);
162
163// ==================== Type-specific functions for convenience ====================
164
165/// Deserialize duration string to `chrono::Duration`.
166///
167/// This is a convenience function equivalent to `deserialize_duration`.
168#[cfg(all(feature = "chrono", feature = "serde"))]
169pub fn deserialize_duration_chrono<'de, D>(deserializer: D) -> Result<CDuration, D::Error>
170where
171    D: serde::Deserializer<'de>,
172{
173    DeserializeDuration::deserialize_duration(deserializer)
174}
175
176/// Deserialize duration string to `time::Duration`.
177///
178/// This is a convenience function equivalent to `deserialize_duration`.
179#[cfg(all(feature = "time", feature = "serde"))]
180pub fn deserialize_duration_time<'de, D>(deserializer: D) -> Result<TDuration, D::Error>
181where
182    D: serde::Deserializer<'de>,
183{
184    DeserializeDuration::deserialize_duration(deserializer)
185}
186
187// ==================== Backward compatible aliases ====================
188
189/// Deprecated: Use `deserialize_duration` instead.
190#[cfg(feature = "serde")]
191#[deprecated(since = "0.19.0", note = "Use `deserialize_duration` instead")]
192pub fn deserialize_option_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
193where
194    D: serde::Deserializer<'de>,
195{
196    DeserializeDuration::deserialize_duration(deserializer)
197}
198
199/// Deprecated: Use `deserialize_duration_chrono` instead.
200#[cfg(all(feature = "chrono", feature = "serde"))]
201#[deprecated(since = "0.19.0", note = "Use `deserialize_duration_chrono` instead")]
202pub fn deserialize_option_duration_chrono<'de, D>(
203    deserializer: D,
204) -> Result<Option<CDuration>, D::Error>
205where
206    D: serde::Deserializer<'de>,
207{
208    DeserializeDuration::deserialize_duration(deserializer)
209}
210
211/// Deprecated: Use `deserialize_duration_time` instead.
212#[cfg(all(feature = "time", feature = "serde"))]
213#[deprecated(since = "0.19.0", note = "Use `deserialize_duration_time` instead")]
214pub fn deserialize_option_duration_time<'de, D>(
215    deserializer: D,
216) -> Result<Option<TDuration>, D::Error>
217where
218    D: serde::Deserializer<'de>,
219{
220    DeserializeDuration::deserialize_duration(deserializer)
221}
222
223#[cfg(all(test, feature = "time"))]
224mod tests {
225    use super::*;
226    use crate::ONE_YEAR_NANOSECOND;
227    use serde::*;
228
229    #[cfg(feature = "serde")]
230    #[test]
231    fn test_deserialize_duration_time() {
232        #[derive(Debug, Deserialize)]
233        struct Config {
234            #[serde(deserialize_with = "deserialize_duration")]
235            time_ticker: TDuration,
236        }
237        #[cfg(not(feature = "no_calc"))]
238        let json = r#"{"time_ticker":"1y+30"}"#;
239        #[cfg(feature = "no_calc")]
240        let json = r#"{"time_ticker":"1y30"}"#;
241        let config: Config = serde_json::from_str(json).unwrap();
242        assert_eq!(
243            config.time_ticker,
244            TDuration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + TDuration::seconds(30)
245        );
246    }
247
248    #[cfg(feature = "serde")]
249    #[test]
250    fn test_deserialize_option_duration_time() {
251        use TDuration;
252
253        #[derive(Debug, Deserialize)]
254        struct Config {
255            #[serde(default, deserialize_with = "deserialize_duration")]
256            time_ticker: Option<TDuration>,
257        }
258        #[cfg(not(feature = "no_calc"))]
259        let json = r#"{"time_ticker":"1y+30"}"#;
260        #[cfg(feature = "no_calc")]
261        let json = r#"{"time_ticker":"1y30"}"#;
262        let config: Config = serde_json::from_str(json).unwrap();
263        assert_eq!(
264            config.time_ticker,
265            Some(TDuration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + TDuration::seconds(30))
266        );
267    }
268
269    #[cfg(feature = "serde")]
270    #[test]
271    fn test_deserialize_option_duration_time_null() {
272        use TDuration;
273
274        #[derive(Debug, Deserialize, PartialEq)]
275        struct Config {
276            #[serde(default, deserialize_with = "deserialize_duration")]
277            time_ticker: Option<TDuration>,
278            name: String,
279        }
280
281        // Test with null
282        let json = r#"{"time_ticker":null,"name":"foo"}"#;
283        let config: Config = serde_json::from_str(json).unwrap();
284        assert_eq!(config.time_ticker, None);
285
286        // Test with missing field
287        let json = r#"{"name":"foo"}"#;
288        let config: Config = serde_json::from_str(json).unwrap();
289        assert_eq!(config.time_ticker, None);
290
291        // Test with empty string
292        let json = r#"{"time_ticker":"","name":"foo"}"#;
293        let config: Config = serde_json::from_str(json).unwrap();
294        assert_eq!(config.time_ticker, None);
295    }
296
297    #[cfg(feature = "serde")]
298    #[test]
299    fn test_deserialize_unit_with_spaces() {
300        #[derive(Debug, Deserialize)]
301        struct Config {
302            #[serde(deserialize_with = "deserialize_duration")]
303            time_ticker: TDuration,
304        }
305        #[cfg(not(feature = "no_calc"))]
306        let json = r#"{"time_ticker":"1 y + 30"}"#;
307        #[cfg(feature = "no_calc")]
308        let json = r#"{"time_ticker":"1 y  30"}"#;
309        let config: Config = serde_json::from_str(json).unwrap();
310        assert_eq!(
311            config.time_ticker,
312            TDuration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + TDuration::seconds(30)
313        );
314    }
315
316    #[cfg(all(feature = "serde", feature = "chrono"))]
317    #[test]
318    fn test_deserialize_duration_chrono() {
319        use chrono::Duration;
320        #[derive(Debug, serde::Deserialize)]
321        struct Config {
322            #[serde(deserialize_with = "deserialize_duration")]
323            time_ticker: Duration,
324        }
325        #[cfg(not(feature = "no_calc"))]
326        let json = r#"{"time_ticker":"1y+30"}"#;
327        #[cfg(feature = "no_calc")]
328        let json = r#"{"time_ticker":"1y30"}"#;
329        let config: Config = serde_json::from_str(json).unwrap();
330        assert_eq!(
331            config.time_ticker,
332            Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30)
333        );
334    }
335
336    #[cfg(all(feature = "serde", feature = "chrono"))]
337    #[test]
338    fn test_deserialize_option_duration_chrono() {
339        use chrono::Duration;
340        #[derive(Debug, serde::Deserialize)]
341        struct Config {
342            #[serde(default, deserialize_with = "deserialize_duration")]
343            time_ticker: Option<Duration>,
344        }
345        #[cfg(not(feature = "no_calc"))]
346        let json = r#"{"time_ticker":"1y+30"}"#;
347        #[cfg(feature = "no_calc")]
348        let json = r#"{"time_ticker":"1y30"}"#;
349        let config: Config = serde_json::from_str(json).unwrap();
350        assert_eq!(
351            config.time_ticker,
352            Some(Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30))
353        );
354    }
355
356    #[cfg(feature = "serde")]
357    #[test]
358    fn test_deserialize_duration() {
359        #[derive(Debug, serde::Deserialize)]
360        struct Config {
361            #[serde(deserialize_with = "deserialize_duration")]
362            time_ticker: std::time::Duration,
363        }
364
365        #[cfg(not(feature = "no_calc"))]
366        let json = r#"{"time_ticker":"1min+30"}"#;
367        #[cfg(feature = "no_calc")]
368        let json = r#"{"time_ticker":"1min30"}"#;
369        let config: Config = serde_json::from_str(json).unwrap();
370        assert_eq!(config.time_ticker, std::time::Duration::from_secs(90));
371    }
372
373    #[cfg(feature = "serde")]
374    #[test]
375    fn test_deserialize_option_duration() {
376        #[derive(Debug, serde::Deserialize)]
377        struct Config {
378            #[serde(default, deserialize_with = "deserialize_duration")]
379            time_ticker: Option<std::time::Duration>,
380        }
381        #[cfg(not(feature = "no_calc"))]
382        let json = r#"{"time_ticker":"1min+30"}"#;
383        #[cfg(feature = "no_calc")]
384        let json = r#"{"time_ticker":"1min30"}"#;
385        let config: Config = serde_json::from_str(json).unwrap();
386        assert_eq!(config.time_ticker, Some(std::time::Duration::from_secs(90)));
387    }
388
389    #[cfg(feature = "serde")]
390    #[test]
391    fn test_deserialize_duration2() {
392        #[derive(Debug, serde::Deserialize)]
393        struct Config {
394            #[serde(deserialize_with = "deserialize_duration")]
395            time_ticker: std::time::Duration,
396        }
397        #[cfg(not(feature = "no_calc"))]
398        let json = r#"{"time_ticker":"1y+30"}"#;
399        #[cfg(feature = "no_calc")]
400        let json = r#"{"time_ticker":"1y30"}"#;
401        let config: Config = serde_json::from_str(json).unwrap();
402        assert_eq!(
403            config.time_ticker,
404            std::time::Duration::from_nanos(ONE_YEAR_NANOSECOND)
405                + std::time::Duration::from_secs(30)
406        );
407    }
408
409    #[cfg(feature = "serde")]
410    #[test]
411    fn test_deserialize_option_duration2() {
412        #[derive(Debug, serde::Deserialize, PartialEq)]
413        struct Config {
414            #[serde(default, deserialize_with = "deserialize_duration")]
415            time_ticker: Option<std::time::Duration>,
416            name: String,
417        }
418        let json = r#"{"time_ticker":null,"name":"foo"}"#;
419        let config: Config = serde_json::from_str(json).unwrap();
420
421        assert_eq!(
422            config,
423            Config {
424                time_ticker: None,
425                name: "foo".into(),
426            }
427        );
428
429        let json = r#"{"name":"foo"}"#;
430        let config: Config = serde_json::from_str(json).unwrap();
431        assert_eq!(
432            config,
433            Config {
434                time_ticker: None,
435                name: "foo".into(),
436            }
437        );
438    }
439
440    #[cfg(feature = "serde")]
441    #[test]
442    fn test_deserialize_option_duration_empty_string() {
443        #[derive(Debug, serde::Deserialize, PartialEq)]
444        struct Config {
445            #[serde(default, deserialize_with = "deserialize_duration")]
446            time_ticker: Option<std::time::Duration>,
447            name: String,
448        }
449        // Empty string should be treated as None
450        let json = r#"{"time_ticker":"","name":"foo"}"#;
451        let config: Config = serde_json::from_str(json).unwrap();
452        assert_eq!(
453            config,
454            Config {
455                time_ticker: None,
456                name: "foo".into(),
457            }
458        );
459    }
460
461    #[cfg(feature = "serde")]
462    #[test]
463    fn test_deserialize_option_duration_with_null_using_struct_flatten() {
464        #[derive(Debug, serde::Deserialize, PartialEq)]
465        struct ConfigSubStruct {
466            #[serde(default, deserialize_with = "deserialize_duration")]
467            time_ticker: Option<std::time::Duration>,
468            name: String,
469        }
470        // The flattened structure containing a property set to null in the
471        // JSON should be deserialized as None
472        #[derive(Debug, serde::Deserialize, PartialEq)]
473        struct Config {
474            #[serde(default, flatten)]
475            config: ConfigSubStruct,
476        }
477        let json = r#"{"time_ticker":null,"name":"foo"}"#;
478        let config: Config = serde_json::from_str(json).unwrap();
479        assert_eq!(
480            config,
481            Config {
482                config: ConfigSubStruct {
483                    time_ticker: None,
484                    name: "foo".into(),
485                }
486            }
487        );
488    }
489
490    // Test backward compatibility with deprecated functions
491    #[cfg(feature = "serde")]
492    #[test]
493    #[allow(deprecated)]
494    fn test_deprecated_deserialize_option_duration() {
495        #[derive(Debug, serde::Deserialize)]
496        struct Config {
497            #[serde(deserialize_with = "deserialize_option_duration")]
498            time_ticker: Option<std::time::Duration>,
499        }
500        #[cfg(not(feature = "no_calc"))]
501        let json = r#"{"time_ticker":"1min+30"}"#;
502        #[cfg(feature = "no_calc")]
503        let json = r#"{"time_ticker":"1min30"}"#;
504        let config: Config = serde_json::from_str(json).unwrap();
505        assert_eq!(config.time_ticker, Some(std::time::Duration::from_secs(90)));
506    }
507}