semdiff_differ_json/
report_html.rs1use crate::{ChangeTag, JsonDiff, JsonDiffBody, JsonDiffLine, JsonDiffReporter, is_json_mime, try_into_json};
2use askama::Template;
3use semdiff_core::fs::FileLeaf;
4use semdiff_core::{DetailReporter, MayUnsupported};
5use semdiff_output::html::{HtmlReport, HtmlReportError};
6use thiserror::Error;
7
8const COMPARES_NAME: &str = "json";
9
10#[derive(Debug, Error)]
11pub enum JsonDiffReportError {
12 #[error("html report error: {0}")]
13 HtmlReport(#[from] HtmlReportError),
14}
15
16#[derive(Template)]
17#[template(path = "json_preview.html")]
18struct JsonPreviewTemplate<'a> {
19 body: JsonPreviewBody<'a>,
20}
21
22enum JsonPreviewBody<'a> {
23 Unchanged { body: &'a str },
24 Modified { lines: &'a [JsonDiffLine] },
25 Added { body: &'a str },
26 Deleted { body: &'a str },
27}
28
29impl JsonPreviewTemplate<'_> {
30 fn is_equal(line: &&JsonDiffLine) -> bool {
31 line.is_equal_for_result()
32 }
33}
34
35#[derive(Template)]
36#[template(path = "json_detail.html")]
37struct JsonDetailTemplate<'a> {
38 detail: JsonDetailBody<'a>,
39}
40
41enum JsonDetailBody<'a> {
42 Diff { lines: &'a [JsonDiffLine] },
43 Single { label: &'a str, body: &'a str },
44}
45
46impl JsonDetailBody<'_> {
47 fn is_multicolumn(&self) -> bool {
48 matches!(self, JsonDetailBody::Diff { .. })
49 }
50}
51
52impl DetailReporter<JsonDiff, FileLeaf, HtmlReport> for JsonDiffReporter {
53 type Error = JsonDiffReportError;
54
55 fn report_unchanged(
56 &self,
57 name: &str,
58 diff: &JsonDiff,
59 reporter: &HtmlReport,
60 ) -> Result<MayUnsupported<()>, Self::Error> {
61 let JsonDiffBody::Equal { body, ignored_lines } = diff.body() else {
62 debug_assert!(false, "report_unchanged called with modified diff");
63 return Ok(MayUnsupported::Ok(()));
64 };
65 let preview_html = JsonPreviewTemplate {
66 body: JsonPreviewBody::Unchanged { body },
67 };
68 let detail_html = if ignored_lines.is_empty() {
69 JsonDetailTemplate {
70 detail: JsonDetailBody::Single { label: "same", body },
71 }
72 } else {
73 JsonDetailTemplate {
74 detail: JsonDetailBody::Diff {
75 lines: &ignored_lines[..],
76 },
77 }
78 };
79 reporter.record_unchanged(name, COMPARES_NAME, preview_html, detail_html)?;
80 Ok(MayUnsupported::Ok(()))
81 }
82
83 fn report_modified(
84 &self,
85 name: &str,
86 diff: &JsonDiff,
87 reporter: &HtmlReport,
88 ) -> Result<MayUnsupported<()>, Self::Error> {
89 let JsonDiffBody::Modified(lines) = diff.body() else {
90 debug_assert!(false, "report_modified called with equal diff");
91 return Ok(MayUnsupported::Ok(()));
92 };
93 let preview_html = JsonPreviewTemplate {
94 body: JsonPreviewBody::Modified { lines: &lines[..] },
95 };
96 let detail_html = JsonDetailTemplate {
97 detail: JsonDetailBody::Diff { lines: &lines[..] },
98 };
99 reporter.record_modified(name, COMPARES_NAME, preview_html, detail_html)?;
100 Ok(MayUnsupported::Ok(()))
101 }
102
103 fn report_added(
104 &self,
105 name: &str,
106 data: &FileLeaf,
107 reporter: &HtmlReport,
108 ) -> Result<MayUnsupported<()>, Self::Error> {
109 if !is_json_mime(&data.kind) {
110 return Ok(MayUnsupported::Unsupported);
111 }
112 let Some(body) = try_into_json(&data.content) else {
113 return Ok(MayUnsupported::Unsupported);
114 };
115 let preview_html = JsonPreviewTemplate {
116 body: JsonPreviewBody::Added { body: &body },
117 };
118 let detail_html = JsonDetailTemplate {
119 detail: JsonDetailBody::Single {
120 label: "added",
121 body: &body,
122 },
123 };
124 reporter.record_added(name, COMPARES_NAME, preview_html, detail_html)?;
125 Ok(MayUnsupported::Ok(()))
126 }
127
128 fn report_deleted(
129 &self,
130 name: &str,
131 data: &FileLeaf,
132 reporter: &HtmlReport,
133 ) -> Result<MayUnsupported<()>, Self::Error> {
134 if !is_json_mime(&data.kind) {
135 return Ok(MayUnsupported::Unsupported);
136 }
137 let Some(body) = try_into_json(&data.content) else {
138 return Ok(MayUnsupported::Unsupported);
139 };
140 let preview_html = JsonPreviewTemplate {
141 body: JsonPreviewBody::Deleted { body: &body },
142 };
143 let detail_html = JsonDetailTemplate {
144 detail: JsonDetailBody::Single {
145 label: "deleted",
146 body: &body,
147 },
148 };
149 reporter.record_deleted(name, COMPARES_NAME, preview_html, detail_html)?;
150 Ok(MayUnsupported::Ok(()))
151 }
152}