From dab4b87ecf169046b8ded5305fc938a847d5a4b5 Mon Sep 17 00:00:00 2001 From: LeoniePhiline <22329650+LeoniePhiline@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:54:42 +0100 Subject: [PATCH 1/2] test: verify serde YAML compatibility Deserializing `jiff` types from YAML is not currently possible. Neither `serde_yaml` nor its maintained fork `serde_yml` support deserializing from bytes. These tests serve in both - proving the current incompatibility - verifying the fix in the following commit, which migrates `Deserialize` implementations to `deserializer.deserialize_str`. Related to #148, #138 --- Cargo.toml | 1 + src/civil/date.rs | 28 ++++++++++++++++++++++++++++ src/civil/datetime.rs | 30 ++++++++++++++++++++++++++++++ src/civil/time.rs | 29 +++++++++++++++++++++++++++++ src/signed_duration.rs | 30 ++++++++++++++++++++++++++++++ src/span.rs | 36 ++++++++++++++++++++++++++++++++++++ src/timestamp.rs | 40 +++++++++++++++++++++++++++++++++++++++- src/zoned.rs | 38 +++++++++++++++++++++++++++++++++++++- 8 files changed, 230 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0b521e8b..2a3d6780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,6 +110,7 @@ jiff = { path = "./", features = ["serde"] } quickcheck = { version = "1.0.3", default-features = false } serde = { version = "1.0.203", features = ["derive"] } serde_json = "1.0.117" +serde_yml = "0.0.12" tabwriter = "1.4.0" time = { version = "0.3.36", features = ["local-offset", "macros", "parsing"] } tzfile = "0.1.3" diff --git a/src/civil/date.rs b/src/civil/date.rs index 7efa93cf..50ff2f3f 100644 --- a/src/civil/date.rs +++ b/src/civil/date.rs @@ -3651,6 +3651,8 @@ fn day_of_year(year: Year, day: i16) -> Result { #[cfg(test)] mod tests { + use std::io::Cursor; + use crate::{civil::date, tz::TimeZone, Timestamp, ToSpan}; use super::*; @@ -4006,4 +4008,30 @@ mod tests { d1 == got } } + + /// # `serde` deserializer compatibility test + /// + /// Serde YAML used to be unable to deserialize `jiff` types, + /// as deserializing from bytes is not supported by the deserializer. + /// + /// - + /// - + #[test] + fn civil_date_deserialize_yaml() { + let expected = date(2024, 10, 31); + + let deserialized: Date = serde_yml::from_str("2024-10-31").unwrap(); + + assert_eq!(deserialized, expected); + + let deserialized: Date = + serde_yml::from_slice("2024-10-31".as_bytes()).unwrap(); + + assert_eq!(deserialized, expected); + + let cursor = Cursor::new(b"2024-10-31"); + let deserialized: Date = serde_yml::from_reader(cursor).unwrap(); + + assert_eq!(deserialized, expected); + } } diff --git a/src/civil/datetime.rs b/src/civil/datetime.rs index 252fe9d6..b581ba63 100644 --- a/src/civil/datetime.rs +++ b/src/civil/datetime.rs @@ -4134,6 +4134,8 @@ impl DateTimeWith { #[cfg(test)] mod tests { + use std::io::Cursor; + use crate::{ civil::{date, time}, RoundMode, ToSpan, Unit, @@ -4277,4 +4279,32 @@ mod tests { assert_eq!(12, core::mem::size_of::()); } } + + /// # `serde` deserializer compatibility test + /// + /// Serde YAML used to be unable to deserialize `jiff` types, + /// as deserializing from bytes is not supported by the deserializer. + /// + /// - + /// - + #[test] + fn civil_datetime_deserialize_yaml() { + let expected = datetime(2024, 10, 31, 16, 33, 53, 123456789); + + let deserialized: DateTime = + serde_yml::from_str("2024-10-31 16:33:53.123456789").unwrap(); + + assert_eq!(deserialized, expected); + + let deserialized: DateTime = + serde_yml::from_slice("2024-10-31 16:33:53.123456789".as_bytes()) + .unwrap(); + + assert_eq!(deserialized, expected); + + let cursor = Cursor::new(b"2024-10-31 16:33:53.123456789"); + let deserialized: DateTime = serde_yml::from_reader(cursor).unwrap(); + + assert_eq!(deserialized, expected); + } } diff --git a/src/civil/time.rs b/src/civil/time.rs index 3cceff99..a4ffbe35 100644 --- a/src/civil/time.rs +++ b/src/civil/time.rs @@ -3144,6 +3144,8 @@ impl TimeWith { #[cfg(test)] mod tests { + use std::io::Cursor; + use crate::{civil::time, ToSpan}; use super::*; @@ -3360,4 +3362,31 @@ mod tests { let expected = max.rem_euclid(t::NANOS_PER_CIVIL_DAY.bound()); assert_eq!(got, expected); } + + /// # `serde` deserializer compatibility test + /// + /// Serde YAML used to be unable to deserialize `jiff` types, + /// as deserializing from bytes is not supported by the deserializer. + /// + /// - + /// - + #[test] + fn civil_time_deserialize_yaml() { + let expected = time(16, 35, 4, 987654321); + + let deserialized: Time = + serde_yml::from_str("16:35:04.987654321").unwrap(); + + assert_eq!(deserialized, expected); + + let deserialized: Time = + serde_yml::from_slice("16:35:04.987654321".as_bytes()).unwrap(); + + assert_eq!(deserialized, expected); + + let cursor = Cursor::new(b"16:35:04.987654321"); + let deserialized: Time = serde_yml::from_reader(cursor).unwrap(); + + assert_eq!(deserialized, expected); + } } diff --git a/src/signed_duration.rs b/src/signed_duration.rs index 2b5f157d..2f22068d 100644 --- a/src/signed_duration.rs +++ b/src/signed_duration.rs @@ -2069,6 +2069,8 @@ impl<'de> serde::Deserialize<'de> for SignedDuration { #[cfg(test)] mod tests { + use std::io::Cursor; + use super::*; #[test] @@ -2186,4 +2188,32 @@ mod tests { assert_eq!(None, add((i64::MAX, 1), (0, 999_999_999))); assert_eq!(None, add((i64::MIN, -1), (0, -999_999_999))); } + + /// # `serde` deserializer compatibility test + /// + /// Serde YAML used to be unable to deserialize `jiff` types, + /// as deserializing from bytes is not supported by the deserializer. + /// + /// - + /// - + #[test] + fn signed_duration_deserialize_yaml() { + let expected = SignedDuration::from_secs(123456789); + + let deserialized: SignedDuration = + serde_yml::from_str("PT34293h33m9s").unwrap(); + + assert_eq!(deserialized, expected); + + let deserialized: SignedDuration = + serde_yml::from_slice("PT34293h33m9s".as_bytes()).unwrap(); + + assert_eq!(deserialized, expected); + + let cursor = Cursor::new(b"PT34293h33m9s"); + let deserialized: SignedDuration = + serde_yml::from_reader(cursor).unwrap(); + + assert_eq!(deserialized, expected); + } } diff --git a/src/span.rs b/src/span.rs index 1db1c75d..76ffb977 100644 --- a/src/span.rs +++ b/src/span.rs @@ -5998,6 +5998,8 @@ fn clamp_relative_span( #[cfg(test)] mod tests { + use std::io::Cursor; + use crate::{civil::date, RoundMode}; use super::*; @@ -6359,4 +6361,38 @@ mod tests { quickcheck::TestResult::from_bool(nanos == got.to_invariant_nanoseconds()) } } + + /// # `serde` deserializer compatibility test + /// + /// Serde YAML used to be unable to deserialize `jiff` types, + /// as deserializing from bytes is not supported by the deserializer. + /// + /// - + /// - + #[test] + fn span_deserialize_yaml() { + let expected = Span::new() + .years(1) + .months(2) + .weeks(3) + .days(4) + .hours(5) + .minutes(6) + .seconds(7); + + let deserialized: Span = + serde_yml::from_str("P1y2m3w4dT5h6m7s").unwrap(); + + assert_eq!(deserialized, expected); + + let deserialized: Span = + serde_yml::from_slice("P1y2m3w4dT5h6m7s".as_bytes()).unwrap(); + + assert_eq!(deserialized, expected); + + let cursor = Cursor::new(b"P1y2m3w4dT5h6m7s"); + let deserialized: Span = serde_yml::from_reader(cursor).unwrap(); + + assert_eq!(deserialized, expected); + } } diff --git a/src/timestamp.rs b/src/timestamp.rs index 5510114b..b6544743 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -3513,7 +3513,12 @@ impl From<(Unit, i64)> for TimestampRound { #[cfg(test)] mod tests { - use crate::{civil, tz::Offset}; + use std::io::Cursor; + + use crate::{ + civil::{self, datetime}, + tz::Offset, + }; use super::*; @@ -3687,4 +3692,37 @@ mod tests { t == got } } + + /// # `serde` deserializer compatibility test + /// + /// Serde YAML used to be unable to deserialize `jiff` types, + /// as deserializing from bytes is not supported by the deserializer. + /// + /// - + /// - + #[test] + fn timestamp_deserialize_yaml() { + let expected = datetime(2024, 10, 31, 16, 33, 53, 123456789) + .intz("UTC") + .unwrap() + .timestamp(); + + let deserialized: Timestamp = + serde_yml::from_str("2024-10-31T16:33:53.123456789+00:00[UTC]") + .unwrap(); + + assert_eq!(deserialized, expected); + + let deserialized: Timestamp = serde_yml::from_slice( + "2024-10-31T16:33:53.123456789+00:00[UTC]".as_bytes(), + ) + .unwrap(); + + assert_eq!(deserialized, expected); + + let cursor = Cursor::new(b"2024-10-31T16:33:53.123456789+00:00[UTC]"); + let deserialized: Timestamp = serde_yml::from_reader(cursor).unwrap(); + + assert_eq!(deserialized, expected); + } } diff --git a/src/zoned.rs b/src/zoned.rs index 5481fb04..758a6063 100644 --- a/src/zoned.rs +++ b/src/zoned.rs @@ -5146,7 +5146,12 @@ fn day_length( #[cfg(test)] mod tests { - use crate::{civil::date, ToSpan}; + use std::io::Cursor; + + use crate::{ + civil::{date, datetime}, + ToSpan, + }; use super::*; @@ -5238,4 +5243,35 @@ mod tests { assert_eq!(40, core::mem::size_of::()); } } + + /// # `serde` deserializer compatibility test + /// + /// Serde YAML used to be unable to deserialize `jiff` types, + /// as deserializing from bytes is not supported by the deserializer. + /// + /// - + /// - + #[test] + fn zoned_deserialize_yaml() { + let expected = + datetime(2024, 10, 31, 16, 33, 53, 123456789).intz("UTC").unwrap(); + + let deserialized: Zoned = + serde_yml::from_str("2024-10-31T16:33:53.123456789+00:00[UTC]") + .unwrap(); + + assert_eq!(deserialized, expected); + + let deserialized: Zoned = serde_yml::from_slice( + "2024-10-31T16:33:53.123456789+00:00[UTC]".as_bytes(), + ) + .unwrap(); + + assert_eq!(deserialized, expected); + + let cursor = Cursor::new(b"2024-10-31T16:33:53.123456789+00:00[UTC]"); + let deserialized: Zoned = serde_yml::from_reader(cursor).unwrap(); + + assert_eq!(deserialized, expected); + } } From d69f3759b1f2b4165b8d5e25a65d427740ed37b2 Mon Sep 17 00:00:00 2001 From: LeoniePhiline <22329650+LeoniePhiline@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:57:58 +0100 Subject: [PATCH 2/2] fix: deserialize from str for serde YAML compatibility Deserializing `jiff` types from YAML was previously not possible: Neither `serde_yaml` nor its maintained fork `serde_yml` support deserializing from bytes. This changset migrates `Deserialize` implementations to `deserializer.deserialize_str`, thus providing serde YAML compatibility. Fixes #148, #138 --- src/civil/date.rs | 2 +- src/civil/datetime.rs | 2 +- src/civil/time.rs | 2 +- src/signed_duration.rs | 2 +- src/span.rs | 2 +- src/timestamp.rs | 2 +- src/zoned.rs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/civil/date.rs b/src/civil/date.rs index 50ff2f3f..7585e1d9 100644 --- a/src/civil/date.rs +++ b/src/civil/date.rs @@ -2490,7 +2490,7 @@ impl<'de> serde::Deserialize<'de> for Date { } } - deserializer.deserialize_bytes(DateVisitor) + deserializer.deserialize_str(DateVisitor) } } diff --git a/src/civil/datetime.rs b/src/civil/datetime.rs index b581ba63..2321d489 100644 --- a/src/civil/datetime.rs +++ b/src/civil/datetime.rs @@ -2604,7 +2604,7 @@ impl<'de> serde::Deserialize<'de> for DateTime { } } - deserializer.deserialize_bytes(DateTimeVisitor) + deserializer.deserialize_str(DateTimeVisitor) } } diff --git a/src/civil/time.rs b/src/civil/time.rs index a4ffbe35..c7d252bf 100644 --- a/src/civil/time.rs +++ b/src/civil/time.rs @@ -2053,7 +2053,7 @@ impl<'de> serde::Deserialize<'de> for Time { } } - deserializer.deserialize_bytes(TimeVisitor) + deserializer.deserialize_str(TimeVisitor) } } diff --git a/src/signed_duration.rs b/src/signed_duration.rs index 2f22068d..b344b97e 100644 --- a/src/signed_duration.rs +++ b/src/signed_duration.rs @@ -2063,7 +2063,7 @@ impl<'de> serde::Deserialize<'de> for SignedDuration { } } - deserializer.deserialize_bytes(SignedDurationVisitor) + deserializer.deserialize_str(SignedDurationVisitor) } } diff --git a/src/span.rs b/src/span.rs index 76ffb977..40cec352 100644 --- a/src/span.rs +++ b/src/span.rs @@ -3562,7 +3562,7 @@ impl<'de> serde::Deserialize<'de> for Span { } } - deserializer.deserialize_bytes(SpanVisitor) + deserializer.deserialize_str(SpanVisitor) } } diff --git a/src/timestamp.rs b/src/timestamp.rs index b6544743..92a4ce1a 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -2721,7 +2721,7 @@ impl<'de> serde::Deserialize<'de> for Timestamp { } } - deserializer.deserialize_bytes(TimestampVisitor) + deserializer.deserialize_str(TimestampVisitor) } } diff --git a/src/zoned.rs b/src/zoned.rs index 758a6063..d9eb862b 100644 --- a/src/zoned.rs +++ b/src/zoned.rs @@ -3360,7 +3360,7 @@ impl<'de> serde::Deserialize<'de> for Zoned { } } - deserializer.deserialize_bytes(ZonedVisitor) + deserializer.deserialize_str(ZonedVisitor) } }