use anyhow::Result;
use crate::model::{CacheProofReceipt, CheckStatus, OutputFormat};
pub fn render_receipt(receipt: &CacheProofReceipt, format: OutputFormat) -> Result<String> {
match format {
OutputFormat::Text => Ok(render_text(receipt)),
OutputFormat::Json => Ok(format!("{}\n", serde_json::to_string_pretty(receipt)?)),
OutputFormat::Markdown => Ok(render_markdown(receipt)),
}
}
fn render_text(receipt: &CacheProofReceipt) -> String {
let mut out = String::new();
out.push_str(&format!(
"{} {} ({})\n",
receipt.tool.name, receipt.tool.version, receipt.mode
));
out.push_str(&format!(
"summary: {} passed, {} warned, {} failed, {} skipped\n",
receipt.summary.passed,
receipt.summary.warnings,
receipt.summary.failed,
receipt.summary.skipped
));
for op in &receipt.operations {
out.push_str(&format!(
"\n{:?}: key={} version={} scope={} cache-hit={:?}\n",
op.operation, op.key, op.version, op.scope, op.cache_hit
));
if let Some(matched) = &op.matched {
out.push_str(&format!(
" matched {} in {} via {:?}\n",
matched.key, matched.scope, matched.match_kind
));
}
for check in &op.checks {
out.push_str(&format!(
" {} {:<5} {}\n",
status_symbol(check.status),
status_word(check.status),
check.message
));
}
}
for workflow in &receipt.workflows {
out.push_str(&format!(
"\nworkflow {}: {} cache steps\n",
workflow.workflow,
workflow.cache_steps.len()
));
for step in &workflow.cache_steps {
out.push_str(&format!(
" {}[{}] {} key={}\n",
step.job_id, step.step_index, step.uses, step.key
));
}
}
out
}
fn render_markdown(receipt: &CacheProofReceipt) -> String {
let mut out = String::new();
out.push_str("# gha-cache-proof Receipt\n\n");
out.push_str(&format!(
"- Tool: `{}` `{}`\n",
receipt.tool.name, receipt.tool.version
));
out.push_str(&format!("- Mode: `{}`\n", markdown_escape(&receipt.mode)));
out.push_str(&format!("- Checked at: `{}`\n", receipt.checked_at));
out.push_str(&format!("- Store: `{}`\n", receipt.store));
out.push_str(&format!(
"- Summary: **{} passed**, **{} warned**, **{} failed**, **{} skipped**\n\n",
receipt.summary.passed,
receipt.summary.warnings,
receipt.summary.failed,
receipt.summary.skipped
));
out.push_str("## Operations\n\n");
out.push_str("| Operation | Key | Scope | Match | Cache hit | Checks |\n");
out.push_str("| --- | --- | --- | --- | --- | --- |\n");
for op in &receipt.operations {
let matched = op
.matched
.as_ref()
.map(|m| format!("{} via {:?}", m.key, m.match_kind))
.unwrap_or_else(|| "miss".to_owned());
let summary = op.summary();
out.push_str(&format!(
"| {:?} | `{}` | `{}` | {} | `{}` | {}/{}/{}/{} |\n",
op.operation,
markdown_escape(&op.key),
markdown_escape(&op.scope),
markdown_escape(&matched),
markdown_escape(&op.cache_hit),
summary.passed,
summary.warnings,
summary.failed,
summary.skipped
));
}
if !receipt.workflows.is_empty() {
out.push_str("\n## Workflows\n\n");
for workflow in &receipt.workflows {
out.push_str(&format!(
"### `{}`\n\n{} cache steps.\n\n",
workflow.workflow,
workflow.cache_steps.len()
));
}
}
out
}
fn status_symbol(status: CheckStatus) -> &'static str {
match status {
CheckStatus::Pass => "[PASS]",
CheckStatus::Warn => "[WARN]",
CheckStatus::Fail => "[FAIL]",
CheckStatus::Skip => "[SKIP]",
}
}
fn status_word(status: CheckStatus) -> &'static str {
match status {
CheckStatus::Pass => "pass",
CheckStatus::Warn => "warn",
CheckStatus::Fail => "fail",
CheckStatus::Skip => "skip",
}
}
fn markdown_escape(value: &str) -> String {
value.replace('|', "\\|").replace('\n', "<br>")
}