Skip to main content

template/
error.rs

1//! Error types for parse, syntax, render, and type errors with source positions.
2
3#[cfg(any(feature = "std", test))]
4use alloc::string::ToString;
5use alloc::{boxed::Box, string::String};
6use core::fmt;
7
8#[cfg(feature = "std")]
9use serde::ser;
10
11/// A source position (line and column) in a template.
12#[derive(Clone, Debug, PartialEq)]
13pub struct SourcePosition {
14    pub line: usize,
15    pub column: usize,
16}
17
18impl SourcePosition {
19    pub fn new(line: usize, column: usize) -> Self {
20        Self {
21            line,
22            column,
23        }
24    }
25}
26
27#[derive(Debug)]
28enum ErrorKind {
29    Parse {
30        message: String,
31    },
32    Syntax {
33        message: String,
34        position: SourcePosition,
35    },
36    UndefinedVariable {
37        name: String,
38        position: SourcePosition,
39    },
40    UndefinedFilter {
41        name: String,
42        position: SourcePosition,
43    },
44    UndefinedTemplate {
45        name: String,
46    },
47    Render {
48        message: String,
49    },
50    Type {
51        message: String,
52    },
53    #[cfg(feature = "std")]
54    Io(std::io::Error),
55}
56
57/// An error returned by the template engine.
58#[derive(Debug)]
59pub struct Error {
60    inner: Box<ErrorInner>,
61}
62
63#[derive(Debug)]
64struct ErrorInner {
65    kind: ErrorKind,
66    source: Option<String>,
67}
68
69impl Error {
70    fn new(kind: ErrorKind) -> Self {
71        Self {
72            inner: Box::new(ErrorInner {
73                kind,
74                source: None,
75            }),
76        }
77    }
78
79    /// Create a generic parse error.
80    pub fn parse(message: impl Into<String>) -> Self {
81        Self::new(ErrorKind::Parse {
82            message: message.into(),
83        })
84    }
85
86    /// Create a syntax error at a specific position.
87    pub fn syntax(message: impl Into<String>, line: usize, column: usize) -> Self {
88        Self::new(ErrorKind::Syntax {
89            message: message.into(),
90            position: SourcePosition::new(line, column),
91        })
92    }
93
94    /// Create an error for an undefined variable.
95    pub fn undefined_variable(name: impl Into<String>, line: usize, column: usize) -> Self {
96        Self::new(ErrorKind::UndefinedVariable {
97            name: name.into(),
98            position: SourcePosition::new(line, column),
99        })
100    }
101
102    /// Create an error for an undefined filter.
103    pub fn undefined_filter(name: impl Into<String>, line: usize, column: usize) -> Self {
104        Self::new(ErrorKind::UndefinedFilter {
105            name: name.into(),
106            position: SourcePosition::new(line, column),
107        })
108    }
109
110    /// Create an error for an undefined template name.
111    pub fn undefined_template(name: impl Into<String>) -> Self {
112        Self::new(ErrorKind::UndefinedTemplate {
113            name: name.into(),
114        })
115    }
116
117    /// Create a generic render error.
118    pub fn render(message: impl Into<String>) -> Self {
119        Self::new(ErrorKind::Render {
120            message: message.into(),
121        })
122    }
123
124    /// Create a type error.
125    pub fn r#type(message: impl Into<String>) -> Self {
126        Self::new(ErrorKind::Type {
127            message: message.into(),
128        })
129    }
130
131    /// Attach source information to the error.
132    pub fn with_source(mut self, source: impl Into<String>) -> Self {
133        self.inner.source = Some(source.into());
134        self
135    }
136}
137
138#[cfg(feature = "std")]
139impl From<std::io::Error> for Error {
140    fn from(err: std::io::Error) -> Self {
141        Self::new(ErrorKind::Io(err))
142    }
143}
144
145impl fmt::Display for Error {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        match &self.inner.kind {
148            ErrorKind::Parse {
149                message,
150            } => {
151                write!(f, "parse error: {message}")
152            }
153            ErrorKind::Syntax {
154                message,
155                position,
156            } => {
157                write!(f, "syntax error at {}:{}: {message}", position.line, position.column)
158            }
159            ErrorKind::UndefinedVariable {
160                name,
161                position,
162            } => {
163                write!(f, "undefined variable `{name}` at {}:{}", position.line, position.column)
164            }
165            ErrorKind::UndefinedFilter {
166                name,
167                position,
168            } => {
169                write!(f, "undefined filter `{name}` at {}:{}", position.line, position.column)
170            }
171            ErrorKind::UndefinedTemplate {
172                name,
173            } => {
174                write!(f, "undefined template `{name}`")
175            }
176            ErrorKind::Render {
177                message,
178            } => {
179                write!(f, "render error: {message}")
180            }
181            ErrorKind::Type {
182                message,
183            } => {
184                write!(f, "type error: {message}")
185            }
186            #[cfg(feature = "std")]
187            ErrorKind::Io(err) => {
188                write!(f, "I/O error: {err}")
189            }
190        }
191    }
192}
193
194#[cfg(feature = "std")]
195impl std::error::Error for Error {
196    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
197        match &self.inner.kind {
198            ErrorKind::Io(err) => Some(err),
199            _ => None,
200        }
201    }
202}
203
204/// Error returned when serializing a value fails.
205#[derive(Debug)]
206pub struct SerdeError(pub String);
207
208impl fmt::Display for SerdeError {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        write!(f, "{}", self.0)
211    }
212}
213
214#[cfg(feature = "std")]
215impl std::error::Error for SerdeError {}
216
217#[cfg(feature = "std")]
218impl ser::Error for SerdeError {
219    fn custom<T: fmt::Display>(msg: T) -> Self {
220        SerdeError(msg.to_string())
221    }
222}