cranpose-core 0.9.7

Composition runtime and reactive state for Cranpose
Documentation
use super::{
    super::{GroupRecord, PayloadRecord, SlotTable},
    SlotInvariantError,
    groups::{SlotTreeChecks, SlotTreeView},
};

pub(super) fn validate_group_payloads(
    view: &SlotTreeView<'_>,
    checks: &mut impl SlotTreeChecks,
    group_index: usize,
    group: &GroupRecord,
    expected_payload_start: usize,
) -> Result<usize, SlotInvariantError> {
    let payload_start = group.payload_start as usize;
    if payload_start != expected_payload_start {
        return Err(view.payload_start_mismatch(
            group_index,
            expected_payload_start,
            payload_start,
        ));
    }

    let payload_len = group.payload_len as usize;
    let payload_end = payload_start.saturating_add(payload_len);
    if payload_end > view.payloads.item_count() {
        return Err(view.payload_out_of_range(
            group_index,
            payload_start,
            payload_len,
            view.payloads.item_count(),
        ));
    }

    for payload_index in 0..payload_len {
        let Some(payload) = view.payloads.item(payload_start + payload_index) else {
            return Err(view.payload_out_of_range(
                group_index,
                payload_start,
                payload_len,
                view.payloads.item_count(),
            ));
        };
        checks.validate_payload(group_index, group, payload_index, payload)?;
    }

    Ok(payload_end)
}

pub(super) fn validate_active_payload_anchor(
    table: &SlotTable,
    group: &GroupRecord,
    payload_index: usize,
    payload: &PayloadRecord,
) -> Result<(), SlotInvariantError> {
    let expected_location = (group.anchor, payload_index);
    let actual = table.payload_anchors.active_location(payload.anchor);
    if actual == Some(expected_location) {
        return Ok(());
    }

    Err(SlotInvariantError::PayloadAnchorRegistryMismatch {
        payload_anchor: payload.anchor,
        expected: expected_location,
        actual,
    })
}

pub(super) fn validate_payload_anchor_registry_count(
    table: &SlotTable,
) -> Result<(), SlotInvariantError> {
    if table.payload_anchors.active_len() == table.payloads.len() {
        return Ok(());
    }

    Err(SlotInvariantError::PayloadAnchorRegistryCountMismatch {
        expected: table.payloads.len(),
        actual: table.payload_anchors.active_len(),
    })
}

pub(super) fn validate_payload_anchor_registry_integrity(
    table: &SlotTable,
) -> Result<(), SlotInvariantError> {
    table.payload_anchors.validate_integrity()
}

pub(super) fn validate_payload_anchor_registry(
    table: &SlotTable,
) -> Result<(), SlotInvariantError> {
    for (payload_anchor, (owner, payload_index)) in table.payload_anchors.active_entries() {
        let Some(group_index) = table.anchors.active_index(owner) else {
            return Err(SlotInvariantError::PayloadAnchorRegistryTargetMismatch {
                payload_anchor,
                expected_owner: owner,
                expected_payload_index: payload_index,
                actual: None,
            });
        };
        let actual = table
            .group_payload_record_at(group_index, payload_index)
            .map(|payload| payload.anchor);
        if actual != Some(payload_anchor) {
            return Err(SlotInvariantError::PayloadAnchorRegistryTargetMismatch {
                payload_anchor,
                expected_owner: owner,
                expected_payload_index: payload_index,
                actual,
            });
        }
    }

    Ok(())
}

#[cfg(test)]
#[path = "tests/payloads_tests.rs"]
mod tests;