diff --git a/Cargo.toml b/Cargo.toml index addb91b..a792590 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "intervallum" -version = "1.4.2" +version = "1.4.3" authors = [ "Pierre Talbot " ] description = "Generic interval and interval set library." -documentation = "https://docs.rs/intervallum/1.4.2/interval/" +documentation = "https://docs.rs/intervallum/1.4.3/interval/" repository = "https://github.com/ptal/intervallum" readme = "README.md" keywords = ["interval", "math", "data-structure", "containers", "interval-set"] @@ -24,3 +24,7 @@ num-traits = "0.2.14" bit-set = "0.5.0" gcollections = "1.5.0" trilean = "1.1.0" +serde = "1.0.219" + +[dev-dependencies] +serde_test = "1.0.177" diff --git a/src/libinterval/interval.rs b/src/libinterval/interval.rs index e07ba3a..a739a11 100644 --- a/src/libinterval/interval.rs +++ b/src/libinterval/interval.rs @@ -37,11 +37,15 @@ use crate::ops::*; use gcollections::ops::*; use gcollections::*; +use serde::de::{self, SeqAccess, Visitor}; +use serde::ser::SerializeTuple; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use trilean::SKleene; use num_traits::{Num, Zero}; use std::cmp::{max, min}; -use std::fmt::{Display, Error, Formatter}; +use std::fmt::{self, Display, Error, Formatter}; +use std::marker::PhantomData; use std::ops::{Add, Mul, Sub}; /// Closed interval (endpoints included). @@ -51,6 +55,84 @@ pub struct Interval { ub: Bound, } +impl Serialize for Interval +where + Bound: Serialize + Width + Num, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.is_empty() { + serializer.serialize_none() + } else { + let mut tuple = serializer.serialize_tuple(2)?; + tuple.serialize_element(&self.lb)?; + tuple.serialize_element(&self.ub)?; + tuple.end() + } + } +} + +impl<'de, Bound> Deserialize<'de> for Interval +where + Bound: Width + Num + Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct IntervalVisitor { + marker: PhantomData Bound>, + } + impl IntervalVisitor { + fn new() -> Self { + IntervalVisitor { + marker: PhantomData, + } + } + } + impl<'de, Bound> Visitor<'de> for IntervalVisitor + where + Bound: Width + Deserialize<'de> + Num, + { + type Value = Interval; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("tuple of two numbers or none") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let lower = seq + .next_element::()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let upper = seq + .next_element::()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + let mut extra_elements = 0; + while seq.next_element::()?.is_some() { + extra_elements += 1; + } + if extra_elements > 0 { + return Err(de::Error::invalid_length(2 + extra_elements, &self)); + } + Ok(Interval::new(lower, upper)) + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(Interval::::empty()) + } + } + deserializer.deserialize_any(IntervalVisitor::new()) + } +} + impl IntervalKind for Interval {} impl Collection for Interval { @@ -1371,6 +1453,8 @@ where #[allow(non_upper_case_globals)] #[cfg(test)] mod tests { + use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token}; + use super::*; const empty: Interval = Interval { lb: 1, ub: 0 }; @@ -2334,4 +2418,96 @@ mod tests { ); tester.test_all(); } + + #[test] + fn test_ser_de_interval() { + let interval = Interval::new(10, 20); + assert_tokens( + &interval, + &[ + Token::Tuple { len: 2 }, + Token::I32(10), + Token::I32(20), + Token::TupleEnd, + ], + ); + } + + #[test] + fn test_de_interval_mixed_types() { + let interval = Interval::new(-5, 15); + assert_de_tokens::>( + &interval, + &[ + Token::Tuple { len: 2 }, + Token::I32(-5), + Token::I64(15), + Token::TupleEnd, + ], + ); + } + + #[test] + fn test_de_interval_extra_token() { + assert_de_tokens_error::>( + &[ + Token::Tuple { len: 3 }, + Token::I32(10), + Token::I32(20), + Token::I32(30), + Token::TupleEnd, + ], + "invalid length 3, expected tuple of two numbers or none", + ); + } + + #[test] + fn test_de_interval_extra_tokens() { + assert_de_tokens_error::>( + &[ + Token::Tuple { len: 5 }, + Token::I32(10), + Token::I32(20), + Token::I32(30), + Token::I32(40), + Token::I32(50), + Token::TupleEnd, + ], + "invalid length 5, expected tuple of two numbers or none", + ); + } + + #[test] + fn test_ser_de_interval_u8() { + let interval = Interval::::new(10, 20); + assert_tokens( + &interval, + &[ + Token::Tuple { len: 2 }, + Token::U8(10), + Token::U8(20), + Token::TupleEnd, + ], + ); + } + + #[test] + fn test_ser_de_interval_i64() { + let interval = Interval::::whole(); + assert_tokens( + &interval, + &[ + Token::Tuple { len: 2 }, + Token::I64(::min_value()), + Token::I64(::max_value()), + Token::TupleEnd, + ], + ); + } + + #[test] + fn test_ser_de_empty_interval() { + let interval = Interval::::empty(); + assert_tokens(&interval, &[Token::None]); + } } diff --git a/src/libinterval/interval_set.rs b/src/libinterval/interval_set.rs index f382c2e..6a3016f 100644 --- a/src/libinterval/interval_set.rs +++ b/src/libinterval/interval_set.rs @@ -33,8 +33,14 @@ use crate::interval::ToInterval; use crate::ops::*; use gcollections::ops::*; use gcollections::*; +use serde::de::SeqAccess; +use serde::de::Visitor; +use serde::Deserialize; +use serde::Serialize; +use std::fmt; use std::fmt::{Display, Error, Formatter}; use std::iter::{IntoIterator, Peekable}; +use std::marker::PhantomData; use std::ops::{Add, Mul, Sub}; use trilean::SKleene; @@ -46,6 +52,78 @@ pub struct IntervalSet { size: Bound::Output, } +impl Serialize for IntervalSet +where + Bound: Width + Num + Serialize, + Interval: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if self.is_empty() { + serializer.serialize_none() + } else { + serializer.collect_seq(&self.intervals) + } + } +} + +impl<'de, Bound> Deserialize<'de> for IntervalSet +where + Bound: Width + Num + Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct IntervalSetVisitor { + marker: PhantomData Interval>, + } + impl IntervalSetVisitor { + fn new() -> Self { + IntervalSetVisitor { + marker: PhantomData, + } + } + } + impl<'de, Bound> Visitor<'de> for IntervalSetVisitor + where + Bound: Width + Deserialize<'de> + Num, + { + type Value = IntervalSet; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("sequence of intervals") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut intervals = Vec::new(); + if let Some(size) = seq.size_hint() { + intervals.reserve(size); + } + while let Some(interval) = seq.next_element::>()? { + intervals.push(interval); + } + let mut interval_set = IntervalSet::empty(); + interval_set.extend(intervals); + Ok(interval_set) + } + + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(IntervalSet::empty()) + } + } + deserializer.deserialize_any(IntervalSetVisitor::new()) + } +} + impl IntervalKind for IntervalSet {} impl Collection for IntervalSet { @@ -1357,6 +1435,8 @@ where #[allow(non_upper_case_globals)] #[cfg(test)] mod tests { + use serde_test::{assert_tokens, Token}; + use super::*; const extend_example: [(i32, i32); 2] = [(11, 33), (-55, -44)]; @@ -2749,4 +2829,47 @@ mod tests { ); } } + + #[test] + fn test_ser_de_single_interval_set() { + assert_tokens( + &IntervalSet::from_interval(Interval::new(8, 12)), + &[ + Token::Seq { len: Some(1) }, + Token::Tuple { len: 2 }, + Token::I32(8), + Token::I32(12), + Token::TupleEnd, + Token::SeqEnd, + ], + ); + } + + #[test] + fn test_ser_de_multiple_interval_set() { + assert_tokens( + &[(20, 21), (3, 5), (-10, -5)].to_interval_set(), + &[ + Token::Seq { len: Some(3) }, + Token::Tuple { len: 2 }, + Token::I32(-10), + Token::I32(-5), + Token::TupleEnd, + Token::Tuple { len: 2 }, + Token::I32(3), + Token::I32(5), + Token::TupleEnd, + Token::Tuple { len: 2 }, + Token::I32(20), + Token::I32(21), + Token::TupleEnd, + Token::SeqEnd, + ], + ); + } + + #[test] + fn test_ser_de_empty_interval_set() { + assert_tokens(&IntervalSet::::empty(), &[Token::None]); + } }