usereport/
renderer.rs

1use crate::analysis::AnalysisReport;
2
3use snafu::{ResultExt, Snafu};
4use std::{fmt::Debug, io::Write, path::PathBuf};
5
6/// Error type
7#[derive(Debug, Snafu)]
8#[allow(missing_docs)]
9pub enum Error {
10    /// Rendering of report to Json failed
11    #[snafu(display("failed to render report to Json: {}", source))]
12    RenderJsonFailed { source: serde_json::Error },
13    /// Failed to read handlebars template from file
14    #[snafu(display("failed to read handlebars template from file '{}': {}", path.display(), source))]
15    ReadHbsTemplateFileFailed { path: PathBuf, source: std::io::Error },
16    /// Handlebars template for Markdown is invalid
17    #[snafu(display("Handlebars template is invalid: {}", source))]
18    InvalidHbsTemplate { source: ::handlebars::TemplateError },
19    /// Rendering of report to Markdown failed
20    #[snafu(display("failed to render handlebars template: {}", source))]
21    RenderHbsTemplateFailed { source: ::handlebars::RenderError },
22}
23
24pub type Result<T, E = Error> = std::result::Result<T, E>;
25
26pub trait Renderer<W: Write>: Debug {
27    fn render(&self, report: &AnalysisReport, w: W) -> Result<()>;
28}
29
30pub use crate::renderer::handlebars::HbsRenderer;
31pub use json::JsonRenderer;
32
33pub mod json {
34    use super::*;
35
36    #[derive(Default, Debug, Eq, PartialEq, Clone)]
37    pub struct JsonRenderer {}
38
39    impl JsonRenderer {
40        pub fn new() -> Self { JsonRenderer {} }
41    }
42
43    impl<W: Write> Renderer<W> for JsonRenderer {
44        fn render(&self, report: &AnalysisReport, w: W) -> Result<()> {
45            serde_json::to_writer(w, report).context(RenderJsonFailed {})
46        }
47    }
48}
49
50pub mod handlebars {
51    use super::*;
52
53    use ::handlebars::Handlebars;
54    use std::{fs::File, io::Read, path::Path};
55
56    #[derive(Default, Debug, Eq, PartialEq, Clone)]
57    pub struct HbsRenderer {
58        template: String,
59    }
60
61    impl HbsRenderer {
62        pub fn new<T: Into<String>>(template: T) -> Self {
63            HbsRenderer {
64                template: template.into(),
65            }
66        }
67
68        pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
69            let mut file = File::open(path.as_ref()).context(ReadHbsTemplateFileFailed {
70                path: path.as_ref().to_path_buf(),
71            })?;
72            let mut template = String::new();
73            file.read_to_string(&mut template).context(ReadHbsTemplateFileFailed {
74                path: path.as_ref().to_path_buf(),
75            })?;
76
77            Ok(HbsRenderer { template })
78        }
79    }
80
81    impl<W: Write> Renderer<W> for HbsRenderer {
82        fn render(&self, report: &AnalysisReport, w: W) -> Result<()> {
83            let mut handlebars = Handlebars::new();
84            handlebars.register_helper("inc", Box::new(helpers::inc));
85            handlebars.register_helper("rfc2822", Box::new(helpers::date_time_2822));
86            handlebars.register_helper("rfc3339", Box::new(helpers::date_time_3339));
87            handlebars.register_helper("escape", Box::new(helpers::escape));
88            handlebars
89                .register_template_string("template", &self.template)
90                .context(InvalidHbsTemplate {})?;
91            handlebars
92                .render_to_write("template", report, w)
93                .context(RenderHbsTemplateFailed {})
94        }
95    }
96
97    mod helpers {
98        use chrono::{DateTime, Local};
99        use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError};
100
101        pub(crate) fn inc(
102            h: &Helper,
103            _: &Handlebars,
104            _: &Context,
105            _: &mut RenderContext,
106            out: &mut dyn Output,
107        ) -> HelperResult {
108            let value = h
109                .param(0)
110                .ok_or_else(|| RenderError::new("no such parameter"))?
111                .value()
112                .as_i64()
113                .ok_or_else(|| RenderError::new("parameter is not a number"))?;
114            let inc = format!("{}", value + 1);
115            out.write(&inc).map_err(RenderError::with)
116        }
117
118        pub(crate) fn date_time_2822(
119            h: &Helper,
120            _: &Handlebars,
121            _: &Context,
122            _: &mut RenderContext,
123            out: &mut dyn Output,
124        ) -> HelperResult {
125            let dt = date_param(h)?;
126            out.write(&dt.to_rfc2822()).map_err(RenderError::with)
127        }
128
129        pub(crate) fn date_time_3339(
130            h: &Helper,
131            _: &Handlebars,
132            _: &Context,
133            _: &mut RenderContext,
134            out: &mut dyn Output,
135        ) -> HelperResult {
136            let dt = date_param(h)?;
137            out.write(&dt.to_rfc3339()).map_err(RenderError::with)
138        }
139
140        fn date_param(h: &Helper) -> ::std::result::Result<DateTime<Local>, RenderError> {
141            let dt_str = h
142                .param(0)
143                .ok_or_else(|| RenderError::new("no such parameter"))?
144                .value()
145                .as_str()
146                .ok_or_else(|| RenderError::new("parameter is not a string"))?;
147            dt_str.parse::<DateTime<Local>>().map_err(RenderError::with)
148        }
149
150        pub(crate) fn escape(
151            h: &Helper,
152            _: &Handlebars,
153            _: &Context,
154            _: &mut RenderContext,
155            out: &mut dyn Output,
156        ) -> HelperResult {
157            let from = h
158                .param(0)
159                .ok_or_else(|| RenderError::new("no 'from' parameter"))?
160                .value()
161                .as_str()
162                .ok_or_else(|| RenderError::new("'from' parameter is not a string"))?;
163
164            let to = h
165                .param(1)
166                .ok_or_else(|| RenderError::new("no 'to' parameter"))?
167                .value()
168                .as_str()
169                .ok_or_else(|| RenderError::new("'to' parameter is not a string"))?;
170
171            let data = h
172                .param(2)
173                .ok_or_else(|| RenderError::new("no 'data' parameter"))?
174                .value()
175                .as_str()
176                .ok_or_else(|| RenderError::new("'data' parameter is not a string"))?;
177
178            let escaped = data.replace(from, to);
179            out.write(&escaped).map_err(RenderError::with)
180        }
181    }
182}