buildfix_artifacts/
lib.rs1use anyhow::Context;
4use buildfix_render::{render_apply_md, render_comment_md, render_plan_md};
5use buildfix_types::apply::BuildfixApply;
6use buildfix_types::plan::BuildfixPlan;
7use buildfix_types::report::BuildfixReport;
8use buildfix_types::schema::BUILDFIX_REPORT_V1;
9use buildfix_types::wire::{PlanV1, ReportV1};
10use camino::Utf8Path;
11use std::collections::BTreeMap;
12use std::fs;
13
14pub trait ArtifactWriter {
16 fn write_file(&self, path: &Utf8Path, contents: &[u8]) -> anyhow::Result<()>;
17 fn create_dir_all(&self, path: &Utf8Path) -> anyhow::Result<()>;
18}
19
20#[derive(Debug, Default, Clone)]
22pub struct FsArtifactWriter;
23
24impl ArtifactWriter for FsArtifactWriter {
25 fn write_file(&self, path: &Utf8Path, contents: &[u8]) -> anyhow::Result<()> {
26 if let Some(parent) = path.parent() {
27 fs::create_dir_all(parent)
28 .with_context(|| format!("create parent dir for {}", path))?;
29 }
30 fs::write(path, contents).with_context(|| format!("write {}", path))
31 }
32
33 fn create_dir_all(&self, path: &Utf8Path) -> anyhow::Result<()> {
34 fs::create_dir_all(path).with_context(|| format!("create dir {}", path))
35 }
36}
37
38fn render_plan_report(
39 plan: &BuildfixPlan,
40 report: &BuildfixReport,
41 patch: &str,
42 out_dir: &Utf8Path,
43) -> anyhow::Result<BTreeMap<String, Vec<u8>>> {
44 let plan_wire = PlanV1::try_from(plan).context("convert plan to wire")?;
45 let plan_json = serde_json::to_string_pretty(&plan_wire).context("serialize plan")?;
46
47 let report_wire = ReportV1::from(report);
48 let report_json = serde_json::to_string_pretty(&report_wire).context("serialize report")?;
49
50 let mut extra_report = report.clone();
51 extra_report.schema = BUILDFIX_REPORT_V1.to_string();
52 let extras_wire = ReportV1::from(&extra_report);
53 let extras_json =
54 serde_json::to_string_pretty(&extras_wire).context("serialize extras report")?;
55
56 let mut files = BTreeMap::new();
57 files.insert(
58 out_dir.join("plan.json").to_string(),
59 plan_json.into_bytes(),
60 );
61 files.insert(
62 out_dir.join("plan.md").to_string(),
63 render_plan_md(plan).into_bytes(),
64 );
65 files.insert(
66 out_dir.join("comment.md").to_string(),
67 render_comment_md(plan).into_bytes(),
68 );
69 files.insert(
70 out_dir.join("patch.diff").to_string(),
71 patch.as_bytes().to_vec(),
72 );
73 files.insert(
74 out_dir.join("report.json").to_string(),
75 report_json.into_bytes(),
76 );
77 files.insert(
78 out_dir
79 .join("extras")
80 .join("buildfix.report.v1.json")
81 .to_string(),
82 extras_json.into_bytes(),
83 );
84 Ok(files)
85}
86
87fn render_apply_report(
88 apply: &BuildfixApply,
89 report: &BuildfixReport,
90 patch: &str,
91 out_dir: &Utf8Path,
92) -> anyhow::Result<BTreeMap<String, Vec<u8>>> {
93 let apply_wire =
94 buildfix_types::wire::ApplyV1::try_from(apply).context("convert apply to wire")?;
95 let apply_json = serde_json::to_string_pretty(&apply_wire).context("serialize apply")?;
96
97 let report_wire = ReportV1::from(report);
98 let report_json = serde_json::to_string_pretty(&report_wire).context("serialize report")?;
99
100 let mut extra_report = report.clone();
101 extra_report.schema = BUILDFIX_REPORT_V1.to_string();
102 let extras_wire = ReportV1::from(&extra_report);
103 let extras_json =
104 serde_json::to_string_pretty(&extras_wire).context("serialize extras report")?;
105
106 let mut files = BTreeMap::new();
107 files.insert(
108 out_dir.join("apply.json").to_string(),
109 apply_json.into_bytes(),
110 );
111 files.insert(
112 out_dir.join("apply.md").to_string(),
113 render_apply_md(apply).into_bytes(),
114 );
115 files.insert(
116 out_dir.join("patch.diff").to_string(),
117 patch.as_bytes().to_vec(),
118 );
119 files.insert(
120 out_dir.join("report.json").to_string(),
121 report_json.into_bytes(),
122 );
123 files.insert(
124 out_dir
125 .join("extras")
126 .join("buildfix.report.v1.json")
127 .to_string(),
128 extras_json.into_bytes(),
129 );
130 Ok(files)
131}
132
133fn write_files<W: ArtifactWriter>(
134 files: BTreeMap<String, Vec<u8>>,
135 writer: &W,
136) -> anyhow::Result<()> {
137 for (path, contents) in files {
138 writer.write_file(Utf8Path::new(&path), &contents)?;
139 }
140 Ok(())
141}
142
143pub fn write_plan_artifacts<W: ArtifactWriter>(
145 plan: &BuildfixPlan,
146 report: &BuildfixReport,
147 patch: &str,
148 out_dir: &Utf8Path,
149 writer: &W,
150) -> anyhow::Result<()> {
151 writer.create_dir_all(out_dir)?;
152 writer.create_dir_all(&out_dir.join("extras"))?;
153 let files = render_plan_report(plan, report, patch, out_dir)?;
154 write_files(files, writer)
155}
156
157pub fn write_apply_artifacts<W: ArtifactWriter>(
159 apply: &BuildfixApply,
160 report: &BuildfixReport,
161 patch: &str,
162 out_dir: &Utf8Path,
163 writer: &W,
164) -> anyhow::Result<()> {
165 writer.create_dir_all(out_dir)?;
166 writer.create_dir_all(&out_dir.join("extras"))?;
167 let files = render_apply_report(apply, report, patch, out_dir)?;
168 write_files(files, writer)
169}