liquid_error/
error.rs

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