weavatrix-rust 2.0.1

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

const BUDGET_KEYS: &[&str] = &["maxFileLoc", "maxFunctionLoc", "runtimeCycles"];

pub(super) fn validate(value: &Value) -> Result<(), String> {
    let Some(budgets) = value.get("budgets") else {
        return Ok(());
    };
    let Some(budgets) = budgets.as_object() else {
        return Err("architecture contract budgets must be an object".to_owned());
    };
    for key in BUDGET_KEYS {
        if let Some(limit) = budgets.get(*key)
            && limit.as_u64().is_none()
        {
            return Err(format!(
                "architecture budget {key} must be a non-negative integer"
            ));
        }
    }
    Ok(())
}

pub(super) fn violations(state: &RepositoryState, value: &Value) -> Result<Vec<Value>, String> {
    let max_file = budget(value, "maxFileLoc");
    let max_function = budget(value, "maxFunctionLoc");
    let max_cycles = budget(value, "runtimeCycles");
    let mut output = BTreeMap::<String, Value>::new();

    if max_file.is_some() || max_function.is_some() {
        let metrics = source_metrics::collect(state, value, max_function.is_some())?;
        if let Some(maximum) = max_file {
            for file in metrics.files {
                if file.lines > maximum {
                    let identity = format!("budget.maxFileLoc|{}", file.path);
                    insert(
                        &mut output,
                        &identity,
                        "budget.maxFileLoc",
                        maximum,
                        &json!({
                            "kind": "file_loc",
                            "file": file.path,
                            "actual": file.lines,
                            "maximum": maximum
                        }),
                    );
                }
            }
        }
        if let Some(maximum) = max_function {
            for function in metrics.functions {
                if function.lines > maximum {
                    let identity = format!(
                        "budget.maxFunctionLoc|{}|{}|{}|{}",
                        function.file, function.kind, function.name, function.start_line
                    );
                    insert(
                        &mut output,
                        &identity,
                        "budget.maxFunctionLoc",
                        maximum,
                        &json!({
                            "kind": "function_loc",
                            "file": function.file,
                            "symbol": function.name,
                            "symbol_kind": function.kind,
                            "start_line": function.start_line,
                            "actual": function.lines,
                            "maximum": maximum
                        }),
                    );
                }
            }
        }
    }

    if let Some(maximum) = max_cycles {
        let cycles = super::super::health::runtime_dependency_cycles(state.graph(), &json!({}))
            .into_iter()
            .filter(|cycle| {
                cycle.iter().any(|member| {
                    member
                        .strip_prefix("file:")
                        .is_some_and(|path| component_for(value, path).is_some())
                })
            })
            .collect::<Vec<_>>();
        let actual = u64::try_from(cycles.len()).unwrap_or(u64::MAX);
        if actual > maximum {
            insert(
                &mut output,
                "budget.runtimeCycles",
                "budget.runtimeCycles",
                maximum,
                &json!({
                    "kind": "runtime_cycles",
                    "actual": actual,
                    "maximum": maximum,
                    "cycles": cycles
                }),
            );
        }
    }

    Ok(output.into_values().collect())
}

fn budget(value: &Value, key: &str) -> Option<u64> {
    value
        .pointer(&format!("/budgets/{key}"))
        .and_then(Value::as_u64)
}

fn insert(
    output: &mut BTreeMap<String, Value>,
    identity: &str,
    rule_id: &str,
    maximum: u64,
    evidence: &Value,
) {
    let fingerprint = stable_hash(identity);
    output.insert(
        fingerprint.clone(),
        json!({
            "fingerprint": fingerprint,
            "category": "budget",
            "rule": {
                "id": rule_id,
                "action": "limit",
                "maximum": maximum
            },
            "evidence": evidence
        }),
    );
}