loose_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
19impl Error {
20    /// Identifies the underlying kind for this error
21    pub fn kind(&self) -> &ErrorKind {
22        &self.inner.kind
23    }
24}
25
26// Guts of `Error` here to keep `Error`'s memory size small to avoid bloating the size of
27// `Result<T>` in the success case and spilling over from register-based returns to stack-based
28// returns.  There are already enough memory allocations below, one more
29// shouldn't hurt.
30#[derive(Debug, Clone)]
31struct InnerError {
32    kind: ErrorKind,
33    user_backtrace: Vec<Trace>,
34    cause: Option<BoxedError>,
35}
36
37impl Error {
38    /// Create an error that identifies the provided variable as unknown
39    pub fn unknown_variable<S: Into<crate::model::KString>>(name: S) -> Self {
40        Self::with_kind(ErrorKind::UnknownVariable).context("requested variable", name)
41    }
42
43    /// Create a new error of the given kind
44    pub fn with_kind(kind: ErrorKind) -> Self {
45        let error = InnerError {
46            kind,
47            user_backtrace: vec![Trace::empty()],
48            cause: None,
49        };
50        Self {
51            inner: Box::new(error),
52        }
53    }
54
55    /// Create a new custom error with the given message
56    pub fn with_msg<S: Into<crate::model::KString>>(msg: S) -> Self {
57        Self::with_msg_cow(msg.into())
58    }
59
60    fn with_msg_cow(msg: crate::model::KString) -> Self {
61        let error = InnerError {
62            kind: ErrorKind::Custom(msg),
63            user_backtrace: vec![Trace::empty()],
64            cause: None,
65        };
66        Self {
67            inner: Box::new(error),
68        }
69    }
70
71    /// Add a new call to the user-visible backtrace
72    pub fn trace<T>(self, trace: T) -> Self
73    where
74        T: Into<crate::model::KString>,
75    {
76        self.trace_trace(trace.into())
77    }
78
79    fn trace_trace(mut self, trace: crate::model::KString) -> Self {
80        let trace = Trace::new(trace);
81        self.inner.user_backtrace.push(trace);
82        self
83    }
84
85    /// Add context to the last traced call.
86    ///
87    /// Example context: Value that parameters from the `trace` evaluate to.
88    pub fn context<K, V>(self, key: K, value: V) -> Self
89    where
90        K: Into<crate::model::KString>,
91        V: Into<crate::model::KString>,
92    {
93        self.context_cow_string(key.into(), value.into())
94    }
95
96    fn context_cow_string(
97        mut self,
98        key: crate::model::KString,
99        value: crate::model::KString,
100    ) -> Self {
101        self.inner
102            .user_backtrace
103            .last_mut()
104            .expect("always a trace available")
105            .append_context(key, value);
106        self
107    }
108
109    /// Add an external cause to the error for debugging purposes.
110    pub fn cause<E: ErrorClone>(self, cause: E) -> Self {
111        let cause = Box::new(cause);
112        self.cause_error(cause)
113    }
114
115    fn cause_error(mut self, cause: BoxedError) -> Self {
116        let cause = Some(cause);
117        self.inner.cause = cause;
118        self
119    }
120
121    /// Simplify returning early with an error.
122    pub fn into_err<T, E>(self) -> ::std::result::Result<T, E>
123    where
124        Self: Into<E>,
125    {
126        let err = self.into();
127        Err(err)
128    }
129}
130
131const ERROR_DESCRIPTION: &str = "liquid";
132
133impl fmt::Display for Error {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        writeln!(f, "{}: {}", ERROR_DESCRIPTION, self.inner.kind)?;
136        for trace in &self.inner.user_backtrace {
137            if let Some(trace) = trace.get_trace() {
138                writeln!(f, "from: {}", trace)?;
139            }
140            if !trace.get_context().is_empty() {
141                writeln!(f, "  with:")?;
142            }
143            for (key, value) in trace.get_context() {
144                writeln!(f, "    {}={}", key, value)?;
145            }
146        }
147        Ok(())
148    }
149}
150
151impl error::Error for Error {
152    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
153        self.inner.cause.as_ref().and_then(|e| e.source())
154    }
155}
156
157/// The type of an error.
158#[derive(Debug, Clone)]
159pub enum ErrorKind {
160    /// A variable was being indexed but the desired index did not exist
161    UnknownIndex,
162    /// A referenced variable did not exist
163    UnknownVariable,
164    /// A custom error with no discernible kind
165    Custom(crate::model::KString),
166}
167
168impl std::fmt::Display for ErrorKind {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        match self {
171            ErrorKind::UnknownIndex => f.write_str("Unknown index"),
172            ErrorKind::UnknownVariable => f.write_str("Unknown variable"),
173            ErrorKind::Custom(s) => s.fmt(f),
174        }
175    }
176}