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        matches!(line.tag(), ChangeTag::Unchanged)
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) = 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 = JsonDetailTemplate {
69            detail: JsonDetailBody::Single { label: "same", body },
70        };
71        reporter.record_unchanged(name, COMPARES_NAME, preview_html, detail_html)?;
72        Ok(MayUnsupported::Ok(()))
73    }
74
75    fn report_modified(
76        &self,
77        name: &str,
78        diff: &JsonDiff,
79        reporter: &HtmlReport,
80    ) -> Result<MayUnsupported<()>, Self::Error> {
81        let JsonDiffBody::Modified(lines) = diff.body() else {
82            debug_assert!(false, "report_modified called with equal diff");
83            return Ok(MayUnsupported::Ok(()));
84        };
85        let preview_html = JsonPreviewTemplate {
86            body: JsonPreviewBody::Modified { lines },
87        };
88        let detail_html = JsonDetailTemplate {
89            detail: JsonDetailBody::Diff { lines },
90        };
91        reporter.record_modified(name, COMPARES_NAME, preview_html, detail_html)?;
92        Ok(MayUnsupported::Ok(()))
93    }
94
95    fn report_added(
96        &self,
97        name: &str,
98        data: &FileLeaf,
99        reporter: &HtmlReport,
100    ) -> Result<MayUnsupported<()>, Self::Error> {
101        if !is_json_mime(&data.kind) {
102            return Ok(MayUnsupported::Unsupported);
103        }
104        let Some(body) = try_into_json(&data.content) else {
105            return Ok(MayUnsupported::Unsupported);
106        };
107        let preview_html = JsonPreviewTemplate {
108            body: JsonPreviewBody::Added { body: &body },
109        };
110        let detail_html = JsonDetailTemplate {
111            detail: JsonDetailBody::Single {
112                label: "added",
113                body: &body,
114            },
115        };
116        reporter.record_added(name, COMPARES_NAME, preview_html, detail_html)?;
117        Ok(MayUnsupported::Ok(()))
118    }
119
120    fn report_deleted(
121        &self,
122        name: &str,
123        data: &FileLeaf,
124        reporter: &HtmlReport,
125    ) -> Result<MayUnsupported<()>, Self::Error> {
126        if !is_json_mime(&data.kind) {
127            return Ok(MayUnsupported::Unsupported);
128        }
129        let Some(body) = try_into_json(&data.content) else {
130            return Ok(MayUnsupported::Unsupported);
131        };
132        let preview_html = JsonPreviewTemplate {
133            body: JsonPreviewBody::Deleted { body: &body },
134        };
135        let detail_html = JsonDetailTemplate {
136            detail: JsonDetailBody::Single {
137                label: "deleted",
138                body: &body,
139            },
140        };
141        reporter.record_deleted(name, COMPARES_NAME, preview_html, detail_html)?;
142        Ok(MayUnsupported::Ok(()))
143    }
144}