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_none<E>(self) -> Result<Self::Value, E>
97                    where
98                        E: serde::de::Error,
99                    {
100                        Ok(None)
101                    }
102                }
103
104                deserializer.deserialize_option(OptionDurationVisitor)
105            }
106        }
107    };
108}
109
110/// Deserialize duration string to Duration or Option<Duration>.
111///
112/// This function works with both required and optional fields:
113///
114/// ```ignore
115/// // For required Duration field
116/// #[serde(deserialize_with = "deserialize_duration")]
117/// time_ticker: Duration,
118///
119/// // For optional Duration field  
120/// #[serde(default, deserialize_with = "deserialize_duration")]
121/// time_ticker: Option<Duration>,
122/// ```
123#[cfg(feature = "serde")]
124pub fn deserialize_duration<'de, D, T>(deserializer: D) -> Result<T, D::Error>
125where
126    D: serde::Deserializer<'de>,
127    T: DeserializeDuration<'de>,
128{
129    T::deserialize_duration(deserializer)
130}
131
132// ==================== Implementations for std::time::Duration ====================
133
134#[cfg(feature = "serde")]
135impl_deserialize_duration!(Duration, parse_std);
136
137#[cfg(feature = "serde")]
138impl_deserialize_option_duration!(Duration, parse_std);
139
140// ==================== Implementations for chrono::Duration ====================
141
142#[cfg(all(feature = "chrono", feature = "serde"))]
143impl_deserialize_duration!(CDuration, parse_chrono);
144
145#[cfg(all(feature = "chrono", feature = "serde"))]
146impl_deserialize_option_duration!(CDuration, parse_chrono);
147
148// ==================== Implementations for time::Duration ====================
149
150#[cfg(all(feature = "time", feature = "serde"))]
151impl_deserialize_duration!(TDuration, parse_time);
152
153#[cfg(all(feature = "time", feature = "serde"))]
154impl_deserialize_option_duration!(TDuration, parse_time);
155
156// ==================== Type-specific functions for convenience ====================
157
158/// Deserialize duration string to `chrono::Duration`.
159///
160/// This is a convenience function equivalent to `deserialize_duration`.
161#[cfg(all(feature = "chrono", feature = "serde"))]
162pub fn deserialize_duration_chrono<'de, D>(deserializer: D) -> Result<CDuration, D::Error>
163where
164    D: serde::Deserializer<'de>,
165{
166    DeserializeDuration::deserialize_duration(deserializer)
167}
168
169/// Deserialize duration string to `time::Duration`.
170///
171/// This is a convenience function equivalent to `deserialize_duration`.
172#[cfg(all(feature = "time", feature = "serde"))]
173pub fn deserialize_duration_time<'de, D>(deserializer: D) -> Result<TDuration, D::Error>
174where
175    D: serde::Deserializer<'de>,
176{
177    DeserializeDuration::deserialize_duration(deserializer)
178}
179
180// ==================== Backward compatible aliases ====================
181
182/// Deprecated: Use `deserialize_duration` instead.
183#[cfg(feature = "serde")]
184#[deprecated(since = "0.19.0", note = "Use `deserialize_duration` instead")]
185pub fn deserialize_option_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
186where
187    D: serde::Deserializer<'de>,
188{
189    DeserializeDuration::deserialize_duration(deserializer)
190}
191
192/// Deprecated: Use `deserialize_duration_chrono` instead.
193#[cfg(all(feature = "chrono", feature = "serde"))]
194#[deprecated(since = "0.19.0", note = "Use `deserialize_duration_chrono` instead")]
195pub fn deserialize_option_duration_chrono<'de, D>(
196    deserializer: D,
197) -> Result<Option<CDuration>, D::Error>
198where
199    D: serde::Deserializer<'de>,
200{
201    DeserializeDuration::deserialize_duration(deserializer)
202}
203
204/// Deprecated: Use `deserialize_duration_time` instead.
205#[cfg(all(feature = "time", feature = "serde"))]
206#[deprecated(since = "0.19.0", note = "Use `deserialize_duration_time` instead")]
207pub fn deserialize_option_duration_time<'de, D>(
208    deserializer: D,
209) -> Result<Option<TDuration>, D::Error>
210where
211    D: serde::Deserializer<'de>,
212{
213    DeserializeDuration::deserialize_duration(deserializer)
214}
215
216#[cfg(all(test, feature = "time"))]
217mod tests {
218    use super::*;
219    use crate::ONE_YEAR_NANOSECOND;
220    use serde::*;
221
222    #[cfg(feature = "serde")]
223    #[test]
224    fn test_deserialize_duration_time() {
225        #[derive(Debug, Deserialize)]
226        struct Config {
227            #[serde(deserialize_with = "deserialize_duration")]
228            time_ticker: TDuration,
229        }
230        #[cfg(not(feature = "no_calc"))]
231        let json = r#"{"time_ticker":"1y+30"}"#;
232        #[cfg(feature = "no_calc")]
233        let json = r#"{"time_ticker":"1y30"}"#;
234        let config: Config = serde_json::from_str(json).unwrap();
235        assert_eq!(
236            config.time_ticker,
237            TDuration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + TDuration::seconds(30)
238        );
239    }
240
241    #[cfg(feature = "serde")]
242    #[test]
243    fn test_deserialize_option_duration_time() {
244        use TDuration;
245
246        #[derive(Debug, Deserialize)]
247        struct Config {
248            #[serde(default, deserialize_with = "deserialize_duration")]
249            time_ticker: Option<TDuration>,
250        }
251        #[cfg(not(feature = "no_calc"))]
252        let json = r#"{"time_ticker":"1y+30"}"#;
253        #[cfg(feature = "no_calc")]
254        let json = r#"{"time_ticker":"1y30"}"#;
255        let config: Config = serde_json::from_str(json).unwrap();
256        assert_eq!(
257            config.time_ticker,
258            Some(TDuration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + TDuration::seconds(30))
259        );
260    }
261
262    #[cfg(feature = "serde")]
263    #[test]
264    fn test_deserialize_option_duration_time_null() {
265        use TDuration;
266
267        #[derive(Debug, Deserialize, PartialEq)]
268        struct Config {
269            #[serde(default, deserialize_with = "deserialize_duration")]
270            time_ticker: Option<TDuration>,
271            name: String,
272        }
273
274        // Test with null
275        let json = r#"{"time_ticker":null,"name":"foo"}"#;
276        let config: Config = serde_json::from_str(json).unwrap();
277        assert_eq!(config.time_ticker, None);
278
279        // Test with missing field
280        let json = r#"{"name":"foo"}"#;
281        let config: Config = serde_json::from_str(json).unwrap();
282        assert_eq!(config.time_ticker, None);
283
284        // Test with empty string
285        let json = r#"{"time_ticker":"","name":"foo"}"#;
286        let config: Config = serde_json::from_str(json).unwrap();
287        assert_eq!(config.time_ticker, None);
288    }
289
290    #[cfg(feature = "serde")]
291    #[test]
292    fn test_deserialize_unit_with_spaces() {
293        #[derive(Debug, Deserialize)]
294        struct Config {
295            #[serde(deserialize_with = "deserialize_duration")]
296            time_ticker: TDuration,
297        }
298        #[cfg(not(feature = "no_calc"))]
299        let json = r#"{"time_ticker":"1 y + 30"}"#;
300        #[cfg(feature = "no_calc")]
301        let json = r#"{"time_ticker":"1 y  30"}"#;
302        let config: Config = serde_json::from_str(json).unwrap();
303        assert_eq!(
304            config.time_ticker,
305            TDuration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + TDuration::seconds(30)
306        );
307    }
308
309    #[cfg(all(feature = "serde", feature = "chrono"))]
310    #[test]
311    fn test_deserialize_duration_chrono() {
312        use chrono::Duration;
313        #[derive(Debug, serde::Deserialize)]
314        struct Config {
315            #[serde(deserialize_with = "deserialize_duration")]
316            time_ticker: Duration,
317        }
318        #[cfg(not(feature = "no_calc"))]
319        let json = r#"{"time_ticker":"1y+30"}"#;
320        #[cfg(feature = "no_calc")]
321        let json = r#"{"time_ticker":"1y30"}"#;
322        let config: Config = serde_json::from_str(json).unwrap();
323        assert_eq!(
324            config.time_ticker,
325            Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30)
326        );
327    }
328
329    #[cfg(all(feature = "serde", feature = "chrono"))]
330    #[test]
331    fn test_deserialize_option_duration_chrono() {
332        use chrono::Duration;
333        #[derive(Debug, serde::Deserialize)]
334        struct Config {
335            #[serde(default, deserialize_with = "deserialize_duration")]
336            time_ticker: Option<Duration>,
337        }
338        #[cfg(not(feature = "no_calc"))]
339        let json = r#"{"time_ticker":"1y+30"}"#;
340        #[cfg(feature = "no_calc")]
341        let json = r#"{"time_ticker":"1y30"}"#;
342        let config: Config = serde_json::from_str(json).unwrap();
343        assert_eq!(
344            config.time_ticker,
345            Some(Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30))
346        );
347    }
348
349    #[cfg(feature = "serde")]
350    #[test]
351    fn test_deserialize_duration() {
352        #[derive(Debug, serde::Deserialize)]
353        struct Config {
354            #[serde(deserialize_with = "deserialize_duration")]
355            time_ticker: std::time::Duration,
356        }
357
358        #[cfg(not(feature = "no_calc"))]
359        let json = r#"{"time_ticker":"1min+30"}"#;
360        #[cfg(feature = "no_calc")]
361        let json = r#"{"time_ticker":"1min30"}"#;
362        let config: Config = serde_json::from_str(json).unwrap();
363        assert_eq!(config.time_ticker, std::time::Duration::from_secs(90));
364    }
365
366    #[cfg(feature = "serde")]
367    #[test]
368    fn test_deserialize_option_duration() {
369        #[derive(Debug, serde::Deserialize)]
370        struct Config {
371            #[serde(default, deserialize_with = "deserialize_duration")]
372            time_ticker: Option<std::time::Duration>,
373        }
374        #[cfg(not(feature = "no_calc"))]
375        let json = r#"{"time_ticker":"1min+30"}"#;
376        #[cfg(feature = "no_calc")]
377        let json = r#"{"time_ticker":"1min30"}"#;
378        let config: Config = serde_json::from_str(json).unwrap();
379        assert_eq!(config.time_ticker, Some(std::time::Duration::from_secs(90)));
380    }
381
382    #[cfg(feature = "serde")]
383    #[test]
384    fn test_deserialize_duration2() {
385        #[derive(Debug, serde::Deserialize)]
386        struct Config {
387            #[serde(deserialize_with = "deserialize_duration")]
388            time_ticker: std::time::Duration,
389        }
390        #[cfg(not(feature = "no_calc"))]
391        let json = r#"{"time_ticker":"1y+30"}"#;
392        #[cfg(feature = "no_calc")]
393        let json = r#"{"time_ticker":"1y30"}"#;
394        let config: Config = serde_json::from_str(json).unwrap();
395        assert_eq!(
396            config.time_ticker,
397            std::time::Duration::from_nanos(ONE_YEAR_NANOSECOND)
398                + std::time::Duration::from_secs(30)
399        );
400    }
401
402    #[cfg(feature = "serde")]
403    #[test]
404    fn test_deserialize_option_duration2() {
405        #[derive(Debug, serde::Deserialize, PartialEq)]
406        struct Config {
407            #[serde(default, deserialize_with = "deserialize_duration")]
408            time_ticker: Option<std::time::Duration>,
409            name: String,
410        }
411        let json = r#"{"time_ticker":null,"name":"foo"}"#;
412        let config: Config = serde_json::from_str(json).unwrap();
413
414        assert_eq!(
415            config,
416            Config {
417                time_ticker: None,
418                name: "foo".into(),
419            }
420        );
421
422        let json = r#"{"name":"foo"}"#;
423        let config: Config = serde_json::from_str(json).unwrap();
424        assert_eq!(
425            config,
426            Config {
427                time_ticker: None,
428                name: "foo".into(),
429            }
430        );
431    }
432
433    #[cfg(feature = "serde")]
434    #[test]
435    fn test_deserialize_option_duration_empty_string() {
436        #[derive(Debug, serde::Deserialize, PartialEq)]
437        struct Config {
438            #[serde(default, deserialize_with = "deserialize_duration")]
439            time_ticker: Option<std::time::Duration>,
440            name: String,
441        }
442        // Empty string should be treated as None
443        let json = r#"{"time_ticker":"","name":"foo"}"#;
444        let config: Config = serde_json::from_str(json).unwrap();
445        assert_eq!(
446            config,
447            Config {
448                time_ticker: None,
449                name: "foo".into(),
450            }
451        );
452    }
453
454    // Test backward compatibility with deprecated functions
455    #[cfg(feature = "serde")]
456    #[test]
457    #[allow(deprecated)]
458    fn test_deprecated_deserialize_option_duration() {
459        #[derive(Debug, serde::Deserialize)]
460        struct Config {
461            #[serde(deserialize_with = "deserialize_option_duration")]
462            time_ticker: Option<std::time::Duration>,
463        }
464        #[cfg(not(feature = "no_calc"))]
465        let json = r#"{"time_ticker":"1min+30"}"#;
466        #[cfg(feature = "no_calc")]
467        let json = r#"{"time_ticker":"1min30"}"#;
468        let config: Config = serde_json::from_str(json).unwrap();
469        assert_eq!(config.time_ticker, Some(std::time::Duration::from_secs(90)));
470    }
471}