use crate::cli;
use anyhow::{Result, bail};
use serde::Serialize;
use tokmd_gate::{GateResult, RatchetGateResult, evaluate_policy, evaluate_ratchet_policy};
use crate::config::ResolvedConfig;
#[path = "gate/policy.rs"]
mod policy;
#[path = "gate/receipt.rs"]
mod receipt;
#[path = "gate/render.rs"]
mod render;
const EXIT_FAIL: i32 = 1;
#[derive(Debug, Clone, Serialize)]
struct CombinedGateResult {
pub passed: bool,
pub policy: Option<GateResult>,
pub ratchet: Option<RatchetGateResult>,
pub total_errors: usize,
pub total_warnings: usize,
}
pub(crate) fn handle(
args: cli::CliGateArgs,
global: &cli::GlobalArgs,
resolved: &ResolvedConfig,
) -> Result<()> {
let receipt = receipt::load_or_compute_receipt(&args, global)?;
let policy = policy::load_policy(&args, resolved).ok();
let baseline = policy::load_baseline(&args, resolved)?;
let ratchet_config = if baseline.is_some() {
policy::load_ratchet_config(&args, resolved)?
} else {
None
};
if policy.is_none() && ratchet_config.is_none() {
bail!(
"No policy or ratchet rules specified.\n\
\n\
Use --policy <path> for policy rules, or\n\
--baseline <path> with --ratchet-config <path> for ratchet rules, or\n\
add rules to [gate] in tokmd.toml.\n\
\n\
Example tokmd.toml with policy rules:\n\
\n\
[[gate.rules]]\n\
name = \"max_tokens\"\n\
pointer = \"/derived/totals/tokens\"\n\
op = \"lte\"\n\
value = 500000\n\
\n\
Example tokmd.toml with ratchet rules:\n\
\n\
[gate]\n\
baseline = \".tokmd/baseline.json\"\n\
\n\
[[gate.ratchet]]\n\
pointer = \"/complexity/avg_cyclomatic\"\n\
max_increase_pct = 10.0\n\
description = \"Avg cyclomatic complexity\""
);
}
let policy_result = policy.as_ref().map(|p| evaluate_policy(&receipt, p));
let ratchet_result = match (&baseline, &ratchet_config) {
(Some(baseline_value), Some(ratchet)) => {
Some(evaluate_ratchet_policy(ratchet, baseline_value, &receipt))
}
_ => None,
};
let combined = combine_results(policy_result, ratchet_result);
match args.format {
cli::GateFormat::Text => render::print_text_result(&combined),
cli::GateFormat::Json => render::print_json_result(&combined)?,
}
if !combined.passed {
std::process::exit(EXIT_FAIL);
}
Ok(())
}
fn combine_results(
policy: Option<GateResult>,
ratchet: Option<RatchetGateResult>,
) -> CombinedGateResult {
let policy_errors = policy.as_ref().map(|p| p.errors).unwrap_or(0);
let policy_warnings = policy.as_ref().map(|p| p.warnings).unwrap_or(0);
let ratchet_errors = ratchet.as_ref().map(|r| r.errors).unwrap_or(0);
let ratchet_warnings = ratchet.as_ref().map(|r| r.warnings).unwrap_or(0);
let total_errors = policy_errors + ratchet_errors;
let total_warnings = policy_warnings + ratchet_warnings;
let passed = total_errors == 0;
CombinedGateResult {
passed,
policy,
ratchet,
total_errors,
total_warnings,
}
}