cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
//! Reject two issues sharing a numeric id. Same shape as the DR-side
//! rule.

use std::collections::HashMap;

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

pub struct DuplicateIdRule;

pub const RULE_ID: &str = "issue/duplicate-id";

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

    fn find(&self, ctx: &IssueCheckCtx<'_>) -> anyhow::Result<Vec<IssueFinding>> {
        let mut by_id: HashMap<IssueRef, Vec<std::path::PathBuf>> = HashMap::new();
        for (path, issue) in ctx.issues {
            by_id
                .entry(issue.id.clone())
                .or_default()
                .push(path.clone());
        }
        let mut out = Vec::new();
        for (id, paths) in by_id {
            if paths.len() < 2 {
                continue;
            }
            for path in &paths {
                let also_at: Vec<_> = paths.iter().filter(|p| *p != path).cloned().collect();
                let kind = CheckViolationKind::DuplicateId {
                    id: id.as_entity_ref().clone(),
                    also_at,
                };
                out.push(CheckViolation {
                    rule_id: RULE_ID,
                    path: path.clone(),
                    severity: Severity::Error,
                    kind,
                });
            }
        }
        Ok(out.into_iter().map(IssueFinding::report).collect())
    }
}