1use std::error::Error as StdError;
3use std::fmt::{self};
4
5use crate::reporting::generate_report;
6
7use crate::utils::Span;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub(crate) struct Note {
11 pub(crate) label: String,
12 pub(crate) filename: String,
13 pub(crate) source: String,
14 pub(crate) span: Span,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ReportError {
20 pub(crate) message: String,
21 pub(crate) filename: String,
22 pub(crate) source: String,
23 pub(crate) span: Span,
24 pub(crate) notes: Vec<Note>,
25}
26
27impl ReportError {
28 pub(crate) fn new(message: String, filename: &str, source: &str, span: &Span) -> Self {
29 Self {
30 message,
31 filename: filename.to_string(),
32 source: source.to_string(),
33 span: span.clone(),
34 notes: Vec::new(),
35 }
36 }
37
38 pub(crate) fn new_without_source(message: String, span: &Span) -> Self {
40 Self {
41 message,
42 filename: String::new(),
43 source: String::new(),
44 span: span.clone(),
45 notes: Vec::new(),
46 }
47 }
48
49 pub(crate) fn set_source(&mut self, filename: &str, source: &str) {
50 self.filename = filename.to_string();
51 self.source = source.to_string();
52 }
53
54 pub(crate) fn add_note(&mut self, label: &str, filename: &str, source: &str, span: &Span) {
55 self.notes.push(Note {
56 label: label.to_string(),
57 filename: filename.to_string(),
58 source: source.to_string(),
59 span: span.clone(),
60 });
61 }
62
63 pub fn generate_report(&self) -> String {
64 generate_report(self)
65 }
66
67 pub fn message(&self) -> &str {
68 &self.message
69 }
70
71 pub fn span(&self) -> &Span {
72 &self.span
73 }
74
75 pub fn filename(&self) -> &str {
76 &self.filename
77 }
78
79 pub fn source(&self) -> &str {
80 &self.source
81 }
82
83 pub(crate) fn unexpected_end_of_input(span: &Span) -> Self {
84 Self::new_without_source("Unexpected end of input".to_string(), span)
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
91#[non_exhaustive]
92pub enum ErrorKind {
93 Msg(String),
95 SyntaxError(Box<ReportError>),
97 RenderingError(Box<ReportError>),
99 CircularExtend {
101 tpl: String,
103 inheritance_chain: Vec<String>,
105 },
106 CircularInclude {
108 tpl: String,
110 include_chain: Vec<String>,
112 },
113 MissingParent {
115 current: String,
117 parent: String,
119 },
120 NamespaceNotLoaded {
122 tpl: String,
124 namespace: String,
126 },
127 MacroNotFound {
129 tpl: String,
131 namespace: String,
133 name: String,
135 },
136 TemplateNotFound(String),
138 ComponentNotFound(String),
141 InvalidArgument {
143 #[allow(missing_docs)]
144 expected_type: String,
145 #[allow(missing_docs)]
146 actual_type: String,
147 },
148 MissingArgument {
150 #[allow(missing_docs)]
151 arg_name: String,
152 },
153 Io(std::io::ErrorKind),
155 Utf8Conversion,
159}
160
161impl fmt::Display for ErrorKind {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 ErrorKind::Msg(message) => write!(f, "{message}"),
165 ErrorKind::SyntaxError(s) | ErrorKind::RenderingError(s) => {
166 write!(f, "{}", s.generate_report())
167 }
168 ErrorKind::CircularExtend {
169 tpl,
170 inheritance_chain,
171 } => write!(
172 f,
173 "Circular extend detected for template '{tpl}'. Inheritance chain: `{inheritance_chain:?}`",
174 ),
175 ErrorKind::CircularInclude { tpl, include_chain } => write!(
176 f,
177 "Circular include detected for template '{tpl}'. Include chain: `{include_chain:?}`",
178 ),
179 ErrorKind::MissingParent { current, parent } => write!(
180 f,
181 "Template '{current}' is inheriting from '{parent}', which doesn't exist or isn't loaded.",
182 ),
183 ErrorKind::TemplateNotFound(name) => write!(f, "Template '{name}' not found"),
184 ErrorKind::ComponentNotFound(name) => write!(f, "Component '{name}' not found"),
185 ErrorKind::NamespaceNotLoaded { tpl, namespace } => write!(
186 f,
187 "Template '{tpl}' is trying to use namespace `{namespace}` which is not loaded",
188 ),
189 ErrorKind::MacroNotFound {
190 tpl,
191 namespace,
192 name,
193 } => write!(
194 f,
195 "Template '{tpl}' is using macro `{namespace}::{name}` which is not found in the namespace",
196 ),
197 ErrorKind::InvalidArgument {
198 expected_type,
199 actual_type,
200 } => write!(
201 f,
202 "Invalid type for the value, expected `{expected_type}` but got `{actual_type}`"
203 ),
204 ErrorKind::MissingArgument { arg_name } => {
205 write!(f, "Missing keyword argument `{arg_name}`")
206 }
207 ErrorKind::Io(io_error) => {
208 write!(
209 f,
210 "Io error while writing rendered value to output: {:?}",
211 io_error
212 )
213 }
214 ErrorKind::Utf8Conversion => {
215 write!(f, "Invalid UTF-8 characters found while rendering.")
216 }
217 }
218 }
219}
220
221#[derive(Debug)]
223pub struct Error {
224 pub(crate) kind: ErrorKind,
225 pub(crate) source: Option<Box<dyn std::error::Error + Send + Sync>>,
227}
228
229impl fmt::Display for Error {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 write!(f, "{}", self.kind)
232 }
233}
234
235impl Error {
236 pub fn new(kind: ErrorKind) -> Self {
238 Self { kind, source: None }
239 }
240
241 pub fn kind(&self) -> &ErrorKind {
243 &self.kind
244 }
245
246 pub fn chain(value: impl ToString, source: impl Into<Box<dyn StdError + Send + Sync>>) -> Self {
248 Self {
249 kind: ErrorKind::Msg(value.to_string()),
250 source: Some(source.into()),
251 }
252 }
253
254 pub fn message(message: impl ToString) -> Self {
256 Self {
257 kind: ErrorKind::Msg(message.to_string()),
258 source: None,
259 }
260 }
261
262 pub(crate) fn syntax_error(message: String, span: &Span) -> Self {
263 Self {
264 kind: ErrorKind::SyntaxError(Box::new(ReportError::new_without_source(message, span))),
265 source: None,
266 }
267 }
268
269 pub(crate) fn circular_extend(tpl: impl ToString, inheritance_chain: Vec<String>) -> Self {
270 Self {
271 kind: ErrorKind::CircularExtend {
272 tpl: tpl.to_string(),
273 inheritance_chain,
274 },
275 source: None,
276 }
277 }
278
279 pub(crate) fn circular_include(tpl: impl ToString, include_chain: Vec<String>) -> Self {
280 Self {
281 kind: ErrorKind::CircularInclude {
282 tpl: tpl.to_string(),
283 include_chain,
284 },
285 source: None,
286 }
287 }
288
289 pub(crate) fn missing_parent(current: impl ToString, parent: impl ToString) -> Self {
290 Self {
291 kind: ErrorKind::MissingParent {
292 current: current.to_string(),
293 parent: parent.to_string(),
294 },
295 source: None,
296 }
297 }
298
299 pub(crate) fn io_error(error: std::io::Error) -> Self {
300 Self {
301 kind: ErrorKind::Io(error.kind()),
302 source: Some(Box::new(error)),
303 }
304 }
305
306 pub(crate) fn template_not_found(tpl: impl ToString) -> Self {
307 Self {
308 kind: ErrorKind::TemplateNotFound(tpl.to_string()),
309 source: None,
310 }
311 }
312
313 pub(crate) fn component_not_found(name: impl ToString) -> Self {
314 Self {
315 kind: ErrorKind::ComponentNotFound(name.to_string()),
316 source: None,
317 }
318 }
319
320 pub(crate) fn invalid_arg_type(
321 expected_type: impl ToString,
322 actual_type: impl ToString,
323 ) -> Self {
324 Self {
325 kind: ErrorKind::InvalidArgument {
326 expected_type: expected_type.to_string(),
327 actual_type: actual_type.to_string(),
328 },
329 source: None,
330 }
331 }
332
333 pub(crate) fn missing_arg(arg_name: impl ToString) -> Self {
334 Self {
335 kind: ErrorKind::MissingArgument {
336 arg_name: arg_name.to_string(),
337 },
338 source: None,
339 }
340 }
341
342 pub(crate) fn invalid_utf8(error: std::string::FromUtf8Error) -> Self {
343 Self {
344 kind: ErrorKind::Utf8Conversion,
345 source: Some(Box::new(error)),
346 }
347 }
348}
349
350impl std::error::Error for Error {
351 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
352 self.source.as_ref().map(|e| e.as_ref() as _)
353 }
354}
355
356impl From<std::io::Error> for Error {
357 fn from(error: std::io::Error) -> Self {
358 Self::io_error(error)
359 }
360}
361
362impl From<std::string::FromUtf8Error> for Error {
363 fn from(error: std::string::FromUtf8Error) -> Self {
364 Self::invalid_utf8(error)
365 }
366}
367
368pub type TeraResult<T> = Result<T, Error>;
370
371#[cfg(test)]
372mod tests {
373 #[test]
374 fn test_error_is_send_and_sync() {
375 fn test_send_sync<T: Send + Sync>() {}
376
377 test_send_sync::<super::Error>();
378 }
379}