gha_cache_proof/
render.rs1use anyhow::Result;
2
3use crate::model::{CacheProofReceipt, CheckStatus, OutputFormat};
4
5pub fn render_receipt(receipt: &CacheProofReceipt, format: OutputFormat) -> Result<String> {
6 match format {
7 OutputFormat::Text => Ok(render_text(receipt)),
8 OutputFormat::Json => Ok(format!("{}\n", serde_json::to_string_pretty(receipt)?)),
9 OutputFormat::Markdown => Ok(render_markdown(receipt)),
10 }
11}
12
13fn render_text(receipt: &CacheProofReceipt) -> String {
14 let mut out = String::new();
15 out.push_str(&format!(
16 "{} {} ({})\n",
17 receipt.tool.name, receipt.tool.version, receipt.mode
18 ));
19 out.push_str(&format!(
20 "summary: {} passed, {} warned, {} failed, {} skipped\n",
21 receipt.summary.passed,
22 receipt.summary.warnings,
23 receipt.summary.failed,
24 receipt.summary.skipped
25 ));
26
27 for op in &receipt.operations {
28 out.push_str(&format!(
29 "\n{:?}: key={} version={} scope={} cache-hit={:?}\n",
30 op.operation, op.key, op.version, op.scope, op.cache_hit
31 ));
32 if let Some(matched) = &op.matched {
33 out.push_str(&format!(
34 " matched {} in {} via {:?}\n",
35 matched.key, matched.scope, matched.match_kind
36 ));
37 }
38 for check in &op.checks {
39 out.push_str(&format!(
40 " {} {:<5} {}\n",
41 status_symbol(check.status),
42 status_word(check.status),
43 check.message
44 ));
45 }
46 }
47
48 for workflow in &receipt.workflows {
49 out.push_str(&format!(
50 "\nworkflow {}: {} cache steps\n",
51 workflow.workflow,
52 workflow.cache_steps.len()
53 ));
54 for step in &workflow.cache_steps {
55 out.push_str(&format!(
56 " {}[{}] {} key={}\n",
57 step.job_id, step.step_index, step.uses, step.key
58 ));
59 }
60 }
61
62 out
63}
64
65fn render_markdown(receipt: &CacheProofReceipt) -> String {
66 let mut out = String::new();
67 out.push_str("# gha-cache-proof Receipt\n\n");
68 out.push_str(&format!(
69 "- Tool: `{}` `{}`\n",
70 receipt.tool.name, receipt.tool.version
71 ));
72 out.push_str(&format!("- Mode: `{}`\n", markdown_escape(&receipt.mode)));
73 out.push_str(&format!("- Checked at: `{}`\n", receipt.checked_at));
74 out.push_str(&format!("- Store: `{}`\n", receipt.store));
75 out.push_str(&format!(
76 "- Summary: **{} passed**, **{} warned**, **{} failed**, **{} skipped**\n\n",
77 receipt.summary.passed,
78 receipt.summary.warnings,
79 receipt.summary.failed,
80 receipt.summary.skipped
81 ));
82
83 out.push_str("## Operations\n\n");
84 out.push_str("| Operation | Key | Scope | Match | Cache hit | Checks |\n");
85 out.push_str("| --- | --- | --- | --- | --- | --- |\n");
86 for op in &receipt.operations {
87 let matched = op
88 .matched
89 .as_ref()
90 .map(|m| format!("{} via {:?}", m.key, m.match_kind))
91 .unwrap_or_else(|| "miss".to_owned());
92 let summary = op.summary();
93 out.push_str(&format!(
94 "| {:?} | `{}` | `{}` | {} | `{}` | {}/{}/{}/{} |\n",
95 op.operation,
96 markdown_escape(&op.key),
97 markdown_escape(&op.scope),
98 markdown_escape(&matched),
99 markdown_escape(&op.cache_hit),
100 summary.passed,
101 summary.warnings,
102 summary.failed,
103 summary.skipped
104 ));
105 }
106
107 if !receipt.workflows.is_empty() {
108 out.push_str("\n## Workflows\n\n");
109 for workflow in &receipt.workflows {
110 out.push_str(&format!(
111 "### `{}`\n\n{} cache steps.\n\n",
112 workflow.workflow,
113 workflow.cache_steps.len()
114 ));
115 }
116 }
117
118 out
119}
120
121fn status_symbol(status: CheckStatus) -> &'static str {
122 match status {
123 CheckStatus::Pass => "[PASS]",
124 CheckStatus::Warn => "[WARN]",
125 CheckStatus::Fail => "[FAIL]",
126 CheckStatus::Skip => "[SKIP]",
127 }
128}
129
130fn status_word(status: CheckStatus) -> &'static str {
131 match status {
132 CheckStatus::Pass => "pass",
133 CheckStatus::Warn => "warn",
134 CheckStatus::Fail => "fail",
135 CheckStatus::Skip => "skip",
136 }
137}
138
139fn markdown_escape(value: &str) -> String {
140 value.replace('|', "\\|").replace('\n', "<br>")
141}