From 783e35cb98283fd34519901950cfe584da419b32 Mon Sep 17 00:00:00 2001 From: Joel Hendrix Date: Fri, 10 Jan 2025 12:59:26 -0800 Subject: [PATCH] Add serde helpers for Unix timestamps (#1988) --- .../typespec_client_core/src/date/mod.rs | 1 + .../src/date/unix_time.rs | 134 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 sdk/typespec/typespec_client_core/src/date/unix_time.rs diff --git a/sdk/typespec/typespec_client_core/src/date/mod.rs b/sdk/typespec/typespec_client_core/src/date/mod.rs index 1386be95a8..4696bfb11b 100644 --- a/sdk/typespec/typespec_client_core/src/date/mod.rs +++ b/sdk/typespec/typespec_client_core/src/date/mod.rs @@ -19,6 +19,7 @@ pub use time::serde::timestamp; // RFC 3339 vs ISO 8601: pub mod iso8601; pub mod rfc7231; +pub mod unix_time; /// RFC 3339: Date and Time on the Internet: Timestamps. /// diff --git a/sdk/typespec/typespec_client_core/src/date/unix_time.rs b/sdk/typespec/typespec_client_core/src/date/unix_time.rs new file mode 100644 index 0000000000..9390286515 --- /dev/null +++ b/sdk/typespec/typespec_client_core/src/date/unix_time.rs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +//! Unix timestamp serde helpers. +use serde::{de, Deserialize, Deserializer, Serializer}; +use time::{OffsetDateTime, UtcOffset}; + +/// Deserialize a Unix timestamp into an [`OffsetDateTime`]. +pub fn deserialize<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let i = i64::deserialize(deserializer)?; + OffsetDateTime::from_unix_timestamp(i).map_err(de::Error::custom) +} + +/// Serialize an [`OffsetDateTime`] to a Unix timestamp. +pub fn serialize(date: &OffsetDateTime, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_i64(date.to_offset(UtcOffset::UTC).unix_timestamp()) +} + +pub mod option { + use serde::{Deserialize, Deserializer, Serializer}; + use time::{OffsetDateTime, UtcOffset}; + + /// Deserialize a Unix timestamp into an optional [`OffsetDateTime`]. + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let i: Option = Option::deserialize(deserializer)?; + i.map(|i| OffsetDateTime::from_unix_timestamp(i).map_err(serde::de::Error::custom)) + .transpose() + } + + /// Serialize an optional [`OffsetDateTime`] to a Unix timestamp. + pub fn serialize(date: &Option, serializer: S) -> Result + where + S: Serializer, + { + if let Some(date) = date { + serializer.serialize_i64(date.to_offset(UtcOffset::UTC).unix_timestamp()) + } else { + serializer.serialize_none() + } + } +} + +#[cfg(test)] +mod tests { + use crate::json::{from_json, to_json}; + use serde::{Deserialize, Serialize}; + use time::macros::datetime; + + #[derive(Deserialize, Serialize)] + struct TestType { + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "crate::date::unix_time::option" + )] + optional_timestamp: Option, + + #[serde(with = "crate::date::unix_time")] + required_timestamp: time::OffsetDateTime, + } + + #[test] + fn test_deserialize_none() -> crate::Result<()> { + let json_body = r#"{"required_timestamp":1627904772}"#; + let test_type: TestType = from_json(json_body)?; + assert_eq!(test_type.optional_timestamp, None); + assert_eq!( + test_type.required_timestamp, + datetime!(2021-08-02 11:46:12 UTC) + ); + Ok(()) + } + + #[test] + fn test_deserialize_null() -> crate::Result<()> { + let json_body = r#"{"optional_timestamp":null,"required_timestamp":1627904772}"#; + let test_type: TestType = from_json(json_body)?; + assert_eq!(test_type.optional_timestamp, None); + assert_eq!( + test_type.required_timestamp, + datetime!(2021-08-02 11:46:12 UTC) + ); + Ok(()) + } + + #[test] + fn test_deserialize_some() -> crate::Result<()> { + let json_body = r#"{"optional_timestamp":1625136302,"required_timestamp":1627904772}"#; + let test_type: TestType = from_json(json_body)?; + assert_eq!( + test_type.optional_timestamp, + Some(datetime!(2021-07-01 10:45:02 UTC)) + ); + assert_eq!( + test_type.required_timestamp, + datetime!(2021-08-02 11:46:12 UTC) + ); + Ok(()) + } + + #[test] + fn test_serialize_none() -> crate::Result<()> { + let test_type = TestType { + optional_timestamp: None, + required_timestamp: datetime!(2021-08-02 11:46:12 UTC), + }; + let json_body = to_json(&test_type)?; + assert_eq!(json_body, r#"{"required_timestamp":1627904772}"#); + Ok(()) + } + + #[test] + fn test_serialize_some() -> crate::Result<()> { + let test_type = TestType { + optional_timestamp: Some(datetime!(2021-07-01 10:45:02 UTC)), + required_timestamp: datetime!(2021-08-02 11:46:12 UTC), + }; + let json_body = to_json(&test_type)?; + assert_eq!( + json_body, + r#"{"optional_timestamp":1625136302,"required_timestamp":1627904772}"# + ); + Ok(()) + } +}