1 | use std::error::Error as StdError; |
2 | use std::fmt::{self, Write}; |
3 | use std::io::Error as IOError; |
4 | use std::num::ParseIntError; |
5 | use std::string::FromUtf8Error; |
6 | |
7 | use serde_json::error::Error as SerdeError; |
8 | use thiserror::Error; |
9 | |
10 | #[cfg (feature = "dir_source" )] |
11 | use walkdir::Error as WalkdirError; |
12 | |
13 | #[cfg (feature = "script_helper" )] |
14 | use rhai::{EvalAltResult, ParseError}; |
15 | |
16 | /// Error when rendering data on template. |
17 | #[derive (Debug, Default, Error)] |
18 | pub 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 | |
29 | impl 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 | |
45 | impl From<IOError> for RenderError { |
46 | fn from(e: IOError) -> RenderError { |
47 | RenderError::from_error(error_info:"Cannot generate output." , cause:e) |
48 | } |
49 | } |
50 | |
51 | impl 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 | |
57 | impl 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 | |
63 | impl 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 | |
69 | impl 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" )] |
76 | impl 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" )] |
83 | impl From<ScriptError> for RenderError { |
84 | fn from(e: ScriptError) -> RenderError { |
85 | RenderError::from_error("Failed to load rhai script" , e) |
86 | } |
87 | } |
88 | |
89 | impl 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)] |
130 | pub 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)] |
153 | pub 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 | |
162 | impl 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 | |
193 | impl 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" )] |
201 | impl From<WalkdirError> for TemplateError { |
202 | fn from(e: WalkdirError) -> TemplateError { |
203 | TemplateError::of(TemplateErrorReason::from(e)) |
204 | } |
205 | } |
206 | |
207 | fn 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 | |
233 | impl 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)] |
255 | pub enum ScriptError { |
256 | #[error(transparent)] |
257 | IoError(#[from] IOError), |
258 | |
259 | #[error(transparent)] |
260 | ParseError(#[from] ParseError), |
261 | } |
262 | |