rust_intervals 1.0.0

Intervals arithmetic with any combination of open, closed or infinite bounds, along with operations like intersection, convex hull, union, difference,...
Documentation
use crate::bounds::Bound;
use crate::nothing_between::NothingBetween;
use crate::step::Bounded;
use ::core::cmp::PartialOrd;
use serde::{de::DeserializeOwned, Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
#[serde(rename = "Interval")]
enum SerdeInterval<T> {
    ClosedClosed(T, T),
    ClosedOpen(T, T),
    ClosedUnbounded(T),
    DoublyUnbounded,
    Empty,
    OpenClosed(T, T),
    OpenOpen(T, T),
    OpenUnbounded(T),
    UnboundedClosed(T),
    UnboundedOpen(T),
}

impl<T> Serialize for crate::intervals::Interval<T>
where
    T: PartialOrd + NothingBetween + Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        if self.is_empty() {
            SerdeInterval::<T>::Empty.serialize(serializer)
        } else {
            let intv = match (&self.lower, &self.upper) {
                (Bound::LeftOf(lo), Bound::LeftOf(up)) => {
                    SerdeInterval::ClosedOpen(lo, up)
                }
                (Bound::LeftOf(lo), Bound::RightOf(up)) => {
                    SerdeInterval::ClosedClosed(lo, up)
                }
                (Bound::LeftOf(lo), Bound::RightUnbounded) => {
                    SerdeInterval::ClosedUnbounded(lo)
                }
                (Bound::LeftUnbounded, Bound::RightUnbounded) => {
                    SerdeInterval::DoublyUnbounded
                }
                (Bound::LeftUnbounded, Bound::LeftOf(up)) => {
                    SerdeInterval::UnboundedOpen(up)
                }
                (Bound::LeftUnbounded, Bound::RightOf(up)) => {
                    SerdeInterval::UnboundedClosed(up)
                }
                (Bound::RightOf(lo), Bound::LeftOf(up)) => {
                    SerdeInterval::OpenOpen(lo, up)
                }
                (Bound::RightOf(lo), Bound::RightOf(up)) => {
                    SerdeInterval::OpenClosed(lo, up)
                }
                (Bound::RightOf(lo), Bound::RightUnbounded) => {
                    SerdeInterval::OpenUnbounded(lo)
                }
                (_, Bound::LeftUnbounded) | (Bound::RightUnbounded, _) => {
                    // Cannot be reached, we have tested for is_empty() earlier
                    SerdeInterval::Empty
                }
            };
            intv.serialize(serializer)
        }
    }
}

impl<'de, T> Deserialize<'de> for crate::intervals::Interval<T>
where
    T: PartialOrd + NothingBetween + DeserializeOwned + Bounded,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
        Ok(match SerdeInterval::<T>::deserialize(deserializer)? {
            SerdeInterval::Empty => crate::intervals::Interval::empty(),
            SerdeInterval::ClosedOpen(lo, up) => {
                crate::intervals::Interval::new_closed_open(lo, up)
            }
            SerdeInterval::ClosedClosed(lo, up) => {
                crate::intervals::Interval::new_closed_closed(lo, up)
            }
            SerdeInterval::OpenClosed(lo, up) => {
                crate::intervals::Interval::new_open_closed(lo, up)
            }
            SerdeInterval::OpenOpen(lo, up) => {
                crate::intervals::Interval::new_open_open(lo, up)
            }
            SerdeInterval::OpenUnbounded(lo) => {
                crate::intervals::Interval::new_open_unbounded(lo)
            }
            SerdeInterval::ClosedUnbounded(lo) => {
                crate::intervals::Interval::new_closed_unbounded(lo)
            }
            SerdeInterval::UnboundedOpen(up) => {
                crate::intervals::Interval::new_unbounded_open(up)
            }
            SerdeInterval::UnboundedClosed(up) => {
                crate::intervals::Interval::new_unbounded_closed(up)
            }
            SerdeInterval::DoublyUnbounded => {
                crate::intervals::Interval::doubly_unbounded()
            }
        })
    }
}

#[cfg(test)]
mod test {
    use crate::nothing_between::NothingBetween;
    use crate::*;
    use ::core::cmp::PartialOrd;
    use ::core::fmt::Debug;
    use ::serde::{de::DeserializeOwned, Serialize};

    fn roundtrip<
        T: PartialOrd
            + NothingBetween
            + Serialize
            + DeserializeOwned
            + Debug
            + Bounded,
    >(
        intv: Interval<T>,
        json_str: &str,
        ron_str: &str,
    ) {
        assert_eq!(serde_json::to_string(&intv).unwrap(), json_str,);
        assert_eq!(ron::to_string(&intv).unwrap(), ron_str);
        assert_eq!(
            serde_json::from_str::<Interval<T>>(json_str).unwrap(),
            intv,
        );
        assert_eq!(ron::from_str::<Interval<T>>(ron_str).unwrap(), intv,);
    }

    #[test]
    fn test_serde() {
        roundtrip(interval!(1, 2), "{\"ClosedOpen\":[1,2]}", "ClosedOpen(1,2)");
        roundtrip(
            interval!(1, 2, "[]"),
            "{\"ClosedClosed\":[1,2]}",
            "ClosedClosed(1,2)",
        );
        roundtrip(
            interval!(1, 2, "[)"),
            "{\"ClosedOpen\":[1,2]}",
            "ClosedOpen(1,2)",
        );
        roundtrip(
            interval!(1, 3, "()"),
            "{\"OpenOpen\":[1,3]}",
            "OpenOpen(1,3)",
        );
        roundtrip(
            interval!(1, 2, "(]"),
            "{\"OpenClosed\":[1,2]}",
            "OpenClosed(1,2)",
        );
        roundtrip(
            interval!(1, "[inf"),
            "{\"ClosedUnbounded\":1}",
            "ClosedUnbounded(1)",
        );
        roundtrip(
            interval!(1, "(inf"),
            "{\"OpenUnbounded\":1}",
            "OpenUnbounded(1)",
        );
        roundtrip(
            interval!("-inf", 1.0, "]"),
            "{\"UnboundedClosed\":1.0}",
            "UnboundedClosed(1.0)",
        );
        roundtrip(
            interval!("-inf", 1.0, ")"),
            "{\"UnboundedOpen\":1.0}",
            "UnboundedOpen(1.0)",
        );
        roundtrip(Interval::<f32>::empty(), "\"Empty\"", "Empty");
        roundtrip(
            Interval::<f32>::doubly_unbounded(),
            "\"DoublyUnbounded\"",
            "DoublyUnbounded",
        );
    }
}