Skip to main content

rustack_kms_model/
epoch_seconds.rs

1//! Epoch seconds serde helpers for timestamp fields.
2//!
3//! AWS KMS transmits timestamps as floating-point epoch seconds in JSON
4//! (e.g., `1710766800.123`). The `chrono::DateTime<Utc>` default serde
5//! would produce ISO 8601 strings, which the AWS SDK cannot parse.
6
7use chrono::{DateTime, TimeZone, Utc};
8use serde::{Deserialize, Deserializer, Serializer};
9
10/// Serde helpers for `Option<DateTime<Utc>>` as epoch seconds.
11pub mod option {
12    use super::{DateTime, Deserialize, Deserializer, Serializer, TimeZone, Utc};
13
14    /// Serialize `Option<DateTime<Utc>>` as epoch seconds (f64) or null.
15    pub fn serialize<S: Serializer>(dt: &Option<DateTime<Utc>>, s: S) -> Result<S::Ok, S::Error> {
16        match dt {
17            Some(d) => {
18                #[allow(clippy::cast_precision_loss)]
19                let secs = d.timestamp() as f64 + f64::from(d.timestamp_subsec_millis()) / 1000.0;
20                s.serialize_f64(secs)
21            }
22            None => s.serialize_none(),
23        }
24    }
25
26    /// Deserialize epoch seconds (f64) or null into `Option<DateTime<Utc>>`.
27    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<DateTime<Utc>>, D::Error> {
28        let opt: Option<f64> = Option::deserialize(d)?;
29        match opt {
30            Some(secs) => {
31                #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
32                let whole = secs.trunc() as i64;
33                #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
34                let nanos = (secs.fract() * 1_000_000_000.0) as u32;
35                Utc.timestamp_opt(whole, nanos)
36                    .single()
37                    .ok_or_else(|| serde::de::Error::custom("invalid epoch timestamp"))
38                    .map(Some)
39            }
40            None => Ok(None),
41        }
42    }
43}