cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
//! Warn when an issue's outgoing link or `relates:` target is absent
//! from the workspace.

use crate::domain::model::check::{CheckViolationKind, Severity};
use crate::domain::usecases::check::{CheckViolation, IssueCheckCtx, IssueFinding, IssueRule};

pub struct UnknownLinkTargetRule;

pub const RULE_ID: &str = "issue/unknown-link-target";

impl IssueRule for UnknownLinkTargetRule {
    fn id(&self) -> &'static str {
        RULE_ID
    }

    fn find(&self, ctx: &IssueCheckCtx<'_>) -> anyhow::Result<Vec<IssueFinding>> {
        if ctx.known_refs.is_empty() {
            return Ok(Vec::new());
        }
        let mut out = Vec::new();
        for (path, issue) in ctx.issues {
            for link in issue.links.iter() {
                if !ctx.known_refs.contains(link.target.as_entity_ref()) {
                    let kind = CheckViolationKind::LinkTargetNotFound {
                        target: link.target.as_entity_ref().clone(),
                    };
                    out.push(CheckViolation {
                        rule_id: RULE_ID,
                        path: path.clone(),
                        severity: Severity::Warning,
                        kind,
                    });
                }
            }
            for target in issue.relates().iter() {
                if !ctx.known_refs.contains(target) {
                    let kind = CheckViolationKind::LinkTargetNotFound {
                        target: target.clone(),
                    };
                    out.push(CheckViolation {
                        rule_id: RULE_ID,
                        path: path.clone(),
                        severity: Severity::Warning,
                        kind,
                    });
                }
            }
        }
        Ok(out.into_iter().map(IssueFinding::report).collect())
    }
}