willow-data-model 0.4.1

The core datatypes of Willow, an eventually consistent data store with improved distributed deletion.
Documentation
use core::fmt::Debug;
use core::hash::Hash;
use core::{cmp::Ordering, ops::RangeBounds};

#[cfg(feature = "dev")]
use arbitrary::Arbitrary;

use order_theory::{
    GreatestElement, LeastElement, LowerSemilattice, PredecessorExceptForLeast,
    SuccessorExceptForGreatest, UpperSemilattice,
};

use crate::prelude::*;

/// An arbitrary box in three-dimensional willow space, consisting of a [`SubspaceRange`](super::SubspaceRange), a [`PathRange`](super::PathRange), and a [`TimeRange`](super::TimeRange).
///
/// As an application developer, you probably do not need to interact with 3d ranges, you should prefer [`Areas`](super::Area) — the latter work well with encrypted data, whereas 3d ranges do not define human-meaningful subsets of data when working with encryption.
///
/// ```
/// use willow_data_model::prelude::*;
///
/// let r = Range3d::<2, 2, 2, u8>::new(5..=8, .., ..Timestamp::from(17));
///
/// assert!(r.wdm_includes(&(6, Path::new(), Timestamp::from(9))));
/// assert_eq!(r.subspaces(), &WillowRange::from(5u8..9));
///
/// let r2 = Range3d::<2, 2, 2, u8>::new(7..8, .., Timestamp::from(15)..);
/// assert_eq!(
///     r.wdm_intersection(&r2),
///     Range3d::<2, 2, 2, u8>::new(7..8, .., Timestamp::from(15)..Timestamp::from(17)),
/// );
/// ```
///
/// [Specification](https://willowprotocol.org/specs/grouping-entries/index.html#D3Range)
#[derive(Clone, Debug)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct Range3d<const MCL: usize, const MCC: usize, const MPL: usize, S> {
    subspaces: SubspaceRange<S>,
    paths: PathRange<MCL, MCC, MPL>,
    times: TimeRange,
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Range3d<MCL, MCC, MPL, S> {
    /// Creates a new `Range3d` from its constituent [`SubspaceRange`](super::SubspaceRange), [`PathRange`](super::PathRange), and [`TimeRange`](super::TimeRange).
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let r = Range3d::<2, 2, 2, u8>::new(5..=8, .., ..Timestamp::from(17));
    ///
    /// assert!(r.wdm_includes(&(6, Path::new(), Timestamp::from(9))));
    /// assert_eq!(r.subspaces(), &WillowRange::from(5u8..9));
    /// ```
    pub fn new<SR, PR, TR>(subspaces: SR, paths: PR, times: TR) -> Self
    where
        SR: Into<SubspaceRange<S>>,
        PR: Into<PathRange<MCL, MCC, MPL>>,
        TR: Into<TimeRange>,
    {
        Self {
            subspaces: subspaces.into(),
            paths: paths.into(),
            times: times.into(),
        }
    }

    /// Returns a reference to the inner [`SubspaceRange`](super::SubspaceRange).
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let r = Range3d::<2, 2, 2, u8>::new(5..=8, .., ..Timestamp::from(17));
    /// assert_eq!(r.subspaces(), &SubspaceRange::from(5u8..9));
    /// ```
    ///
    /// [Definition](https://willowprotocol.org/specs/grouping-entries/index.html#D3RangeSubspace).
    pub fn subspaces(&self) -> &SubspaceRange<S> {
        &self.subspaces
    }

    /// Returns a reference to the inner [`PathRange`](super::PathRange).
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let r = Range3d::<2, 2, 2, u8>::new(5..=8, .., ..Timestamp::from(17));
    /// assert_eq!(r.paths(), &WillowRange::full());
    /// ```
    ///
    /// [Definition](https://willowprotocol.org/specs/grouping-entries/index.html#D3RangePath).
    pub fn paths(&self) -> &PathRange<MCL, MCC, MPL> {
        &self.paths
    }

    /// Returns a reference to the inner [`TimeRange`](super::TimeRange).
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let r = Range3d::<2, 2, 2, u8>::new(5..=8, .., ..Timestamp::from(17));
    /// assert_eq!(r.times(), &WillowRange::from(..Timestamp::from(17)));
    /// ```
    ///
    /// [Definition](https://willowprotocol.org/specs/grouping-entries/index.html#D3RangeTime).
    pub fn times(&self) -> &TimeRange {
        &self.times
    }

    /// Sets the inner [`SubspaceRange`](super::SubspaceRange).
    pub fn set_subspaces<SR>(&mut self, new_range: SR)
    where
        SR: Into<SubspaceRange<S>>,
    {
        self.subspaces = new_range.into();
    }

    /// Sets the inner [`PathRange`](super::PathRange).
    pub fn set_paths<PR>(&mut self, new_range: PR)
    where
        PR: Into<PathRange<MCL, MCC, MPL>>,
    {
        self.paths = new_range.into();
    }

    /// Sets the inner [`TimeRange`](super::TimeRange).
    pub fn set_times<TR>(&mut self, new_range: TR)
    where
        TR: Into<TimeRange>,
    {
        self.times = new_range.into();
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Grouping<MCL, MCC, MPL, S>
    for Range3d<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn wdm_includes<Coord>(&self, coord: &Coord) -> bool
    where
        Coord: Coordinatelike<MCL, MCC, MPL, S> + ?Sized,
    {
        self.times().contains(&coord.wdm_timestamp())
            && self.subspaces().contains(coord.wdm_subspace_id())
            && self.paths().contains(coord.wdm_path())
    }
}

/// A 3d-range is equal to another iff both include exactly the same values.
impl<const MCL: usize, const MCC: usize, const MPL: usize, S> PartialEq<Self>
    for Range3d<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn eq(&self, other: &Self) -> bool {
        match (self.wdm_is_empty(), other.wdm_is_empty()) {
            (true, true) => true,
            (true, false) | (false, true) => false,
            (false, false) => {
                self.subspaces == other.subspaces
                    && self.paths == other.paths
                    && self.times == other.times
            }
        }
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Eq for Range3d<MCL, MCC, MPL, S> where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest
{
}

/// A 3d-range is less than another iff all values included in the first are also included in the other.
impl<const MCL: usize, const MCC: usize, const MPL: usize, S> PartialOrd<Self>
    for Range3d<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    /// A 3d-range is less than another iff all values included in the first are also included in the other.
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (self.wdm_is_empty(), other.wdm_is_empty()) {
            (true, true) => Some(Ordering::Equal),
            (true, false) => Some(Ordering::Less),
            (false, true) => Some(Ordering::Greater),
            (false, false) => {
                match (
                    self.subspaces().partial_cmp(other.subspaces())?,
                    self.paths().partial_cmp(other.paths())?,
                    self.times().partial_cmp(other.times())?,
                ) {
                    (Ordering::Equal, Ordering::Equal, Ordering::Equal) => Some(Ordering::Equal),
                    (subspaces_cmp, paths_cmp, times_cmp) => {
                        if subspaces_cmp.is_le() && paths_cmp.is_le() && times_cmp.is_le() {
                            Some(Ordering::Less)
                        } else if subspaces_cmp.is_ge() && paths_cmp.is_ge() && times_cmp.is_ge() {
                            Some(Ordering::Greater)
                        } else {
                            None
                        }
                    }
                }
            }
        }
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> LeastElement
    for Range3d<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn least() -> Self {
        Self::new(
            SubspaceRange::least(),
            PathRange::least(),
            TimeRange::least(),
        )
    }

    fn is_least(&self) -> bool {
        self.subspaces().is_empty() || self.paths().is_empty() || self.times().is_empty()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> GreatestElement
    for Range3d<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn greatest() -> Self {
        Self::new(SubspaceRange::full(), PathRange::full(), TimeRange::full())
    }

    fn is_greatest(&self) -> bool {
        self.times().is_full() && self.subspaces().is_full() && self.paths().is_full()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> LowerSemilattice
    for Range3d<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn greatest_lower_bound(&self, other: &Self) -> Self {
        Self::new(
            self.subspaces().greatest_lower_bound(other.subspaces()),
            self.paths().greatest_lower_bound(other.paths()),
            self.times().greatest_lower_bound(other.times()),
        )
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> UpperSemilattice
    for Range3d<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn least_upper_bound(&self, other: &Self) -> Self {
        if self.wdm_is_empty() {
            other.clone()
        } else if other.wdm_is_empty() {
            self.clone()
        } else {
            Self::new(
                self.subspaces().least_upper_bound(other.subspaces()),
                self.paths().least_upper_bound(other.paths()),
                self.times().least_upper_bound(other.times()),
            )
        }
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> From<Area<MCL, MCC, MPL, S>>
    for Range3d<MCL, MCC, MPL, S>
where
    S: Clone + SuccessorExceptForGreatest + LeastElement,
{
    fn from(value: Area<MCL, MCC, MPL, S>) -> Self {
        let subspaces = match value.subspace() {
            None => WillowRange::full(),
            Some(s) => WillowRange::singleton(s.clone()),
        };

        let paths = match value.path().greater_but_not_prefixed() {
            Some(succ) => WillowRange::from(value.path().clone()..succ),
            None => WillowRange::from(value.path().clone()..),
        };

        Self::new(subspaces, paths, value.times().clone())
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Range3d<MCL, MCC, MPL, S>
where
    S: Clone + SuccessorExceptForGreatest,
{
    /// Returns the [`Range3d`] which [includes](Grouping::wdm_includes) `coord` but no other value.
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let r = Range3d::<2, 2, 2, u8>::singleton(&(6, Path::new(), Timestamp::from(9)));
    ///
    /// assert!(r.wdm_includes(&(6, Path::new(), Timestamp::from(9))));
    /// assert!(!r.wdm_includes(&(16, Path::new(), Timestamp::from(9))));
    /// ```
    pub fn singleton<Coord>(coord: &Coord) -> Self
    where
        Coord: Coordinatelike<MCL, MCC, MPL, S>,
    {
        Self::new(
            SubspaceRange::singleton(coord.wdm_subspace_id().clone()),
            PathRange::singleton(coord.wdm_path().clone()),
            TimeRange::singleton(coord.wdm_timestamp()),
        )
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Range3d<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    /// Returns the `Range3d` which [includes](Grouping::wdm_includes) every [coordinate](Coordinatelike).
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let r = Range3d::<2, 2, 2, u8>::full();
    ///
    /// assert!(r.wdm_includes(&(6, Path::new(), Timestamp::from(9))));
    /// assert!(r.wdm_includes(&(16, Path::new(), Timestamp::from(9))));
    /// ```
    pub fn full() -> Self {
        Self::greatest()
    }

    /// Returns whether `self` is the full 3d range, i.e., the 3d range which [includes](Grouping::wdm_includes) every [coordinate](Coordinatelike).
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// assert!(Range3d::<2, 2, 2, u8>::full().is_full());
    /// assert!(!Range3d::<2, 2, 2, u8>::new(5..=8, .., ..Timestamp::from(17)).is_full());
    /// ```
    pub fn is_full(&self) -> bool {
        self.is_greatest()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Hash for Range3d<MCL, MCC, MPL, S>
where
    S: Hash + Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        if self.is_least() {
            255u8.hash(state);
        } else if self.is_greatest() {
            254u8.hash(state);
        } else {
            253u8.hash(state);
            self.subspaces.hash(state);
            self.paths.hash(state);
            self.times.hash(state);
        }
    }
}