minijinja/
error.rs

1use std::borrow::Cow;
2use std::fmt;
3use std::sync::Arc;
4
5use crate::compiler::tokens::Span;
6
7/// Represents template errors.
8///
9/// If debug mode is enabled a template error contains additional debug
10/// information that can be displayed by formatting an error with the
11/// alternative formatting (``format!("{:#}", err)``).  That information
12/// is also shown for the [`Debug`] display where the extended information
13/// is hidden when the alternative formatting is used.
14///
15/// Since MiniJinja takes advantage of chained errors it's recommended
16/// to render the entire chain to better understand the causes.
17///
18/// # Example
19///
20/// Here is an example of how you might want to render errors:
21///
22/// ```rust
23/// # let mut env = minijinja::Environment::new();
24/// # env.add_template("", "");
25/// # let template = env.get_template("").unwrap(); let ctx = ();
26/// match template.render(ctx) {
27///     Ok(result) => println!("{}", result),
28///     Err(err) => {
29///         eprintln!("Could not render template: {:#}", err);
30///         // render causes as well
31///         let mut err = &err as &dyn std::error::Error;
32///         while let Some(next_err) = err.source() {
33///             eprintln!();
34///             eprintln!("caused by: {:#}", next_err);
35///             err = next_err;
36///         }
37///     }
38/// }
39/// ```
40pub struct Error {
41    repr: Box<ErrorRepr>,
42}
43
44/// The internal error data
45#[derive(Clone)]
46struct ErrorRepr {
47    kind: ErrorKind,
48    detail: Option<Cow<'static, str>>,
49    name: Option<String>,
50    lineno: usize,
51    span: Option<Span>,
52    source: Option<Arc<dyn std::error::Error + Send + Sync>>,
53    #[cfg(feature = "debug")]
54    debug_info: Option<Arc<crate::debug::DebugInfo>>,
55}
56
57impl fmt::Debug for Error {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        let mut err = f.debug_struct("Error");
60        err.field("kind", &self.kind());
61        if let Some(ref detail) = self.repr.detail {
62            err.field("detail", detail);
63        }
64        if let Some(ref name) = self.name() {
65            err.field("name", name);
66        }
67        if let Some(line) = self.line() {
68            err.field("line", &line);
69        }
70        if let Some(ref source) = std::error::Error::source(self) {
71            err.field("source", source);
72        }
73        ok!(err.finish());
74
75        // so this is a bit questionable, but because of how commonly errors are just
76        // unwrapped i think it's sensible to spit out the debug info following the
77        // error struct dump.
78        #[cfg(feature = "debug")]
79        {
80            if !f.alternate() && self.debug_info().is_some() {
81                ok!(writeln!(f));
82                ok!(writeln!(f, "{}", self.display_debug_info()));
83            }
84        }
85
86        Ok(())
87    }
88}
89
90/// An enum describing the error kind.
91#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
92#[non_exhaustive]
93pub enum ErrorKind {
94    /// A non primitive value was encountered where one was expected.
95    NonPrimitive,
96    /// A value is not valid for a key in a map.
97    NonKey,
98    /// An invalid operation was attempted.
99    InvalidOperation,
100    /// The template has a syntax error
101    SyntaxError,
102    /// A template was not found.
103    TemplateNotFound,
104    /// Too many arguments were passed to a function.
105    TooManyArguments,
106    /// A expected argument was missing
107    MissingArgument,
108    /// A filter is unknown
109    UnknownFilter,
110    /// A test is unknown
111    UnknownTest,
112    /// A function is unknown
113    UnknownFunction,
114    /// Un unknown method was called
115    UnknownMethod,
116    /// A bad escape sequence in a string was encountered.
117    BadEscape,
118    /// An operation on an undefined value was attempted.
119    UndefinedError,
120    /// Not able to serialize this value.
121    BadSerialization,
122    /// Not able to deserialize this value.
123    #[cfg(feature = "deserialization")]
124    CannotDeserialize,
125    /// An error happened in an include.
126    BadInclude,
127    /// An error happened in a super block.
128    EvalBlock,
129    /// Unable to unpack a value.
130    CannotUnpack,
131    /// Failed writing output.
132    WriteFailure,
133    /// Engine ran out of fuel
134    #[cfg(feature = "fuel")]
135    OutOfFuel,
136    #[cfg(feature = "custom_syntax")]
137    /// Error creating aho-corasick delimiters
138    InvalidDelimiter,
139    /// An unknown block was called
140    #[cfg(feature = "multi_template")]
141    UnknownBlock,
142}
143
144impl ErrorKind {
145    fn description(self) -> &'static str {
146        match self {
147            ErrorKind::NonPrimitive => "not a primitive",
148            ErrorKind::NonKey => "not a key type",
149            ErrorKind::InvalidOperation => "invalid operation",
150            ErrorKind::SyntaxError => "syntax error",
151            ErrorKind::TemplateNotFound => "template not found",
152            ErrorKind::TooManyArguments => "too many arguments",
153            ErrorKind::MissingArgument => "missing argument",
154            ErrorKind::UnknownFilter => "unknown filter",
155            ErrorKind::UnknownFunction => "unknown function",
156            ErrorKind::UnknownTest => "unknown test",
157            ErrorKind::UnknownMethod => "unknown method",
158            ErrorKind::BadEscape => "bad string escape",
159            ErrorKind::UndefinedError => "undefined value",
160            ErrorKind::BadSerialization => "could not serialize to value",
161            ErrorKind::BadInclude => "could not render include",
162            ErrorKind::EvalBlock => "could not render block",
163            ErrorKind::CannotUnpack => "cannot unpack",
164            ErrorKind::WriteFailure => "failed to write output",
165            #[cfg(feature = "deserialization")]
166            ErrorKind::CannotDeserialize => "cannot deserialize",
167            #[cfg(feature = "fuel")]
168            ErrorKind::OutOfFuel => "engine ran out of fuel",
169            #[cfg(feature = "custom_syntax")]
170            ErrorKind::InvalidDelimiter => "invalid custom delimiters",
171            #[cfg(feature = "multi_template")]
172            ErrorKind::UnknownBlock => "unknown block",
173        }
174    }
175}
176
177impl fmt::Display for ErrorKind {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        write!(f, "{}", self.description())
180    }
181}
182
183impl fmt::Display for Error {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        if let Some(ref detail) = self.repr.detail {
186            ok!(write!(f, "{}: {}", self.kind(), detail));
187        } else {
188            ok!(write!(f, "{}", self.kind()));
189        }
190        if let Some(ref filename) = self.name() {
191            ok!(write!(f, " (in {}:{})", filename, self.line().unwrap_or(0)))
192        }
193        #[cfg(feature = "debug")]
194        {
195            if f.alternate() && self.debug_info().is_some() {
196                ok!(write!(f, "{}", self.display_debug_info()));
197            }
198        }
199        Ok(())
200    }
201}
202
203impl Error {
204    /// Creates a new error with kind and detail.
205    pub fn new<D: Into<Cow<'static, str>>>(kind: ErrorKind, detail: D) -> Error {
206        Error {
207            repr: Box::new(ErrorRepr {
208                kind,
209                detail: Some(detail.into()),
210                name: None,
211                lineno: 0,
212                span: None,
213                source: None,
214                #[cfg(feature = "debug")]
215                debug_info: None,
216            }),
217        }
218    }
219
220    pub(crate) fn internal_clone(&self) -> Error {
221        Error {
222            repr: self.repr.clone(),
223        }
224    }
225
226    pub(crate) fn set_filename_and_line(&mut self, filename: &str, lineno: usize) {
227        self.repr.name = Some(filename.into());
228        self.repr.lineno = lineno;
229    }
230
231    pub(crate) fn set_filename_and_span(&mut self, filename: &str, span: Span) {
232        self.repr.name = Some(filename.into());
233        self.repr.span = Some(span);
234        self.repr.lineno = span.start_line as usize;
235    }
236
237    pub(crate) fn new_not_found(name: &str) -> Error {
238        Error::new(
239            ErrorKind::TemplateNotFound,
240            format!("template {name:?} does not exist"),
241        )
242    }
243
244    /// Attaches another error as source to this error.
245    pub fn with_source<E: std::error::Error + Send + Sync + 'static>(mut self, source: E) -> Self {
246        self.repr.source = Some(Arc::new(source));
247        self
248    }
249
250    /// Returns the error kind
251    pub fn kind(&self) -> ErrorKind {
252        self.repr.kind
253    }
254
255    /// Returns the error detail
256    ///
257    /// The detail is an error message that provides further details about
258    /// the error kind.
259    pub fn detail(&self) -> Option<&str> {
260        self.repr.detail.as_deref()
261    }
262
263    /// Overrides the detail.
264    pub(crate) fn set_detail<D: Into<Cow<'static, str>>>(&mut self, d: D) {
265        self.repr.detail = Some(d.into());
266    }
267
268    /// Returns the filename of the template that caused the error.
269    pub fn name(&self) -> Option<&str> {
270        self.repr.name.as_deref()
271    }
272
273    /// Returns the line number where the error occurred.
274    pub fn line(&self) -> Option<usize> {
275        if self.repr.lineno > 0 {
276            Some(self.repr.lineno)
277        } else {
278            None
279        }
280    }
281
282    /// Returns the byte range of where the error occurred if available.
283    ///
284    /// In combination with [`template_source`](Self::template_source) this can be
285    /// used to better visualize where the error is coming from.  By indexing into
286    /// the template source one ends up with the source of the failing expression.
287    ///
288    /// Note that debug mode ([`Environment::set_debug`](crate::Environment::set_debug))
289    /// needs to be enabled, and the `debug` feature must be turned on.  The engine
290    /// usually keeps track of spans in all cases, but there is no absolute guarantee
291    /// that it is able to provide a range in all error cases.
292    ///
293    /// ```
294    /// # use minijinja::{Error, Environment, context};
295    /// # let mut env = Environment::new();
296    /// # env.set_debug(true);
297    /// let tmpl = env.template_from_str("Hello {{ foo + bar }}!").unwrap();
298    /// let err = tmpl.render(context!(foo => "a string", bar => 0)).unwrap_err();
299    /// let src = err.template_source().unwrap();
300    /// assert_eq!(&src[err.range().unwrap()], "foo + bar");
301    /// ```
302    #[cfg(feature = "debug")]
303    #[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
304    pub fn range(&self) -> Option<std::ops::Range<usize>> {
305        self.repr
306            .span
307            .map(|x| x.start_offset as usize..x.end_offset as usize)
308    }
309
310    /// Helper function that renders all known debug info on format.
311    ///
312    /// This method returns an object that when formatted prints out the debug information
313    /// that is contained on that error.  Normally this is automatically rendered when the
314    /// error is displayed but in some cases you might want to decide for yourself when and
315    /// how to display that information.
316    #[cfg(feature = "debug")]
317    #[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
318    pub fn display_debug_info(&self) -> impl fmt::Display + '_ {
319        struct Proxy<'a>(&'a Error);
320
321        impl fmt::Display for Proxy<'_> {
322            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323                if let Some(info) = self.0.debug_info() {
324                    crate::debug::render_debug_info(
325                        f,
326                        self.0.name(),
327                        self.0.kind(),
328                        self.0.line(),
329                        self.0.span(),
330                        info,
331                    )
332                } else {
333                    Ok(())
334                }
335            }
336        }
337
338        Proxy(self)
339    }
340
341    /// Returns the template source if available.
342    #[cfg(feature = "debug")]
343    #[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
344    pub fn template_source(&self) -> Option<&str> {
345        self.debug_info().and_then(|x| x.source())
346    }
347
348    /// Returns the line number where the error occurred.
349    #[cfg(feature = "debug")]
350    pub(crate) fn span(&self) -> Option<Span> {
351        self.repr.span
352    }
353
354    /// Returns the template debug information is available.
355    ///
356    /// The debug info snapshot is only embedded into the error if the debug
357    /// mode is enabled on the environment
358    /// ([`Environment::set_debug`](crate::Environment::set_debug)).
359    #[cfg(feature = "debug")]
360    pub(crate) fn debug_info(&self) -> Option<&crate::debug::DebugInfo> {
361        self.repr.debug_info.as_deref()
362    }
363
364    #[cfg(feature = "debug")]
365    #[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
366    pub(crate) fn attach_debug_info(&mut self, value: crate::debug::DebugInfo) {
367        self.repr.debug_info = Some(Arc::new(value));
368    }
369}
370
371impl std::error::Error for Error {
372    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
373        self.repr.source.as_ref().map(|err| err.as_ref() as _)
374    }
375}
376
377impl From<ErrorKind> for Error {
378    fn from(kind: ErrorKind) -> Self {
379        Error {
380            repr: Box::new(ErrorRepr {
381                kind,
382                detail: None,
383                name: None,
384                lineno: 0,
385                span: None,
386                source: None,
387                #[cfg(feature = "debug")]
388                debug_info: None,
389            }),
390        }
391    }
392}
393
394impl From<fmt::Error> for Error {
395    fn from(_: fmt::Error) -> Self {
396        Error::new(ErrorKind::WriteFailure, "formatting failed")
397    }
398}
399
400pub fn attach_basic_debug_info<T>(rv: Result<T, Error>, source: &str) -> Result<T, Error> {
401    #[cfg(feature = "debug")]
402    {
403        match rv {
404            Ok(rv) => Ok(rv),
405            Err(mut err) => {
406                err.repr.debug_info = Some(Arc::new(crate::debug::DebugInfo {
407                    template_source: Some(source.to_string()),
408                    ..Default::default()
409                }));
410                Err(err)
411            }
412        }
413    }
414    #[cfg(not(feature = "debug"))]
415    {
416        let _source = source;
417        rv
418    }
419}