Skip to main content

semdiff_differ_json/
report_html.rs

1use 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}