pipecircuit 0.0.23

Correct. Robust. Fast. Portable. Pick four.
Documentation
use super::super::WarpTableRow;
use super::{
    MaintainedView, ObservedTable, ViewCreationError, ViewDefinition, ViewId, ViewRefreshError,
    ViewRevision,
};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CountPublication {
    pub through: ViewRevision,
    pub count: usize,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CountRefreshOutcome {
    Unchanged,
    Published(CountPublication),
}

/// Maintains membership internally and exposes only its cardinality.
/// A selector returns `Some(())` for membership and `None` for exclusion.
pub struct MaintainedCount<
    Row: WarpTableRow,
    Selection: ViewDefinition<Row, Value = ()>,
    Brand = Row,
> {
    membership: MaintainedView<Row, Selection, Brand>,
    published: CountPublication,
}

impl<Row: WarpTableRow, Brand> ObservedTable<Row, Brand> {
    pub fn maintain_count<Selection: ViewDefinition<Row, Value = ()>>(
        &self,
        id: ViewId,
        selection: Selection,
    ) -> Result<MaintainedCount<Row, Selection, Brand>, ViewCreationError> {
        let membership = self.maintain(id, selection)?;
        let published = CountPublication {
            through: membership.revision(),
            count: membership.len(),
        };
        Ok(MaintainedCount {
            membership,
            published,
        })
    }
}

impl<Row: WarpTableRow, Selection: ViewDefinition<Row, Value = ()>, Brand>
    MaintainedCount<Row, Selection, Brand>
{
    pub const fn publication(&self) -> CountPublication {
        self.published
    }

    pub fn refresh(
        &mut self,
        source: &ObservedTable<Row, Brand>,
    ) -> Result<CountRefreshOutcome, ViewRefreshError> {
        let Some(revision) = self.published.through.revision.checked_add(1) else {
            return Err(ViewRefreshError::RevisionExhausted);
        };
        self.membership.refresh(source)?;
        let count = self.membership.len();
        if count == self.published.count {
            return Ok(CountRefreshOutcome::Unchanged);
        }
        self.published.count = count;
        self.published.through.revision = revision;
        Ok(CountRefreshOutcome::Published(self.published))
    }

    pub fn replace_selection(
        &mut self,
        source: &ObservedTable<Row, Brand>,
        selection: Selection,
    ) -> Result<CountRefreshOutcome, (Selection, ViewRefreshError)> {
        self.membership.replace_definition(source, selection)?;
        self.published = CountPublication {
            through: self.membership.revision(),
            count: self.membership.len(),
        };
        Ok(CountRefreshOutcome::Published(self.published))
    }
}

#[cfg(test)]
mod tests {
    use super::super::tests::{RoomCount, insert, source};
    use super::*;

    #[test]
    fn exhausted_count_revision_preserves_last_publication() -> Result<(), &'static str> {
        let mut authority = source(2)?;
        insert(&mut authority, 7, 1, 13)?;
        let mut count = authority
            .maintain_count(ViewId::new(core::num::NonZeroU64::MIN), RoomCount(1))
            .map_err(|_| "count unavailable")?;
        count.published.through.revision = u64::MAX;
        let before = count.publication();
        insert(&mut authority, 11, 1, 17)?;
        assert_eq!(
            count.refresh(&authority),
            Err(ViewRefreshError::RevisionExhausted)
        );
        assert_eq!(count.publication(), before);
        assert_eq!(count.membership.len(), 1);
        Ok(())
    }
}