covgate 0.2.0-rc0

Diff-focused coverage gates for local CI, pull requests, and autonomous coding agents.
Documentation
use anyhow::Result;

use crate::model::{ComputedMetric, GateRule, GateScopeResult, RuleOutcome};

pub fn evaluate(
    label: Option<String>,
    metrics: Vec<ComputedMetric>,
    rules: &[GateRule],
) -> Result<GateScopeResult> {
    let mut outcomes = Vec::new();
    let mut all_passed = true;

    for rule in rules {
        let metric = if let Some(metric) = metrics.iter().find(|m| m.metric == rule.metric()) {
            metric
        } else {
            let base = format!(
                "configured rule for {} is not supported by the loaded report",
                rule.metric().as_str()
            );
            if let Some(label) = &label {
                anyhow::bail!("{base} in gate `{label}`");
            }
            anyhow::bail!("{base}");
        };

        let rule_passed = match rule {
            GateRule::Percent {
                minimum_percent,
                metric: _,
            } => metric.percent + f64::EPSILON >= *minimum_percent,
            GateRule::UncoveredCount {
                maximum_count,
                metric: _,
            } => metric.uncovered_changed_opportunities.len() <= *maximum_count,
        };

        if !rule_passed {
            all_passed = false;
        }

        outcomes.push(RuleOutcome {
            rule: rule.clone(),
            passed: rule_passed,
            observed_percent: metric.percent,
            observed_uncovered_count: metric.uncovered_changed_opportunities.len(),
        });
    }

    Ok(GateScopeResult {
        label,
        metrics,
        rules: outcomes,
        passed: all_passed,
    })
}