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;

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

use crate::prelude::*;

pub use core::num::NonZeroU64;

/// 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(), TimeRange::new_closed(0.into(), 17.into())),
///     NonZeroU64::new(5).unwrap(),
///     400,
/// );
///
/// assert_eq!(aoi.area(), &Area::<2, 2, 2, u8>::new(None, Path::new(), TimeRange::new_closed(0.into(), 17.into())));
/// assert_eq!(aoi.max_count(), NonZeroU64::new(5).unwrap());
/// assert_eq!(aoi.max_size(), 400);
/// ```
///
/// [Specification](https://willowprotocol.org/specs/grouping-entries/#aois)
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[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: NonZeroU64,
    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(), TimeRange::new_closed(0.into(), 17.into())),
    ///     NonZeroU64::new(5).unwrap(),
    ///     400,
    /// );
    ///
    /// assert_eq!(
    ///     aoi.area(),
    ///     &Area::<2, 2, 2, u8>::new(None, Path::new(), TimeRange::new_closed(0.into(), 17.into())),
    /// );
    /// assert_eq!(aoi.max_count(), NonZeroU64::new(5).unwrap());
    /// assert_eq!(aoi.max_size(), 400);
    /// ```
    pub fn new(area: Area<MCL, MCC, MPL, S>, max_count: NonZeroU64, 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(), TimeRange::new_closed(0.into(), 17.into())),
    ///     NonZeroU64::new(5).unwrap(),
    ///     400,
    /// );
    /// assert_eq!(aoi.area(), &Area::<2, 2, 2, u8>::new(None, Path::new(), TimeRange::new_closed(0.into(), 17.into())));
    /// ```
    ///
    /// [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(), TimeRange::new_closed(0.into(), 17.into())),
    ///     NonZeroU64::new(5).unwrap(),
    ///     400,
    /// );
    /// assert_eq!(aoi.max_count(), NonZeroU64::new(5).unwrap());
    /// ```
    ///
    /// [Definition](https://willowprotocol.org/specs/grouping-entries/#AreaPath).
    pub fn max_count(&self) -> NonZeroU64 {
        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(), TimeRange::new_closed(0.into(), 17.into())),
    ///     NonZeroU64::new(5).unwrap(),
    ///     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: NonZeroU64) {
        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: PartialEq + Clone,
{
    /// 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(), TimeRange::new_closed(0.into(), 17.into())),
    ///     NonZeroU64::new(5).unwrap(),
    ///     400,
    /// );
    /// let aoi2 = AreaOfInterest::new(
    ///     Area::<2, 2, 2, u8>::new(None, Path::new(), TimeRange::new_open(14.into())),
    ///     NonZeroU64::new(12).unwrap(),
    ///     32,
    /// );
    ///
    /// assert_eq!(
    ///     aoi1.intersection(&aoi2),
    ///     Ok(AreaOfInterest::new(
    ///         Area::<2, 2, 2, u8>::new(
    ///             None,
    ///             Path::new(),
    ///             TimeRange::new_closed(14.into(), 17.into()),
    ///         ),
    ///         NonZeroU64::new(5).unwrap(),
    ///         32,
    ///     )),
    /// );
    /// ```
    pub fn intersection(&self, other: &Self) -> Result<Self, EmptyGrouping> {
        Ok(Self::new(
            self.area().wdm_intersection(other.area())?,
            core::cmp::min(self.max_count(), other.max_count()),
            self.max_size().greatest_lower_bound(&other.max_size()),
        ))
    }
}

/// 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.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> GreatestElement
    for AreaOfInterest<MCL, MCC, MPL, S>
where
    S: Clone + Ord + PredecessorExceptForLeast + SuccessorExceptForGreatest,
{
    fn greatest() -> Self {
        Self::new(Area::greatest(), NonZeroU64::MAX, u64::MAX)
    }

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