willow-data-model 0.4.1

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;

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

use crate::prelude::*;

/// An [`AreaOfInterest`](https://willowprotocol.org/specs/grouping-entries/#Area) is an [`Area`] together with a maximum number of entries it may include, and a maximum sum of total [payload_lengths](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) for the included entries.
///
/// A [`max_count`](https://willowprotocol.org/specs/grouping-entries/#aoi_count) of [`u64::MAX`] indicates that there is no limit on the number of entries. A [`max_size`](https://willowprotocol.org/specs/grouping-entries/#aoi_size) of [`u64::MAX`] indicates that there is no limit on the total [payload_lengths](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) of the entries.
///
/// ```
/// use willow_data_model::prelude::*;
///
/// let aoi = AreaOfInterest::new(
///     Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
///     5,
///     400,
/// );
///
/// assert_eq!(aoi.area(), &Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)));
/// assert_eq!(aoi.max_count(), 5);
/// assert_eq!(aoi.max_size(), 400);
/// ```
///
/// [Specification](https://willowprotocol.org/specs/grouping-entries/#aois)
#[derive(Clone, Debug)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct AreaOfInterest<const MCL: usize, const MCC: usize, const MPL: usize, S> {
    area: Area<MCL, MCC, MPL, S>,
    max_count: u64,
    max_size: u64,
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> AreaOfInterest<MCL, MCC, MPL, S> {
    /// Creates a new [`AreaOfInterest`] from its constituent [`Area`], [max_count](https://willowprotocol.org/specs/grouping-entries/#aoi_count), and [max_size](https://willowprotocol.org/specs/grouping-entries/#aoi_size).
    ///
    /// A [`max_count`](https://willowprotocol.org/specs/grouping-entries/#aoi_count) of [`u64::MAX`] indicates that there is no limit on the number of entries. A [`max_size`](https://willowprotocol.org/specs/grouping-entries/#aoi_size) of [`u64::MAX`] indicates that there is no limit on the total [payload_lengths](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) of the entries.
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let aoi = AreaOfInterest::new(
    ///     Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
    ///     5,
    ///     400,
    /// );
    ///
    /// assert_eq!(
    ///     aoi.area(),
    ///     &Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
    /// );
    /// assert_eq!(aoi.max_count(), 5);
    /// assert_eq!(aoi.max_size(), 400);
    /// ```
    pub fn new(area: Area<MCL, MCC, MPL, S>, max_count: u64, max_size: u64) -> Self {
        Self {
            area,
            max_count,
            max_size,
        }
    }

    /// Returns a reference to the inner [`Area`].
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let aoi = AreaOfInterest::new(
    ///     Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
    ///     5,
    ///     400,
    /// );
    /// assert_eq!(aoi.area(), &Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)));
    /// ```
    ///
    /// [Definition](https://willowprotocol.org/specs/grouping-entries/#aoi_area).
    pub fn area(&self) -> &Area<MCL, MCC, MPL, S> {
        &self.area
    }

    /// Returns the [max_count](https://willowprotocol.org/specs/grouping-entries/#aoi_count) of entries.
    ///
    /// A [`max_count`](https://willowprotocol.org/specs/grouping-entries/#aoi_count) of [`u64::MAX`] indicates that there is no limit on the number of entries in this area of interest.
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let aoi = AreaOfInterest::new(
    ///     Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
    ///     5,
    ///     400,
    /// );
    /// assert_eq!(aoi.max_count(), 5);
    /// ```
    ///
    /// [Definition](https://willowprotocol.org/specs/grouping-entries/#AreaPath).
    pub fn max_count(&self) -> u64 {
        self.max_count
    }

    /// Returns the [max_count](https://willowprotocol.org/specs/grouping-entries/#aoi_count) of entries.
    ///
    /// A [`max_size`](https://willowprotocol.org/specs/grouping-entries/#aoi_size) of [`u64::MAX`] indicates that there is no limit on the total [payload_lengths](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) of the entries in this area of interest.
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let aoi = AreaOfInterest::new(
    ///     Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
    ///     5,
    ///     400,
    /// );
    /// assert_eq!(aoi.max_size(), 400);
    /// ```
    ///
    /// [Definition](https://willowprotocol.org/specs/grouping-entries/#AreaPath).
    pub fn max_size(&self) -> u64 {
        self.max_size
    }

    /// Sets the inner area.
    pub fn set_area(&mut self, new_area: Area<MCL, MCC, MPL, S>) {
        self.area = new_area;
    }

    /// Sets the [`max_count`](https://willowprotocol.org/specs/grouping-entries/#aoi_count).
    ///
    /// A [`max_count`](https://willowprotocol.org/specs/grouping-entries/#aoi_count) of [`u64::MAX`] indicates that there is no limit on the number of entries in this area of interest.
    pub fn set_max_count(&mut self, new_max_count: u64) {
        self.max_count = new_max_count;
    }

    /// Sets the [`max_size`](https://willowprotocol.org/specs/grouping-entries/#aoi_size).
    ///
    /// A [`max_size`](https://willowprotocol.org/specs/grouping-entries/#aoi_size) of [`u64::MAX`] indicates that there is no limit on the total [payload_lengths](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) of the entries in this area of interest.
    pub fn set_max_size(&mut self, new_max_size: u64) {
        self.max_size = new_max_size;
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> AreaOfInterest<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    /// Returns `true` iff this area of interest cannot possibly contain any entries.
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// assert!(
    ///     !AreaOfInterest::new(
    ///         Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
    ///         5,
    ///         400,
    ///     ).is_empty(),
    /// );
    /// assert!(
    ///     !AreaOfInterest::new(
    ///         Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
    ///         5,
    ///         0, // Could still possibly contain entries with a payload_length of zero.
    ///     ).is_empty(),
    /// );
    ///
    /// assert!(
    ///     AreaOfInterest::new(
    ///         Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
    ///         0,
    ///         400,
    ///     ).is_empty(),
    /// );
    /// assert!(
    ///     AreaOfInterest::new(
    ///         Area::<2, 2, 2, u8>::wdm_empty(),
    ///         5,
    ///         400,
    ///     ).is_empty(),
    /// );
    /// ```
    pub fn is_empty(&self) -> bool {
        self.is_least()
    }

    /// Returns the [intersection](https://willowprotocol.org/specs/grouping-entries/#aoi_intersection) of `self` and `other`.
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// let aoi1 = AreaOfInterest::new(
    ///     Area::<2, 2, 2, u8>::new(None, Path::new(), ..Timestamp::from(17)),
    ///     5,
    ///     400,
    /// );
    /// let aoi2 = AreaOfInterest::new(
    ///     Area::<2, 2, 2, u8>::new(None, Path::new(), Timestamp::from(14)..),
    ///     12,
    ///     32,
    /// );
    ///
    /// assert_eq!(
    ///     aoi1.intersection(&aoi2),
    ///     AreaOfInterest::new(
    ///         Area::<2, 2, 2, u8>::new(
    ///             None,
    ///             Path::new(),
    ///             Timestamp::from(14)..Timestamp::from(17),
    ///         ),
    ///         5,
    ///         32,
    ///     ),
    /// );
    /// ```
    pub fn intersection(&self, other: &Self) -> Self {
        self.greatest_lower_bound(other)
    }
}

/// An area is equal to another iff both are empty, or both have equal constituent values.
///
/// This implementation assumes that `S` is inhabited by more than one value.
impl<const MCL: usize, const MCC: usize, const MPL: usize, S> PartialEq<Self>
    for AreaOfInterest<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn eq(&self, other: &Self) -> bool {
        match (self.is_least(), other.is_least()) {
            (true, true) => true,
            (true, false) | (false, true) => false,
            (false, false) => {
                self.area() == other.area()
                    && self.max_count() == other.max_count()
                    && self.max_size() == other.max_size()
            }
        }
    }
}

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

/// An area is less than another iff is is empty but the other is not, or if its area is less than the other's area and its [`max_count`](https://willowprotocol.org/specs/grouping-entries/#aoi_count) and [`max_size`](https://willowprotocol.org/specs/grouping-entries/#aoi_size) are not strictly greater than those of the other.
///
/// This implementation assumes that `S` is inhabited by more than one value.
impl<const MCL: usize, const MCC: usize, const MPL: usize, S> PartialOrd<Self>
    for AreaOfInterest<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    /// An area is less than another iff it has a [`max_count`](https://willowprotocol.org/specs/grouping-entries/#aoi_count) of zero (and the other doesn't), or if its area is less than the other's area and its [`max_count`](https://willowprotocol.org/specs/grouping-entries/#aoi_count) and [`max_size`](https://willowprotocol.org/specs/grouping-entries/#aoi_size) are not strictly greater than those of the other.
    ///
    /// This implementation assumes that `S` is inhabited by more than one value.
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (self.is_least(), other.is_least()) {
            (true, true) => Some(Ordering::Equal),
            (true, false) => Some(Ordering::Less),
            (false, true) => Some(Ordering::Greater),
            (false, false) => match (
                self.area().partial_cmp(other.area())?,
                self.max_count().cmp(&other.max_count()),
                self.max_size().cmp(&other.max_size()),
            ) {
                (Ordering::Equal, Ordering::Equal, Ordering::Equal) => Some(Ordering::Equal),
                (area_cmp, count_cmp, size_cmp) => {
                    if area_cmp.is_le() && count_cmp.is_le() && size_cmp.is_le() {
                        Some(Ordering::Less)
                    } else if area_cmp.is_ge() && count_cmp.is_ge() && size_cmp.is_ge() {
                        Some(Ordering::Greater)
                    } else {
                        None
                    }
                }
            },
        }
    }
}

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

    fn is_least(&self) -> bool {
        self.max_count() == 0 || self.area().is_least()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> GreatestElement
    for AreaOfInterest<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn greatest() -> Self {
        Self::new(Area::greatest(), u64::MAX, u64::MAX)
    }

    fn is_greatest(&self) -> bool {
        self.area().is_greatest() && self.max_count() == u64::MAX && self.max_size() == u64::MAX
    }
}

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

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> UpperSemilattice
    for AreaOfInterest<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn least_upper_bound(&self, other: &Self) -> Self {
        if self.is_least() {
            other.clone()
        } else if other.is_least() {
            self.clone()
        } else {
            Self::new(
                self.area().least_upper_bound(other.area()),
                self.max_count().least_upper_bound(&other.max_count()),
                self.max_size().least_upper_bound(&other.max_size()),
            )
        }
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Hash
    for AreaOfInterest<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.area.hash(state);
            self.max_count.hash(state);
            self.max_size.hash(state);
        }
    }
}