use serde::Serialize;
use tuff_core::policy::PolicyCoverageEntry;
use tuff_hooks_spec::CoverageLevel;
use crate::adapter::{AdapterKind, AgentAdapter};
use crate::error::Result;
use super::render_table;
#[derive(Serialize)]
struct AgentPolicyMatrix {
adapter: &'static str,
display_name: &'static str,
rules: Vec<PolicyCoverageEntry>,
}
pub fn cmd_policy_matrix(json: bool) -> Result<()> {
let matrices: Vec<AgentPolicyMatrix> = AdapterKind::all()
.into_iter()
.map(|adapter| AgentPolicyMatrix {
adapter: adapter.id(),
display_name: adapter.display_name(),
rules: adapter.policy_compatibility(),
})
.collect();
if json {
println!("{}", serde_json::to_string_pretty(&matrices)?);
return Ok(());
}
let mut rows = Vec::new();
let mut notes: Vec<String> = Vec::new();
for matrix in &matrices {
for entry in &matrix.rules {
rows.push(vec![
matrix.adapter.to_string(),
entry.effect.as_str().to_string(),
entry.subject.as_str().to_string(),
coverage_label(entry.coverage).to_string(),
entry.mechanism.clone().unwrap_or_default(),
]);
if let Some(caveat) = &entry.caveat {
let note = format!("{}: {caveat}", matrix.adapter);
if !notes.contains(¬e) {
notes.push(note);
}
}
}
}
println!(
"{}",
render_table(
&["ADAPTER", "EFFECT", "SUBJECT", "COVERAGE", "MECHANISM"],
&rows
)
);
if !notes.is_empty() {
println!("\nNotes:");
for note in notes {
println!("- {note}");
}
}
Ok(())
}
fn coverage_label(coverage: CoverageLevel) -> &'static str {
match coverage {
CoverageLevel::Full => "full",
CoverageLevel::Partial => "partial",
CoverageLevel::Unsupported => "unsupported",
}
}