diff --git a/Cargo.toml b/Cargo.toml index 2b14daf..f718137 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = ["derive"] default = [] [dependencies] -chrono = { version = "0.4.38", features = ["serde"], optional = true } +chrono = { version = "0.4.39", features = ["serde"], optional = true } derive = { version = "0.10.0", package = "revision-derive", path = "derive" } geo = { version = "0.28.0", features = ["use-serde"], optional = true } ordered-float = { version = "4.2.2", optional = true } diff --git a/README.md b/README.md index f8c1f49..8e8e571 100755 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ compatibility, but where the design of the data format evolves over time.

`Revision` is a framework for revision-tolerant serialization and deserialization with support for schema evolution over time. It allows for easy revisioning of structs and enums for data storage requirements which need to support backwards compatibility, but where the design of the data structures evolve over time. Revision enables data that was serialized at older revisions to be seamlessly deserialized and converted into the latest data structures. It uses [bincode](https://crates.io/crates/bincode) for serialization and deserialization. -The `Revisioned` trait is automatically implemented for the following primitives: `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `f32`, `f64`, `char`, `String`, `Vec`, Arrays up to 32 elements, `Option`, `Box`, `Bound`, `Wrapping`, `Reverse`, `(A, B)`, `(A, B, C)`, `(A, B, C, D)`, `(A, B, C, D, E)`, `Duration`, `HashMap`, `BTreeMap`, `HashSet`, `BTreeSet`, `BinaryHeap`, `Result`, `Cow<'_, T>`, `Decimal`, `regex::Regex`, `uuid::Uuid`, `chrono::DateTime`, `geo::Point`, `geo::LineString` `geo::Polygon`, `geo::MultiPoint`, `geo::MultiLineString`, `geo::MultiPolygon`, and `ordered_float::NotNan`. +The `Revisioned` trait is automatically implemented for the following primitives: `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `f32`, `f64`, `char`, `String`, `Vec`, Arrays up to 32 elements, `Option`, `Box`, `Bound`, `Wrapping`, `Reverse`, `(A, B)`, `(A, B, C)`, `(A, B, C, D)`, `(A, B, C, D, E)`, `Duration`, `HashMap`, `BTreeMap`, `HashSet`, `BTreeSet`, `BinaryHeap`, `Result`, `Cow<'_, T>`, `Decimal`, `regex::Regex`, `uuid::Uuid`, `chrono::Duration`, `chrono::DateTime`, `geo::Point`, `geo::LineString` `geo::Polygon`, `geo::MultiPoint`, `geo::MultiLineString`, `geo::MultiPolygon`, and `ordered_float::NotNan`. ## Inspiration diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 35fa786..bfa88d5 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -6,8 +6,8 @@ //! String, Vec, Arrays up to 32 elements, Option, Box, Bound, Wrapping, //! (A, B), (A, B, C), (A, B, C, D), (A, B, C, D, E), Duration, HashMap, //! BTreeMap, Result, Cow<'_, T>, Decimal, regex::Regex, uuid::Uuid, -//! chrono::DateTime, geo::Point, geo::LineString geo::Polygon, geo::MultiPoint, -//! geo::MultiLineString, and geo::MultiPolygon. +//! chrono::Duration, chrono::DateTime, geo::Point, geo::LineString geo::Polygon, +//! geo::MultiPoint, geo::MultiLineString, and geo::MultiPolygon. use proc_macro::TokenStream; diff --git a/src/implementations/chrono.rs b/src/implementations/chrono.rs index 59f1586..67671a7 100644 --- a/src/implementations/chrono.rs +++ b/src/implementations/chrono.rs @@ -2,7 +2,7 @@ use super::super::Error; use super::super::Revisioned; -use chrono::{offset::TimeZone, DateTime, Datelike, NaiveDate, NaiveTime, Timelike, Utc}; +use chrono::{offset::TimeZone, DateTime, Datelike, Duration, NaiveDate, NaiveTime, Timelike, Utc}; impl Revisioned for DateTime { #[inline] @@ -74,12 +74,48 @@ impl Revisioned for NaiveTime { } } +impl Revisioned for Duration { + #[inline] + fn serialize_revisioned(&self, writer: &mut W) -> Result<(), Error> { + let mut secs = self.num_seconds(); + let mut nano = self.subsec_nanos(); + + if nano < 0 { + secs = secs + .checked_sub(1) + .ok_or_else(|| Error::Serialize("invalid duration".to_string()))?; + nano = nano + .checked_add(1_000_000_000) + .ok_or_else(|| Error::Serialize("invalid duration".to_string()))?; + } + + secs.serialize_revisioned(writer)?; + nano.serialize_revisioned(writer)?; + + Ok(()) + } + + #[inline] + fn deserialize_revisioned(reader: &mut R) -> Result { + let secs = ::deserialize_revisioned(reader)?; + let nano = ::deserialize_revisioned(reader)?; + let nano = + u32::try_from(nano).map_err(|_| Error::Deserialize("invalid duration".to_string()))?; + + Duration::new(secs, nano).ok_or_else(|| Error::Deserialize("invalid duration".to_string())) + } + + fn revision() -> u16 { + 1 + } +} + #[cfg(test)] mod tests { use super::DateTime; use super::Revisioned; use super::Utc; - use chrono::{NaiveDate, NaiveTime}; + use chrono::{Duration, NaiveDate, NaiveTime}; #[test] fn test_datetime_min() { @@ -142,4 +178,34 @@ mod tests { let out = ::deserialize_revisioned(&mut mem.as_slice()).unwrap(); assert_eq!(val, out); } + + #[test] + fn test_duration_min() { + let val = Duration::MIN; + let mut mem: Vec = vec![]; + val.serialize_revisioned(&mut mem).unwrap(); + assert_eq!(mem.len(), 14); + let out = ::deserialize_revisioned(&mut mem.as_slice()).unwrap(); + assert_eq!(val, out); + } + + #[test] + fn test_duration_zero() { + let val = Duration::zero(); + let mut mem: Vec = vec![]; + val.serialize_revisioned(&mut mem).unwrap(); + assert_eq!(mem.len(), 2); + let out = ::deserialize_revisioned(&mut mem.as_slice()).unwrap(); + assert_eq!(val, out); + } + + #[test] + fn test_duration_max() { + let val = Duration::MAX; + let mut mem: Vec = vec![]; + val.serialize_revisioned(&mut mem).unwrap(); + assert_eq!(mem.len(), 14); + let out = ::deserialize_revisioned(&mut mem.as_slice()).unwrap(); + assert_eq!(val, out); + } } diff --git a/src/lib.rs b/src/lib.rs index e619c03..5ee843d 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char, //! String, Vec, Arrays up to 32 elements, Option, Box, Bound, Wrapping, //! (A, B), (A, B, C), (A, B, C, D), (A, B, C, D, E), Duration, HashMap, -//! BTreeMap, Result, Cow<'_, T>, Decimal, regex::Regex, uuid::Uuid, +//! BTreeMap, Result, Cow<'_, T>, Decimal, regex::Regex, uuid::Uuid, chrono::Duration, //! chrono::DateTime, geo::Point, geo::LineString geo::Polygon, geo::MultiPoint, //! geo::MultiLineString, and geo::MultiPolygon.