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