serde_with/utils/
duration.rs

1//! Internal Helper types
2
3// Serialization of large numbers can result in overflows
4// The time calculations are prone to this, so lint here extra
5// https://github.com/jonasbb/serde_with/issues/771
6#![warn(clippy::as_conversions)]
7
8use crate::{
9    formats::{Flexible, Format, Strict, Strictness},
10    prelude::*,
11};
12
13#[derive(Copy, Clone, PartialEq, Eq)]
14#[cfg_attr(test, derive(Debug))]
15pub(crate) enum Sign {
16    Positive,
17    Negative,
18}
19
20impl Sign {
21    #[allow(dead_code)]
22    pub(crate) fn is_positive(&self) -> bool {
23        *self == Sign::Positive
24    }
25
26    #[allow(dead_code)]
27    pub(crate) fn is_negative(&self) -> bool {
28        *self == Sign::Negative
29    }
30
31    pub(crate) fn apply_f64(&self, value: f64) -> f64 {
32        match *self {
33            Sign::Positive => value,
34            Sign::Negative => -value,
35        }
36    }
37
38    pub(crate) fn apply_i64(&self, value: i64) -> Option<i64> {
39        match *self {
40            Sign::Positive => Some(value),
41            Sign::Negative => value.checked_neg(),
42        }
43    }
44}
45
46#[derive(Copy, Clone)]
47pub(crate) struct DurationSigned {
48    pub(crate) sign: Sign,
49    pub(crate) duration: Duration,
50}
51
52impl DurationSigned {
53    pub(crate) fn new(sign: Sign, secs: u64, nanosecs: u32) -> Self {
54        Self {
55            sign,
56            duration: Duration::new(secs, nanosecs),
57        }
58    }
59
60    pub(crate) fn checked_mul(mut self, rhs: u32) -> Option<Self> {
61        self.duration = self.duration.checked_mul(rhs)?;
62        Some(self)
63    }
64
65    pub(crate) fn checked_div(mut self, rhs: u32) -> Option<Self> {
66        self.duration = self.duration.checked_div(rhs)?;
67        Some(self)
68    }
69
70    #[cfg(any(feature = "chrono_0_4", feature = "time_0_3"))]
71    pub(crate) fn with_duration(sign: Sign, duration: Duration) -> Self {
72        Self { sign, duration }
73    }
74
75    #[cfg(feature = "std")]
76    pub(crate) fn to_system_time<'de, D>(self) -> Result<SystemTime, D::Error>
77    where
78        D: Deserializer<'de>,
79    {
80        match self.sign {
81            Sign::Positive => SystemTime::UNIX_EPOCH.checked_add(self.duration),
82            Sign::Negative => SystemTime::UNIX_EPOCH.checked_sub(self.duration),
83        }
84        .ok_or_else(|| DeError::custom("timestamp is outside the range for std::time::SystemTime"))
85    }
86
87    pub(crate) fn to_std_duration<'de, D>(self) -> Result<Duration, D::Error>
88    where
89        D: Deserializer<'de>,
90    {
91        match self.sign {
92            Sign::Positive => Ok(self.duration),
93            Sign::Negative => Err(DeError::custom("std::time::Duration cannot be negative")),
94        }
95    }
96}
97
98impl From<&Duration> for DurationSigned {
99    fn from(&duration: &Duration) -> Self {
100        Self {
101            sign: Sign::Positive,
102            duration,
103        }
104    }
105}
106
107#[cfg(feature = "std")]
108impl From<&SystemTime> for DurationSigned {
109    fn from(time: &SystemTime) -> Self {
110        match time.duration_since(SystemTime::UNIX_EPOCH) {
111            Ok(dur) => DurationSigned {
112                sign: Sign::Positive,
113                duration: dur,
114            },
115            Err(err) => DurationSigned {
116                sign: Sign::Negative,
117                duration: err.duration(),
118            },
119        }
120    }
121}
122
123impl<STRICTNESS> SerializeAs<DurationSigned> for DurationSeconds<u64, STRICTNESS>
124where
125    STRICTNESS: Strictness,
126{
127    fn serialize_as<S>(source: &DurationSigned, serializer: S) -> Result<S::Ok, S::Error>
128    where
129        S: Serializer,
130    {
131        if source.sign.is_negative() {
132            return Err(SerError::custom(
133                "cannot serialize a negative Duration as u64",
134            ));
135        }
136
137        let mut secs = source.duration.as_secs();
138
139        // Properly round the value
140        if source.duration.subsec_millis() >= 500 {
141            if source.sign.is_positive() {
142                secs += 1;
143            } else {
144                secs -= 1;
145            }
146        }
147        secs.serialize(serializer)
148    }
149}
150
151impl<STRICTNESS> SerializeAs<DurationSigned> for DurationSeconds<i64, STRICTNESS>
152where
153    STRICTNESS: Strictness,
154{
155    fn serialize_as<S>(source: &DurationSigned, serializer: S) -> Result<S::Ok, S::Error>
156    where
157        S: Serializer,
158    {
159        let mut secs = source
160            .sign
161            .apply_i64(i64::try_from(source.duration.as_secs()).map_err(|_| {
162                SerError::custom("The Duration of Timestamp is outside the supported range.")
163            })?)
164            .ok_or_else(|| {
165                S::Error::custom("The Duration of Timestamp is outside the supported range.")
166            })?;
167
168        // Properly round the value
169        // TODO check for overflows BUG771
170        if source.duration.subsec_millis() >= 500 {
171            if source.sign.is_positive() {
172                secs += 1;
173            } else {
174                secs -= 1;
175            }
176        }
177        secs.serialize(serializer)
178    }
179}
180
181impl<STRICTNESS> SerializeAs<DurationSigned> for DurationSeconds<f64, STRICTNESS>
182where
183    STRICTNESS: Strictness,
184{
185    fn serialize_as<S>(source: &DurationSigned, serializer: S) -> Result<S::Ok, S::Error>
186    where
187        S: Serializer,
188    {
189        // as conversions are necessary for floats
190        #[allow(clippy::as_conversions)]
191        let mut secs = source.sign.apply_f64(source.duration.as_secs() as f64);
192
193        // Properly round the value
194        if source.duration.subsec_millis() >= 500 {
195            if source.sign.is_positive() {
196                secs += 1.;
197            } else {
198                secs -= 1.;
199            }
200        }
201        secs.serialize(serializer)
202    }
203}
204
205#[cfg(feature = "alloc")]
206impl<STRICTNESS> SerializeAs<DurationSigned> for DurationSeconds<String, STRICTNESS>
207where
208    STRICTNESS: Strictness,
209{
210    fn serialize_as<S>(source: &DurationSigned, serializer: S) -> Result<S::Ok, S::Error>
211    where
212        S: Serializer,
213    {
214        let mut secs = source
215            .sign
216            .apply_i64(i64::try_from(source.duration.as_secs()).map_err(|_| {
217                SerError::custom("The Duration of Timestamp is outside the supported range.")
218            })?)
219            .ok_or_else(|| {
220                S::Error::custom("The Duration of Timestamp is outside the supported range.")
221            })?;
222
223        // Properly round the value
224        if source.duration.subsec_millis() >= 500 {
225            if source.sign.is_positive() {
226                secs += 1;
227            } else {
228                secs -= 1;
229            }
230        }
231        secs.to_string().serialize(serializer)
232    }
233}
234
235impl<STRICTNESS> SerializeAs<DurationSigned> for DurationSecondsWithFrac<f64, STRICTNESS>
236where
237    STRICTNESS: Strictness,
238{
239    fn serialize_as<S>(source: &DurationSigned, serializer: S) -> Result<S::Ok, S::Error>
240    where
241        S: Serializer,
242    {
243        source
244            .sign
245            .apply_f64(source.duration.as_secs_f64())
246            .serialize(serializer)
247    }
248}
249
250#[cfg(feature = "alloc")]
251impl<STRICTNESS> SerializeAs<DurationSigned> for DurationSecondsWithFrac<String, STRICTNESS>
252where
253    STRICTNESS: Strictness,
254{
255    fn serialize_as<S>(source: &DurationSigned, serializer: S) -> Result<S::Ok, S::Error>
256    where
257        S: Serializer,
258    {
259        source
260            .sign
261            .apply_f64(source.duration.as_secs_f64())
262            .to_string()
263            .serialize(serializer)
264    }
265}
266
267macro_rules! duration_impls {
268    ($($inner:ident { $($factor:literal => $outer:ident,)+ })+) => {
269        $($(
270
271        impl<FORMAT, STRICTNESS> SerializeAs<DurationSigned> for $outer<FORMAT, STRICTNESS>
272        where
273            FORMAT: Format,
274            STRICTNESS: Strictness,
275            $inner<FORMAT, STRICTNESS>: SerializeAs<DurationSigned>
276        {
277            fn serialize_as<S>(source: &DurationSigned, serializer: S) -> Result<S::Ok, S::Error>
278            where
279                S: Serializer,
280            {
281                let value = source.checked_mul($factor).ok_or_else(|| S::Error::custom("Failed to serialize value as the value cannot be represented."))?;
282                $inner::<FORMAT, STRICTNESS>::serialize_as(&value, serializer)
283            }
284        }
285
286        impl<'de, FORMAT, STRICTNESS> DeserializeAs<'de, DurationSigned> for $outer<FORMAT, STRICTNESS>
287        where
288            FORMAT: Format,
289            STRICTNESS: Strictness,
290            $inner<FORMAT, STRICTNESS>: DeserializeAs<'de, DurationSigned>,
291        {
292            fn deserialize_as<D>(deserializer: D) -> Result<DurationSigned, D::Error>
293            where
294                D: Deserializer<'de>,
295            {
296                let dur = $inner::<FORMAT, STRICTNESS>::deserialize_as(deserializer)?;
297                let dur = dur.checked_div($factor).ok_or_else(|| D::Error::custom("Failed to deserialize value as the value cannot be represented."))?;
298                Ok(dur)
299            }
300        }
301
302        )+)+    };
303}
304duration_impls!(
305    DurationSeconds {
306        1000u32 => DurationMilliSeconds,
307        1_000_000u32 => DurationMicroSeconds,
308        1_000_000_000u32 => DurationNanoSeconds,
309    }
310    DurationSecondsWithFrac {
311        1000u32 => DurationMilliSecondsWithFrac,
312        1_000_000u32 => DurationMicroSecondsWithFrac,
313        1_000_000_000u32 => DurationNanoSecondsWithFrac,
314    }
315);
316
317struct DurationVisitorFlexible;
318impl Visitor<'_> for DurationVisitorFlexible {
319    type Value = DurationSigned;
320
321    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
322        formatter.write_str("an integer, a float, or a string containing a number")
323    }
324
325    fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
326    where
327        E: DeError,
328    {
329        let sign = if value >= 0 {
330            Sign::Positive
331        } else {
332            Sign::Negative
333        };
334        Ok(DurationSigned::new(sign, value.unsigned_abs(), 0))
335    }
336
337    fn visit_u64<E>(self, secs: u64) -> Result<Self::Value, E>
338    where
339        E: DeError,
340    {
341        Ok(DurationSigned::new(Sign::Positive, secs, 0))
342    }
343
344    fn visit_f64<E>(self, secs: f64) -> Result<Self::Value, E>
345    where
346        E: DeError,
347    {
348        utils::duration_signed_from_secs_f64(secs).map_err(DeError::custom)
349    }
350
351    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
352    where
353        E: DeError,
354    {
355        match parse_float_into_time_parts(value) {
356            Ok((sign, seconds, subseconds)) => Ok(DurationSigned::new(sign, seconds, subseconds)),
357            Err(ParseFloatError::InvalidValue) => {
358                Err(DeError::invalid_value(Unexpected::Str(value), &self))
359            }
360            Err(ParseFloatError::Custom(msg)) => Err(DeError::custom(msg)),
361        }
362    }
363}
364
365impl<'de> DeserializeAs<'de, DurationSigned> for DurationSeconds<u64, Strict> {
366    fn deserialize_as<D>(deserializer: D) -> Result<DurationSigned, D::Error>
367    where
368        D: Deserializer<'de>,
369    {
370        u64::deserialize(deserializer).map(|secs: u64| DurationSigned::new(Sign::Positive, secs, 0))
371    }
372}
373
374impl<'de> DeserializeAs<'de, DurationSigned> for DurationSeconds<i64, Strict> {
375    fn deserialize_as<D>(deserializer: D) -> Result<DurationSigned, D::Error>
376    where
377        D: Deserializer<'de>,
378    {
379        i64::deserialize(deserializer).map(|secs: i64| {
380            let sign = match secs.is_negative() {
381                true => Sign::Negative,
382                false => Sign::Positive,
383            };
384            DurationSigned::new(sign, secs.abs_diff(0), 0)
385        })
386    }
387}
388
389// round() only works on std
390#[cfg(feature = "std")]
391impl<'de> DeserializeAs<'de, DurationSigned> for DurationSeconds<f64, Strict> {
392    fn deserialize_as<D>(deserializer: D) -> Result<DurationSigned, D::Error>
393    where
394        D: Deserializer<'de>,
395    {
396        let val = f64::deserialize(deserializer)?.round();
397        utils::duration_signed_from_secs_f64(val).map_err(DeError::custom)
398    }
399}
400
401#[cfg(feature = "alloc")]
402impl<'de> DeserializeAs<'de, DurationSigned> for DurationSeconds<String, Strict> {
403    fn deserialize_as<D>(deserializer: D) -> Result<DurationSigned, D::Error>
404    where
405        D: Deserializer<'de>,
406    {
407        struct DurationDeserializationVisitor;
408
409        impl Visitor<'_> for DurationDeserializationVisitor {
410            type Value = DurationSigned;
411
412            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
413                formatter.write_str("a string containing a number")
414            }
415
416            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
417            where
418                E: DeError,
419            {
420                let secs: i64 = value.parse().map_err(DeError::custom)?;
421                let sign = match secs.is_negative() {
422                    true => Sign::Negative,
423                    false => Sign::Positive,
424                };
425                Ok(DurationSigned::new(sign, secs.abs_diff(0), 0))
426            }
427        }
428
429        deserializer.deserialize_str(DurationDeserializationVisitor)
430    }
431}
432
433impl<'de, FORMAT> DeserializeAs<'de, DurationSigned> for DurationSeconds<FORMAT, Flexible>
434where
435    FORMAT: Format,
436{
437    fn deserialize_as<D>(deserializer: D) -> Result<DurationSigned, D::Error>
438    where
439        D: Deserializer<'de>,
440    {
441        deserializer.deserialize_any(DurationVisitorFlexible)
442    }
443}
444
445impl<'de> DeserializeAs<'de, DurationSigned> for DurationSecondsWithFrac<f64, Strict> {
446    fn deserialize_as<D>(deserializer: D) -> Result<DurationSigned, D::Error>
447    where
448        D: Deserializer<'de>,
449    {
450        let val = f64::deserialize(deserializer)?;
451        utils::duration_signed_from_secs_f64(val).map_err(DeError::custom)
452    }
453}
454
455#[cfg(feature = "alloc")]
456impl<'de> DeserializeAs<'de, DurationSigned> for DurationSecondsWithFrac<String, Strict> {
457    fn deserialize_as<D>(deserializer: D) -> Result<DurationSigned, D::Error>
458    where
459        D: Deserializer<'de>,
460    {
461        let value = String::deserialize(deserializer)?;
462        match parse_float_into_time_parts(&value) {
463            Ok((sign, seconds, subseconds)) => Ok(DurationSigned {
464                sign,
465                duration: Duration::new(seconds, subseconds),
466            }),
467            Err(ParseFloatError::InvalidValue) => Err(DeError::invalid_value(
468                Unexpected::Str(&value),
469                &"a string containing an integer or float",
470            )),
471            Err(ParseFloatError::Custom(msg)) => Err(DeError::custom(msg)),
472        }
473    }
474}
475
476impl<'de, FORMAT> DeserializeAs<'de, DurationSigned> for DurationSecondsWithFrac<FORMAT, Flexible>
477where
478    FORMAT: Format,
479{
480    fn deserialize_as<D>(deserializer: D) -> Result<DurationSigned, D::Error>
481    where
482        D: Deserializer<'de>,
483    {
484        deserializer.deserialize_any(DurationVisitorFlexible)
485    }
486}
487
488#[cfg_attr(test, derive(Debug, PartialEq))]
489pub(crate) enum ParseFloatError {
490    InvalidValue,
491    #[cfg(not(feature = "alloc"))]
492    Custom(&'static str),
493    #[cfg(feature = "alloc")]
494    Custom(String),
495}
496
497fn parse_float_into_time_parts(mut value: &str) -> Result<(Sign, u64, u32), ParseFloatError> {
498    let sign = match value.chars().next() {
499        // Advance by the size of the parsed char
500        Some('+') => {
501            value = &value[1..];
502            Sign::Positive
503        }
504        Some('-') => {
505            value = &value[1..];
506            Sign::Negative
507        }
508        _ => Sign::Positive,
509    };
510
511    let partslen = value.split('.').count();
512    let mut parts = value.split('.');
513    match partslen {
514        1 => {
515            let seconds = parts.next().expect("Float contains exactly one part");
516            if let Ok(seconds) = seconds.parse() {
517                Ok((sign, seconds, 0))
518            } else {
519                Err(ParseFloatError::InvalidValue)
520            }
521        }
522        2 => {
523            let seconds = parts.next().expect("Float contains exactly one part");
524            if let Ok(seconds) = seconds.parse() {
525                let subseconds = parts.next().expect("Float contains exactly one part");
526                let subseclen = u32::try_from(subseconds.chars().count()).map_err(|_| {
527                    #[cfg(feature = "alloc")]
528                    return ParseFloatError::Custom(alloc::format!(
529                        "Duration and Timestamps with no more than 9 digits precision, but '{value}' has more"
530                    ));
531                    #[cfg(not(feature = "alloc"))]
532                    return ParseFloatError::Custom(
533                        "Duration and Timestamps with no more than 9 digits precision",
534                    );
535                })?;
536                if subseclen > 9 {
537                    #[cfg(feature = "alloc")]
538                    return Err(ParseFloatError::Custom(alloc::format!(
539                        "Duration and Timestamps with no more than 9 digits precision, but '{value}' has more"
540                    )));
541                    #[cfg(not(feature = "alloc"))]
542                    return Err(ParseFloatError::Custom(
543                        "Duration and Timestamps with no more than 9 digits precision",
544                    ));
545                }
546
547                if let Ok(mut subseconds) = subseconds.parse() {
548                    // convert subseconds to nanoseconds (10^-9), require 9 places for nanoseconds
549                    subseconds *= 10u32.pow(9 - subseclen);
550                    Ok((sign, seconds, subseconds))
551                } else {
552                    Err(ParseFloatError::InvalidValue)
553                }
554            } else {
555                Err(ParseFloatError::InvalidValue)
556            }
557        }
558
559        _ => Err(ParseFloatError::InvalidValue),
560    }
561}
562
563#[test]
564fn test_parse_float_into_time_parts() {
565    // Test normal behavior
566    assert_eq!(
567        Ok((Sign::Positive, 123, 456_000_000)),
568        parse_float_into_time_parts("+123.456")
569    );
570    assert_eq!(
571        Ok((Sign::Negative, 123, 987_000)),
572        parse_float_into_time_parts("-123.000987")
573    );
574    assert_eq!(
575        Ok((Sign::Positive, 18446744073709551615, 123_456_789)),
576        parse_float_into_time_parts("18446744073709551615.123456789")
577    );
578
579    // Test behavior around 0
580    assert_eq!(
581        Ok((Sign::Positive, 0, 456_000_000)),
582        parse_float_into_time_parts("+0.456")
583    );
584    assert_eq!(
585        Ok((Sign::Negative, 0, 987_000)),
586        parse_float_into_time_parts("-0.000987")
587    );
588    assert_eq!(
589        Ok((Sign::Positive, 0, 123_456_789)),
590        parse_float_into_time_parts("0.123456789")
591    );
592}