use crate::model::{Report, scenario_budget, scenario_delta_pct, thousands};
#[must_use]
pub fn render(report: &Report) -> String {
let mut out = String::new();
out.push_str("## cu-profiler report\n\n");
let sum = &report.summary;
out.push_str(&format!(
"**{}** scenario(s): {} passed · {} warned · {} failed — **{} total CU**\n\n",
sum.total_scenarios,
sum.passed,
sum.warned,
sum.failed,
thousands(sum.total_cu),
));
out.push_str("| Scenario | Actual CU | Budget | Delta | Status |\n");
out.push_str("| --- | ---: | ---: | ---: | :---: |\n");
for s in &report.scenarios {
let budget = scenario_budget(s).map_or_else(|| "—".to_string(), thousands);
let delta = scenario_delta_pct(s).map_or_else(|| "—".to_string(), |d| format!("{d:+.1}%"));
out.push_str(&format!(
"| `{}` | {} | {} | {} | {} {} |\n",
md_code(&s.name),
thousands(s.measurement.total_cu),
budget,
delta,
status_emoji(s.status),
s.status.label(),
));
}
let diagnostics: Vec<_> = report
.scenarios
.iter()
.flat_map(|s| &s.diagnostics)
.collect();
if !diagnostics.is_empty() {
out.push_str("\n### Diagnostics\n\n");
for d in diagnostics {
out.push_str(&format!(
"- **{}** (`{}`)\n - {}\n - _Recommendation:_ {}\n",
md_text(&d.title),
md_code(&d.scenario),
md_text(&d.evidence),
md_text(&d.recommendation),
));
}
}
out
}
fn md_text(s: &str) -> String {
s.replace(['\n', '\r'], " ").replace('|', "\\|")
}
fn md_code(s: &str) -> String {
md_text(s).replace('`', "'")
}
fn status_emoji(status: cu_profiler_core::model::Status) -> &'static str {
use cu_profiler_core::model::Status;
match status {
Status::Pass => "🟢",
Status::Warn => "🟡",
Status::Fail => "🔴",
Status::Unknown => "⚪",
}
}
#[cfg(test)]
mod tests {
use super::*;
use cu_profiler_core::Profiler;
use cu_profiler_core::backend::RecordedLogsBackend;
use cu_profiler_core::metadata::RunMetadata;
use cu_profiler_core::scenario::Scenario;
#[test]
fn renders_markdown_table() {
let mut backend = RecordedLogsBackend::new();
backend.insert_blob(
"swap",
"Program P invoke [1]\nProgram P consumed 1000 of 200000 compute units\nProgram P success",
true,
);
let report = Profiler::new().run(
&backend,
&[Scenario::new("swap")],
None,
RunMetadata::recorded("0.1.0"),
);
let md = render(&report);
assert!(md.contains("## cu-profiler report"));
assert!(md.contains("| `swap` |"));
assert!(md.contains("PASS"));
}
#[test]
fn sanitises_pipes_backticks_and_newlines() {
assert_eq!(md_text("a|b"), "a\\|b");
assert_eq!(md_text("line1\nline2"), "line1 line2");
assert_eq!(md_code("we`ird|name"), "we'ird\\|name");
}
#[test]
fn malicious_scenario_name_does_not_break_table_row() {
let mut backend = RecordedLogsBackend::new();
backend.insert_blob(
"evil|name`",
"Program P invoke [1]\nProgram P consumed 1000 of 200000 compute units\nProgram P success",
true,
);
let report = Profiler::new().run(
&backend,
&[Scenario::new("evil|name`")],
None,
RunMetadata::recorded("0.1.0"),
);
let md = render(&report);
let row = md
.lines()
.find(|l| l.contains("evil"))
.expect("data row present");
let total_pipes = row.matches('|').count();
let escaped_pipes = row.matches("\\|").count();
assert_eq!(
total_pipes - escaped_pipes,
6,
"unescaped pipe leaked into row: {row}"
);
assert!(row.contains("evil\\|name'"), "name not sanitised: {row}");
}
}