weavatrix-rust 2.1.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use super::contract::{component_for, list_contains};
use crate::engine::RepositoryState;
use crate::operations::node_path;
use blazingly_json::{Value, json};
use std::collections::{BTreeMap, BTreeSet};

const COUPLING_KINDS: &[&str] = &["any", "runtime", "type-only"];
const RELATION_KINDS: &[&str] = &[
    "contains",
    "imports",
    "calls",
    "references",
    "method",
    "implements",
    "re_exports",
    "depends_on",
    "inherits",
    "publishes",
    "consumes",
    "binds",
    "reads",
    "writes",
    "deploys",
    "exposes",
    "mounts",
    "configures",
];

pub(super) fn dependency_violations(state: &RepositoryState, value: &Value) -> Vec<Value> {
    let mut output = BTreeMap::<String, Value>::new();
    for edge in state.graph().edges() {
        let Some(source) = state.graph().node(edge.source.as_str()) else {
            continue;
        };
        let Some(target) = state.graph().node(edge.target.as_str()) else {
            continue;
        };
        let Some(source_file) = node_path(source) else {
            continue;
        };
        let Some(target_file) = node_path(target) else {
            continue;
        };
        let Some(from) = component_for(value, source_file) else {
            continue;
        };
        let Some(to) = component_for(value, target_file) else {
            continue;
        };
        if from == to {
            continue;
        }
        for rule in matching_rules(value, from, to, edge) {
            let identity = format!(
                "{}|{}|{}|{}",
                rule["id"].as_str().unwrap_or("rule"),
                edge.source,
                edge.target,
                edge.kind.as_str()
            );
            let fingerprint = stable_hash(&identity);
            output.entry(fingerprint.clone()).or_insert_with(|| {
                json!({
                    "fingerprint": fingerprint,
                    "rule": rule,
                    "source": source,
                    "target": target,
                    "edge": edge
                })
            });
        }
    }
    output.into_values().collect()
}

pub(super) fn validate(value: &Value) -> Result<(), String> {
    let mut unsupported = BTreeSet::new();
    for rule in value
        .get("dependencyRules")
        .and_then(Value::as_array)
        .into_iter()
        .flatten()
    {
        for kind in rule
            .get("kinds")
            .and_then(Value::as_array)
            .into_iter()
            .flatten()
            .filter_map(Value::as_str)
        {
            if !COUPLING_KINDS.contains(&kind) && !RELATION_KINDS.contains(&kind) {
                unsupported.insert(kind.to_owned());
            }
        }
    }
    if unsupported.is_empty() {
        return Ok(());
    }
    Err(format!(
        "architecture contract uses dependency kinds this engine cannot evaluate: {}. \
         Supported values are {} plus relation names such as imports, calls, inherits. \
         Rules are rejected rather than skipped, because a rule that matches nothing \
         would report a passing verification.",
        unsupported.into_iter().collect::<Vec<_>>().join(", "),
        COUPLING_KINDS.join(", ")
    ))
}

pub(super) fn stable_hash(value: &str) -> String {
    let mut hash = 0xcbf2_9ce4_8422_2325_u64;
    for byte in value.bytes() {
        hash ^= u64::from(byte);
        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
    }
    format!("{hash:016x}")
}

fn rule_selects_edge(rule: &Value, edge: &weavatrix_graph::Edge) -> bool {
    let coupling = match edge.attributes.get("coupling") {
        Some(weavatrix_graph::AttributeValue::String(value)) => value.as_str(),
        _ => "runtime",
    };
    list_contains(rule.get("kinds"), "any")
        || list_contains(rule.get("kinds"), coupling)
        || list_contains(rule.get("kinds"), edge.kind.as_str())
}

fn matching_rules<'contract>(
    value: &'contract Value,
    from: &str,
    to: &str,
    edge: &weavatrix_graph::Edge,
) -> Vec<&'contract Value> {
    value
        .get("dependencyRules")
        .and_then(Value::as_array)
        .into_iter()
        .flatten()
        .filter(|rule| rule["action"] == "forbid")
        .filter(|rule| list_contains(rule.get("from"), from))
        .filter(|rule| list_contains(rule.get("to"), to))
        .filter(|rule| rule_selects_edge(rule, edge))
        .collect()
}