1use std::error::Error as StdError;
2use std::fmt::{self, Write};
3use std::io::Error as IOError;
4use std::num::ParseIntError;
5use std::string::FromUtf8Error;
6
7use serde_json::error::Error as SerdeError;
8use thiserror::Error;
9
10#[cfg(feature = "dir_source")]
11use walkdir::Error as WalkdirError;
12
13#[cfg(feature = "script_helper")]
14use rhai::{EvalAltResult, ParseError};
15
16/// Error when rendering data on template.
17#[derive(Debug, Default, Error)]
18pub struct RenderError {
19 pub desc: String,
20 pub template_name: Option<String>,
21 pub line_no: Option<usize>,
22 pub column_no: Option<usize>,
23 #[source]
24 cause: Option<Box<dyn StdError + Send + Sync + 'static>>,
25 unimplemented: bool,
26 // backtrace: Backtrace,
27}
28
29impl fmt::Display for RenderError {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
31 match (self.line_no, self.column_no) {
32 (Some(line: usize), Some(col: usize)) => write!(
33 f,
34 "Error rendering \"{}\" line {}, col {}: {}",
35 self.template_name.as_deref().unwrap_or("Unnamed template"),
36 line,
37 col,
38 self.desc
39 ),
40 _ => write!(f, "{}", self.desc),
41 }
42 }
43}
44
45impl From<IOError> for RenderError {
46 fn from(e: IOError) -> RenderError {
47 RenderError::from_error(error_info:"Cannot generate output.", cause:e)
48 }
49}
50
51impl From<SerdeError> for RenderError {
52 fn from(e: SerdeError) -> RenderError {
53 RenderError::from_error(error_info:"Failed to access JSON data.", cause:e)
54 }
55}
56
57impl From<FromUtf8Error> for RenderError {
58 fn from(e: FromUtf8Error) -> RenderError {
59 RenderError::from_error(error_info:"Failed to generate bytes.", cause:e)
60 }
61}
62
63impl From<ParseIntError> for RenderError {
64 fn from(e: ParseIntError) -> RenderError {
65 RenderError::from_error(error_info:"Cannot access array/vector with string index.", cause:e)
66 }
67}
68
69impl From<TemplateError> for RenderError {
70 fn from(e: TemplateError) -> RenderError {
71 RenderError::from_error(error_info:"Failed to parse template.", cause:e)
72 }
73}
74
75#[cfg(feature = "script_helper")]
76impl From<Box<EvalAltResult>> for RenderError {
77 fn from(e: Box<EvalAltResult>) -> RenderError {
78 RenderError::from_error("Cannot convert data to Rhai dynamic", e)
79 }
80}
81
82#[cfg(feature = "script_helper")]
83impl From<ScriptError> for RenderError {
84 fn from(e: ScriptError) -> RenderError {
85 RenderError::from_error("Failed to load rhai script", e)
86 }
87}
88
89impl RenderError {
90 pub fn new<T: AsRef<str>>(desc: T) -> RenderError {
91 RenderError {
92 desc: desc.as_ref().to_owned(),
93 ..Default::default()
94 }
95 }
96
97 pub(crate) fn unimplemented() -> RenderError {
98 RenderError {
99 unimplemented: true,
100 ..Default::default()
101 }
102 }
103
104 pub fn strict_error(path: Option<&String>) -> RenderError {
105 let msg = match path {
106 Some(path) => format!("Variable {:?} not found in strict mode.", path),
107 None => "Value is missing in strict mode".to_owned(),
108 };
109 RenderError::new(&msg)
110 }
111
112 pub fn from_error<E>(error_info: &str, cause: E) -> RenderError
113 where
114 E: StdError + Send + Sync + 'static,
115 {
116 let mut e = RenderError::new(error_info);
117 e.cause = Some(Box::new(cause));
118
119 e
120 }
121
122 #[inline]
123 pub(crate) fn is_unimplemented(&self) -> bool {
124 self.unimplemented
125 }
126}
127
128/// Template parsing error
129#[derive(Debug, Error)]
130pub enum TemplateErrorReason {
131 #[error("helper {0:?} was opened, but {1:?} is closing")]
132 MismatchingClosedHelper(String, String),
133 #[error("decorator {0:?} was opened, but {1:?} is closing")]
134 MismatchingClosedDecorator(String, String),
135 #[error("invalid handlebars syntax.")]
136 InvalidSyntax,
137 #[error("invalid parameter {0:?}")]
138 InvalidParam(String),
139 #[error("nested subexpression is not supported")]
140 NestedSubexpression,
141 #[error("Template \"{1}\": {0}")]
142 IoError(IOError, String),
143 #[cfg(feature = "dir_source")]
144 #[error("Walk dir error: {err}")]
145 WalkdirError {
146 #[from]
147 err: WalkdirError,
148 },
149}
150
151/// Error on parsing template.
152#[derive(Debug, Error)]
153pub struct TemplateError {
154 #[deprecated(note = "public access to reason to be removed soon, use .reason() instead.")]
155 pub reason: TemplateErrorReason,
156 pub template_name: Option<String>,
157 pub line_no: Option<usize>,
158 pub column_no: Option<usize>,
159 segment: Option<String>,
160}
161
162impl TemplateError {
163 #[allow(deprecated)]
164 pub fn of(e: TemplateErrorReason) -> TemplateError {
165 TemplateError {
166 reason: e,
167 template_name: None,
168 line_no: None,
169 column_no: None,
170 segment: None,
171 }
172 }
173
174 pub fn at(mut self, template_str: &str, line_no: usize, column_no: usize) -> TemplateError {
175 self.line_no = Some(line_no);
176 self.column_no = Some(column_no);
177 self.segment = Some(template_segment(template_str, line_no, column_no));
178 self
179 }
180
181 pub fn in_template(mut self, name: String) -> TemplateError {
182 self.template_name = Some(name);
183 self
184 }
185
186 /// Get underlying reason for the error
187 #[allow(deprecated)]
188 pub fn reason(&self) -> &TemplateErrorReason {
189 &self.reason
190 }
191}
192
193impl From<(IOError, String)> for TemplateError {
194 fn from(err_info: (IOError, String)) -> TemplateError {
195 let (e: Error, name: String) = err_info;
196 TemplateError::of(TemplateErrorReason::IoError(e, name))
197 }
198}
199
200#[cfg(feature = "dir_source")]
201impl From<WalkdirError> for TemplateError {
202 fn from(e: WalkdirError) -> TemplateError {
203 TemplateError::of(TemplateErrorReason::from(e))
204 }
205}
206
207fn template_segment(template_str: &str, line: usize, col: usize) -> String {
208 let range = 3;
209 let line_start = if line >= range { line - range } else { 0 };
210 let line_end = line + range;
211
212 let mut buf = String::new();
213 for (line_count, line_content) in template_str.lines().enumerate() {
214 if line_count >= line_start && line_count <= line_end {
215 let _ = writeln!(&mut buf, "{:4} | {}", line_count, line_content);
216 if line_count == line - 1 {
217 buf.push_str(" |");
218 for c in 0..line_content.len() {
219 if c != col {
220 buf.push('-');
221 } else {
222 buf.push('^');
223 }
224 }
225 buf.push('\n');
226 }
227 }
228 }
229
230 buf
231}
232
233impl fmt::Display for TemplateError {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
235 match (self.line_no, self.column_no, &self.segment) {
236 (Some(line: usize), Some(col: usize), &Some(ref seg: &String)) => writeln!(
237 f,
238 "Template error: {}\n --> Template error in \"{}\":{}:{}\n |\n{} |\n = reason: {}",
239 self.reason(),
240 self.template_name
241 .as_ref()
242 .unwrap_or(&"Unnamed template".to_owned()),
243 line,
244 col,
245 seg,
246 self.reason()
247 ),
248 _ => write!(f, "{}", self.reason()),
249 }
250 }
251}
252
253#[cfg(feature = "script_helper")]
254#[derive(Debug, Error)]
255pub enum ScriptError {
256 #[error(transparent)]
257 IoError(#[from] IOError),
258
259 #[error(transparent)]
260 ParseError(#[from] ParseError),
261}
262