provenance-cli 0.2.3

Command-line interface for Provenance requirements traceability
use crate::handlers::check::index::CheckIndex;
use crate::handlers::check::references::{check_scoped_reference, node_type_name};
use provenance_core::{Message, ScopeId, Thread};
use provenance_store::state_store::StateStore;
use std::collections::BTreeSet;

pub(super) struct Records {
    threads: Vec<Thread>,
    messages: Vec<Message>,
}

impl Records {
    pub(super) fn load(store: &StateStore, scope_id: &ScopeId) -> anyhow::Result<Self> {
        Ok(Self {
            threads: store.list_threads(scope_id)?,
            messages: store.list_messages(scope_id)?,
        })
    }

    pub(super) fn validate_scope_ownership(
        &self,
        manifest_scopes: &BTreeSet<String>,
        loaded_scope_id: &ScopeId,
        findings: &mut Vec<String>,
    ) {
        macro_rules! check_records {
            ($records:expr, $record_type:literal) => {
                for record in $records {
                    super::check_scope_ownership(
                        loaded_scope_id,
                        &record.scope_id,
                        $record_type,
                        &record.id,
                        findings,
                    );
                }
            };
        }

        for thread in &self.threads {
            if manifest_scopes.contains(thread.scope_id.as_str()) {
                super::check_scope_ownership(
                    loaded_scope_id,
                    &thread.scope_id,
                    "thread",
                    &thread.id,
                    findings,
                );
            }
        }
        check_records!(&self.messages, "message");
    }

    pub(super) fn add_to(&self, index: &mut CheckIndex) {
        for thread in &self.threads {
            index.add_node(&thread.scope_id, "thread", &thread.id);
        }
        for message in &self.messages {
            index.add_node(&message.scope_id, "message", &message.id);
        }
    }

    pub(super) fn validate(
        &self,
        index: &CheckIndex,
        manifest_scopes: &BTreeSet<String>,
        scope_id: &ScopeId,
        dangling: &mut Vec<String>,
    ) {
        for thread in &self.threads {
            let owner = format!("thread {}", thread.id.as_str());
            if !manifest_scopes.contains(thread.scope_id.as_str()) {
                dangling.push(format!(
                    "{owner} is in unknown scope {}",
                    thread.scope_id.as_str()
                ));
            }
            check_scoped_reference(
                index,
                dangling,
                scope_id,
                &owner,
                "parent",
                node_type_name(thread.parent.node_type),
                &thread.parent.node_id,
            );
        }
        for message in &self.messages {
            check_scoped_reference(
                index,
                dangling,
                scope_id,
                &format!("message {}", message.id.as_str()),
                "thread",
                "thread",
                &message.thread_id,
            );
        }
    }
}