#[path = "explain_walk.rs"]
mod explain_walk;
use std::io::Write;
use whyno_core::checks::{CheckReport, LayerResult};
use whyno_core::fix::{commands, scoring, Fix, FixPlan};
use whyno_core::state::{Probe, SystemState};
use super::layer_name;
use crate::error::OutputError;
pub fn render(
report: &CheckReport,
plan: &FixPlan,
state: &SystemState,
writer: &mut dyn Write,
) -> Result<(), OutputError> {
write_subject_section(state, writer)?;
writeln!(writer)?;
explain_walk::write_path_walk(state, writer)?;
writeln!(writer)?;
write_layer_details(report, writer)?;
writeln!(writer)?;
write_fix_plan(plan, writer)?;
Ok(())
}
fn write_subject_section(state: &SystemState, writer: &mut dyn Write) -> Result<(), OutputError> {
let s = &state.subject;
writeln!(writer, "=== Subject ===")?;
writeln!(writer, " uid: {}", s.uid)?;
writeln!(writer, " gid: {}", s.gid)?;
writeln!(writer, " groups: {:?}", s.groups)?;
if let Probe::Known(bitmask) = s.capabilities {
writeln!(writer, " caps: {bitmask:#018x}")?;
}
let op = format!("{:?}", state.operation).to_lowercase();
writeln!(writer, " operation: {op}")?;
Ok(())
}
fn write_layer_details(report: &CheckReport, writer: &mut dyn Write) -> Result<(), OutputError> {
writeln!(writer, "=== Layer Results ===")?;
for (layer, result) in &report.core_results {
write_layer_result(layer_name::short(layer), result, writer)?;
}
for (layer, result) in &report.mac_results {
write_layer_result(layer_name::short_mac(*layer), result, writer)?;
}
Ok(())
}
fn write_layer_result(
name: &str,
result: &LayerResult,
writer: &mut dyn Write,
) -> Result<(), OutputError> {
match result {
LayerResult::Pass { detail, warnings } => {
writeln!(writer, " {name}: PASS -- {detail}")?;
for warning in warnings {
writeln!(writer, " warning: {warning}")?;
}
}
LayerResult::Fail {
detail,
component_index,
} => {
writeln!(writer, " {name}: FAIL -- {detail}")?;
if let Some(idx) = component_index {
writeln!(writer, " failing component index: {idx}")?;
}
}
LayerResult::Degraded { reason } => {
writeln!(writer, " {name}: DEGRADED -- {reason}")?;
}
_ => {
writeln!(writer, " {name}: UNKNOWN")?;
}
}
Ok(())
}
fn write_fix_plan(plan: &FixPlan, writer: &mut dyn Write) -> Result<(), OutputError> {
writeln!(writer, "=== Fix Plan ===")?;
if plan.fixes.is_empty() {
writeln!(writer, " No fixes needed.")?;
return Ok(());
}
for (i, fix) in plan.fixes.iter().enumerate() {
write_fix_detail(i, fix, writer)?;
}
write_plan_warnings(&plan.warnings, writer)?;
Ok(())
}
fn write_fix_detail(index: usize, fix: &Fix, writer: &mut dyn Write) -> Result<(), OutputError> {
let name = layer_name::short(fix.layer);
let cmd = commands::render_command(&fix.action);
let warn = if scoring::needs_warning(fix.impact) {
" [HIGH IMPACT]"
} else {
""
};
writeln!(
writer,
" [{index}] layer={name} impact={}/6{warn}",
fix.impact
)?;
writeln!(writer, " command: {cmd}")?;
writeln!(writer, " description: {}", fix.description)?;
Ok(())
}
fn write_plan_warnings(warnings: &[String], writer: &mut dyn Write) -> Result<(), OutputError> {
if warnings.is_empty() {
return Ok(());
}
writeln!(writer, " Warnings:")?;
for warning in warnings {
writeln!(writer, " - {warning}")?;
}
Ok(())
}
#[cfg(test)]
#[path = "explain_tests.rs"]
mod tests;