cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
//! Build a `KnownRefs` from every configured repository (ISSUE-018D373JDKYFA).
//!
//! Aliases contribute on equal footing with canonical ids: a link target
//! written as `ADR-0008` (a pre-TSID short form) resolves the same as the
//! canonical `ADR-00PBCT63105NH` once the migration mapped them.

use crate::domain::model::entity_ref::{EntityRef, KnownRefs};
use crate::domain::usecases::decision_record::DecisionRecordRepository;
use crate::domain::usecases::issue::IssueRepository;

/// Load every well-formed record from the supplied repositories and
/// assemble the set of references the workspace can resolve. Aliases
/// are included.
///
/// Unparseable files are silently absent — `cartu check` is the
/// dedicated reporter for parse failures, surfacing them via the
/// `EntryDefectScanner` pipeline.
pub fn load_known_refs(
    decision_repos: &[&dyn DecisionRecordRepository],
    issue_repo: &dyn IssueRepository,
) -> anyhow::Result<KnownRefs> {
    let mut refs = KnownRefs::new();

    for repo in decision_repos {
        for record in repo.list()? {
            refs.insert(record.id.as_entity_ref().clone());
            for alias in &record.aliases {
                if let Ok(r) = EntityRef::new(alias) {
                    refs.insert(r);
                }
            }
        }
    }

    for issue in issue_repo.list()? {
        refs.insert(issue.id.as_entity_ref().clone());
        for alias in &issue.aliases {
            if let Ok(r) = EntityRef::new(alias) {
                refs.insert(r);
            }
        }
    }

    Ok(refs)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::usecases::decision_record::tests::{adr, dr, FakeDecisionRecordRepository};
    use crate::domain::usecases::issue::tests::{ir, issue, FakeIssueRepository};

    fn er(s: &str) -> EntityRef {
        EntityRef::new(s).unwrap()
    }

    #[test]
    fn empty_repositories_yield_empty_set() {
        let dr_repo = FakeDecisionRecordRepository::with_records(vec![]);
        let issue_repo = FakeIssueRepository::new();
        let refs = load_known_refs(&[&dr_repo], &issue_repo).unwrap();
        assert!(refs.is_empty());
    }

    #[test]
    fn canonical_ids_are_collected() {
        let dr_repo = FakeDecisionRecordRepository::with_records(vec![adr("Use Rust")
            .with_id("ADR-0001")
            .build(dr(1))]);
        let issue_repo = FakeIssueRepository::with_issues(vec![issue("Add login")
            .with_id("ISSUE-0042")
            .build(ir(42))]);
        let refs = load_known_refs(&[&dr_repo], &issue_repo).unwrap();
        assert!(refs.contains(&er("ADR-0001")));
        assert!(refs.contains(&er("ISSUE-0042")));
    }

    #[test]
    fn aliases_are_collected_alongside_canonical_id() {
        let dr_repo = FakeDecisionRecordRepository::with_records(vec![adr("Prefixed id")
            .with_id("ADR-0001")
            .with_alias("ADR-0008")
            .build(dr(1))]);
        let issue_repo = FakeIssueRepository::new();
        let refs = load_known_refs(&[&dr_repo], &issue_repo).unwrap();
        assert!(refs.contains(&er("ADR-0001")));
        assert!(refs.contains(&er("ADR-0008")));
    }

    #[test]
    fn malformed_aliases_are_silently_skipped() {
        let dr_repo = FakeDecisionRecordRepository::with_records(vec![adr("Bad alias")
            .with_id("ADR-0001")
            .with_alias("not a ref")
            .build(dr(1))]);
        let issue_repo = FakeIssueRepository::new();
        let refs = load_known_refs(&[&dr_repo], &issue_repo).unwrap();
        assert!(refs.contains(&er("ADR-0001")));
    }
}