willow-data-model 0.7.0

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

#[cfg(feature = "dev")]
use arbitrary::{Arbitrary, size_hint};

use order_theory::{GreatestElement, LeastElement, SuccessorExceptForGreatest};

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::*;
///
/// # #[cfg(feature = "dev")] {
/// let r = Range3d::<4, 4, 4, TestSubspace>::new(
///     SubspaceRange::new_open(Betty),
///     PathRange::full(),
///     TimeRange::new_closed(0.into(), 17.into())
/// );
///
/// assert!(r.wdm_includes(&(Gemma, Path::new(), Timestamp::from(9))));
/// assert_eq!(r.subspaces(), &SubspaceRange::new_open(Betty));
///
/// let r2 = Range3d::<4, 4, 4, TestSubspace>::new(
///     SubspaceRange::new_closed(Alfie, Dalton),
///     PathRange::full(),
///     TimeRange::new_open(15.into()),
/// );
/// assert_eq!(
///     r.wdm_intersection(&r2),
///     Ok(Range3d::<4, 4, 4, TestSubspace>::new(
///         SubspaceRange::new_closed(Betty, Dalton),
///         PathRange::full(),
///         TimeRange::new_closed(15.into(), 17.into()),
///     )),
/// );
/// # }
/// ```
///
/// [Specification](https://willowprotocol.org/specs/grouping-entries/index.html#D3Range)
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Range3d<const MCL: usize, const MCC: usize, const MPL: usize, S> {
    subspaces: SubspaceRange<S>,
    paths: PathRange<MCL, MCC, MPL>,
    times: TimeRange,
}

#[cfg(feature = "dev")]
impl<'a, const MCL: usize, const MCC: usize, const MPL: usize, S> Arbitrary<'a>
    for Range3d<MCL, MCC, MPL, S>
where
    S: PartialOrd + Arbitrary<'a>,
{
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        let subspaces = SubspaceRange::<S>::arbitrary(u)?;
        let paths = PathRange::<MCL, MCC, MPL>::arbitrary(u)?;
        let times = TimeRange::arbitrary(u)?;

        Ok(Self {
            subspaces,
            paths,
            times,
        })
    }

    fn try_size_hint(
        depth: usize,
    ) -> arbitrary::Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
        Ok(size_hint::and_all(&[
            SubspaceRange::<S>::try_size_hint(depth)?,
            PathRange::<MCL, MCC, MPL>::try_size_hint(depth)?,
            TimeRange::try_size_hint(depth)?,
        ]))
    }
}

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::*;
    ///
    /// # #[cfg(feature = "dev")] {
    /// let r = Range3d::<4, 4, 4, TestSubspace>::new(
    ///     SubspaceRange::new_open(Betty),
    ///     PathRange::full(),
    ///     TimeRange::new_closed(0.into(), 17.into())
    /// );
    ///
    /// assert!(r.wdm_includes(&(Gemma, Path::new(), Timestamp::from(9))));
    /// assert_eq!(r.subspaces(), &SubspaceRange::new_open(Betty));
    /// # }
    /// ```
    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::*;
    ///
    /// # #[cfg(feature = "dev")] {
    /// let r = Range3d::<4, 4, 4, TestSubspace>::new(
    ///     SubspaceRange::new_open(Betty),
    ///     PathRange::full(),
    ///     TimeRange::new_closed(0.into(), 17.into())
    /// );
    /// assert_eq!(r.subspaces(), &SubspaceRange::new_open(Betty));
    /// # }
    /// ```
    ///
    /// [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::*;
    /// # #[cfg(feature = "dev")] {
    /// let r = Range3d::<4, 4, 4, TestSubspace>::new(
    ///     SubspaceRange::new_open(Betty),
    ///     PathRange::full(),
    ///     TimeRange::new_closed(0.into(), 17.into())
    /// );
    /// assert_eq!(r.paths(), &PathRange::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::*;
    ///
    /// # #[cfg(feature = "dev")] {
    /// let r = Range3d::<4, 4, 4, TestSubspace>::new(
    ///     SubspaceRange::new_open(Betty),
    ///     PathRange::full(),
    ///     TimeRange::new_closed(0.into(), 17.into())
    /// );
    /// assert_eq!(r.times(), &TimeRange::new_closed(0.into(), 17.into()));
    /// # }
    /// ```
    ///
    /// [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: PartialOrd,
{
    fn wdm_includes<Coord>(&self, coord: &Coord) -> bool
    where
        Coord: Coordinatelike<MCL, MCC, MPL, S> + ?Sized,
    {
        self.times().includes_value(&coord.wdm_timestamp())
            && self.subspaces().includes_value(coord.wdm_subspace_id())
            && self.paths().includes_value(coord.wdm_path())
    }

    fn wdm_intersection(&self, other: &Self) -> Result<Self, EmptyGrouping>
    where
        S: Clone,
    {
        Ok(Self {
            subspaces: self
                .subspaces()
                .intersection_willow_range(other.subspaces())?,
            paths: self.paths().intersection_willow_range(other.paths())?,
            times: self.times().intersection_willow_range(other.times())?,
        })
    }
}

/// 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: PartialOrd,
{
    /// 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> {
        let cmp_subspaces = self.subspaces().partial_cmp(other.subspaces())?;
        let cmp_paths = self.paths().partial_cmp(other.paths())?;
        let cmp_times = self.times().partial_cmp(other.times())?;

        if cmp_subspaces == Ordering::Equal
            && cmp_paths == Ordering::Equal
            && cmp_times == Ordering::Equal
        {
            Some(Ordering::Equal)
        } else if cmp_subspaces.is_le() && cmp_paths.is_le() && cmp_times.is_le() {
            return Some(Ordering::Less);
        } else if cmp_subspaces.is_ge() && cmp_paths.is_ge() && cmp_times.is_ge() {
            return Some(Ordering::Greater);
        } else {
            return None;
        }
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> GreatestElement
    for Range3d<MCL, MCC, MPL, S>
where
    S: LeastElement,
{
    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> 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::new_closed(value.path().clone(), succ),
            None => WillowRange::new_open(value.path().clone()),
        };

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

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::<4, 4, 4, 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: LeastElement,
{
    /// Returns the `Range3d` which [includes](Grouping::wdm_includes) every [coordinate](Coordinatelike).
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let r = Range3d::<4, 4, 4, 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::*;
    ///
    /// # #[cfg(feature = "dev")] {
    /// assert!(Range3d::<4, 4, 4, TestSubspace>::full().is_full());
    /// assert!(!Range3d::<4, 4, 4, TestSubspace>::new(
    ///     SubspaceRange::new_open(Betty),
    ///     PathRange::full(),
    ///     TimeRange::new_closed(0.into(), 17.into()),
    /// ).is_full());
    /// # }
    /// ```
    pub fn is_full(&self) -> bool {
        self.is_greatest()
    }
}