liquid_core/error/
error.rs

1use std::error;
2use std::fmt;
3use std::result;
4
5use super::ErrorClone;
6use super::Trace;
7
8/// Convenience type alias for Liquid compiler errors
9pub type Result<T, E = Error> = result::Result<T, E>;
10
11type BoxedError = Box<dyn ErrorClone>;
12
13/// Compiler error
14#[derive(Debug, Clone)]
15pub struct Error {
16    inner: Box<InnerError>,
17}
18
19// Guts of `Error` here to keep `Error`'s memory size small to avoid bloating the size of
20// `Result<T>` in the success case and spilling over from register-based returns to stack-based
21// returns.  There are already enough memory allocations below, one more
22// shouldn't hurt.
23#[derive(Debug, Clone)]
24struct InnerError {
25    msg: crate::model::KString,
26    user_backtrace: Vec<Trace>,
27    cause: Option<BoxedError>,
28}
29
30impl Error {
31    /// Create a new compiler error with the given message
32    pub fn with_msg<S: Into<crate::model::KString>>(msg: S) -> Self {
33        Self::with_msg_cow(msg.into())
34    }
35
36    fn with_msg_cow(msg: crate::model::KString) -> Self {
37        let error = InnerError {
38            msg,
39            user_backtrace: vec![Trace::empty()],
40            cause: None,
41        };
42        Self {
43            inner: Box::new(error),
44        }
45    }
46
47    /// Add a new call to the user-visible backtrace
48    pub fn trace<T>(self, trace: T) -> Self
49    where
50        T: Into<crate::model::KString>,
51    {
52        self.trace_trace(trace.into())
53    }
54
55    fn trace_trace(mut self, trace: crate::model::KString) -> Self {
56        let trace = Trace::new(trace);
57        self.inner.user_backtrace.push(trace);
58        self
59    }
60
61    /// Add context to the last traced call.
62    ///
63    /// Example context: Value that parameters from the `trace` evaluate to.
64    pub fn context<K, V>(self, key: K, value: V) -> Self
65    where
66        K: Into<crate::model::KString>,
67        V: Into<crate::model::KString>,
68    {
69        self.context_cow_string(key.into(), value.into())
70    }
71
72    fn context_cow_string(
73        mut self,
74        key: crate::model::KString,
75        value: crate::model::KString,
76    ) -> Self {
77        self.inner
78            .user_backtrace
79            .last_mut()
80            .expect("always a trace available")
81            .append_context(key, value);
82        self
83    }
84
85    /// Add an external cause to the error for debugging purposes.
86    pub fn cause<E: ErrorClone>(self, cause: E) -> Self {
87        let cause = Box::new(cause);
88        self.cause_error(cause)
89    }
90
91    fn cause_error(mut self, cause: BoxedError) -> Self {
92        let cause = Some(cause);
93        self.inner.cause = cause;
94        self
95    }
96
97    /// Simplify returning early with an error.
98    pub fn into_err<T, E>(self) -> ::std::result::Result<T, E>
99    where
100        Self: Into<E>,
101    {
102        let err = self.into();
103        Err(err)
104    }
105}
106
107const ERROR_DESCRIPTION: &str = "liquid";
108
109impl fmt::Display for Error {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        writeln!(f, "{}: {}", ERROR_DESCRIPTION, self.inner.msg)?;
112        for trace in &self.inner.user_backtrace {
113            if let Some(trace) = trace.get_trace() {
114                writeln!(f, "from: {}", trace)?;
115            }
116            if !trace.get_context().is_empty() {
117                writeln!(f, "  with:")?;
118            }
119            for (key, value) in trace.get_context() {
120                writeln!(f, "    {}={}", key, value)?;
121            }
122        }
123        Ok(())
124    }
125}
126
127impl error::Error for Error {
128    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
129        self.inner.cause.as_ref().and_then(|e| e.source())
130    }
131}