Skip to main content

plf/
errors.rs

1//! The Tera error type, with optional nice terminal error reporting.
2use 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/// An error that knows how to present itself nicely, with the right spans/notes etc.
18#[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    /// Create a ReportError without filename/source - must call set_source before generating report
39    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/// All the kind of errors Tera can produce.
89/// Non-exhaustive so we can add more if needed without a breaking change.
90#[derive(Debug, Clone, PartialEq, Eq)]
91#[non_exhaustive]
92pub enum ErrorKind {
93    /// Generic error
94    Msg(String),
95    /// Both lexer and parser errors. Will point to the source file
96    SyntaxError(Box<ReportError>),
97    /// An error that happens while rendering a template. Will point to the source file
98    RenderingError(Box<ReportError>),
99    /// A loop was found while looking up the inheritance chain
100    CircularExtend {
101        /// Name of the template with the loop
102        tpl: String,
103        /// All the parents templates we found so far
104        inheritance_chain: Vec<String>,
105    },
106    /// A loop was found while following `{% include %}` calls between templates
107    CircularInclude {
108        /// Name of the template where the cycle was detected
109        tpl: String,
110        /// Ordered chain of templates from `tpl` back to `tpl`
111        include_chain: Vec<String>,
112    },
113    /// A template is extending a template that wasn't found in the Tera instance
114    MissingParent {
115        /// The template we are currently looking at
116        current: String,
117        /// The missing template
118        parent: String,
119    },
120    /// A template is calling a macro namespace that is not loaded
121    NamespaceNotLoaded {
122        /// Name of the template with the issue
123        tpl: String,
124        /// The namespace causing problems
125        namespace: String,
126    },
127    /// The template is calling a macro which isn't found in the namespace
128    MacroNotFound {
129        /// Name of the template with the issue
130        tpl: String,
131        /// The namespace used
132        namespace: String,
133        /// The name of the macro that cannot be found
134        name: String,
135    },
136    /// A template was missing
137    TemplateNotFound(String),
138    /// A component was missing
139    /// Only raised if you're trying to render a specific component
140    ComponentNotFound(String),
141    /// A filter/test main value was not the expected type
142    InvalidArgument {
143        #[allow(missing_docs)]
144        expected_type: String,
145        #[allow(missing_docs)]
146        actual_type: String,
147    },
148    /// A function/test/filter was expecting an argument but it wasn't found
149    MissingArgument {
150        #[allow(missing_docs)]
151        arg_name: String,
152    },
153    /// An IO error occurred
154    Io(std::io::ErrorKind),
155    /// UTF-8 conversion error when converting output to UTF-8
156    ///
157    /// This should not occur unless invalid UTF8 chars are rendered
158    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/// The Error struct for Tera.
222#[derive(Debug)]
223pub struct Error {
224    pub(crate) kind: ErrorKind,
225    // If the error comes from some third party libs, TODO we need that?
226    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    /// Creates a new error of the given kind.
237    pub fn new(kind: ErrorKind) -> Self {
238        Self { kind, source: None }
239    }
240
241    /// Returns the kind of error
242    pub fn kind(&self) -> &ErrorKind {
243        &self.kind
244    }
245
246    /// Creates generic error with a source
247    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    /// Creates generic error with a message and no source.
255    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
368/// A custom Result type for this library
369pub 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}