From 6d46fd2147043c1ae99b5b88bacdf0f6c8757c90 Mon Sep 17 00:00:00 2001 From: loqusion <38332081+loqusion@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:53:48 -0500 Subject: [PATCH] feat(datetime): impl Serialize/Deserialize for Date/Time --- crates/toml/tests/testsuite/serde.rs | 108 +++++++++++++++++++++++++++ crates/toml_datetime/src/datetime.rs | 91 ++++++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/crates/toml/tests/testsuite/serde.rs b/crates/toml/tests/testsuite/serde.rs index c3a10eb7..a2a63cd6 100644 --- a/crates/toml/tests/testsuite/serde.rs +++ b/crates/toml/tests/testsuite/serde.rs @@ -1270,6 +1270,114 @@ fn datetime_offset_issue_496() { assert_data_eq!(output, original.raw()); } +#[test] +fn serialize_date() { + use toml::value::Date; + + #[derive(Serialize)] + struct Document { + date: Date, + } + + let input = Document { + date: Date { + year: 2024, + month: 1, + day: 1, + }, + }; + let raw = toml::to_string(&input).unwrap(); + assert_data_eq!( + raw, + str![[r#" +date = 2024-01-01 + +"#]] + .raw() + ); +} + +#[test] +fn serialize_time() { + use toml::value::Time; + + #[derive(Serialize)] + struct Document { + date: Time, + } + + let input = Document { + date: Time { + hour: 5, + minute: 0, + second: 0, + nanosecond: 0, + }, + }; + let raw = toml::to_string(&input).unwrap(); + assert_data_eq!( + raw, + str![[r#" +date = 05:00:00 + +"#]] + .raw() + ); +} + +#[test] +fn deserialize_date() { + use toml::value::Date; + + #[derive(Debug, Deserialize)] + struct Document { + date: Date, + } + + let document: Document = toml::from_str("date = 2024-01-01").unwrap(); + assert_eq!( + document.date, + Date { + year: 2024, + month: 1, + day: 1 + } + ); + + let err = toml::from_str::("date = 2024-01-01T05:00:00").unwrap_err(); + assert_data_eq!( + err.message(), + str!["invalid type: local datetime, expected local date"] + ); +} + +#[test] +fn deserialize_time() { + use toml::value::Time; + + #[derive(Debug, Deserialize)] + struct Document { + time: Time, + } + + let document: Document = toml::from_str("time = 05:00:00").unwrap(); + assert_eq!( + document.time, + Time { + hour: 5, + minute: 0, + second: 0, + nanosecond: 0, + } + ); + + let err = toml::from_str::("time = 2024-01-01T05:00:00").unwrap_err(); + assert_data_eq!( + err.message(), + str!["invalid type: local datetime, expected local time"] + ); +} + #[test] fn serialize_array_with_none_value() { #[derive(Serialize)] diff --git a/crates/toml_datetime/src/datetime.rs b/crates/toml_datetime/src/datetime.rs index e76b3b94..cea13b9f 100644 --- a/crates/toml_datetime/src/datetime.rs +++ b/crates/toml_datetime/src/datetime.rs @@ -184,6 +184,37 @@ pub enum Offset { }, } +impl Datetime { + #[cfg(feature = "serde")] + fn type_name(&self) -> &'static str { + match ( + self.date.is_some(), + self.time.is_some(), + self.offset.is_some(), + ) { + (true, true, true) => "offset datetime", + (true, true, false) => "local datetime", + (true, false, false) => Date::type_name(), + (false, true, false) => Time::type_name(), + _ => unreachable!("unsupported datetime combination"), + } + } +} + +impl Date { + #[cfg(feature = "serde")] + fn type_name() -> &'static str { + "local date" + } +} + +impl Time { + #[cfg(feature = "serde")] + fn type_name() -> &'static str { + "local time" + } +} + impl From for Datetime { fn from(other: Date) -> Self { Datetime { @@ -480,6 +511,26 @@ impl ser::Serialize for Datetime { } } +#[cfg(feature = "serde")] +impl ser::Serialize for Date { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + Datetime::from(*self).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl ser::Serialize for Time { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + Datetime::from(*self).serialize(serializer) + } +} + #[cfg(feature = "serde")] impl<'de> de::Deserialize<'de> for Datetime { fn deserialize(deserializer: D) -> Result @@ -513,6 +564,46 @@ impl<'de> de::Deserialize<'de> for Datetime { } } +#[cfg(feature = "serde")] +impl<'de> de::Deserialize<'de> for Date { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + match Datetime::deserialize(deserializer)? { + Datetime { + date: Some(date), + time: None, + offset: None, + } => Ok(date), + datetime => Err(de::Error::invalid_type( + de::Unexpected::Other(datetime.type_name()), + &Self::type_name(), + )), + } + } +} + +#[cfg(feature = "serde")] +impl<'de> de::Deserialize<'de> for Time { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + match Datetime::deserialize(deserializer)? { + Datetime { + date: None, + time: Some(time), + offset: None, + } => Ok(time), + datetime => Err(de::Error::invalid_type( + de::Unexpected::Other(datetime.type_name()), + &Self::type_name(), + )), + } + } +} + #[cfg(feature = "serde")] struct DatetimeKey;