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 { message: String },
30    Syntax { message: String, position: SourcePosition },
31    UndefinedVariable { name: String, position: SourcePosition },
32    UndefinedFilter { name: String, position: SourcePosition },
33    UndefinedTemplate { name: String },
34    Render { message: String },
35    Type { message: String },
36}
37
38/// An error returned by the template engine.
39#[derive(Debug)]
40pub struct Error {
41    inner: Box<ErrorInner>,
42}
43
44#[derive(Debug)]
45struct ErrorInner {
46    kind: ErrorKind,
47    source: Option<String>,
48}
49
50impl Error {
51    fn new(kind: ErrorKind) -> Self {
52        Self {
53            inner: Box::new(ErrorInner {
54                kind,
55                source: None,
56            }),
57        }
58    }
59
60    /// Create a generic parse error.
61    pub fn parse(message: impl Into<String>) -> Self {
62        Self::new(ErrorKind::Parse {
63            message: message.into(),
64        })
65    }
66
67    /// Create a syntax error at a specific position.
68    pub fn syntax(message: impl Into<String>, line: usize, column: usize) -> Self {
69        Self::new(ErrorKind::Syntax {
70            message: message.into(),
71            position: SourcePosition::new(line, column),
72        })
73    }
74
75    /// Create an error for an undefined variable.
76    pub fn undefined_variable(name: impl Into<String>, line: usize, column: usize) -> Self {
77        Self::new(ErrorKind::UndefinedVariable {
78            name: name.into(),
79            position: SourcePosition::new(line, column),
80        })
81    }
82
83    /// Create an error for an undefined filter.
84    pub fn undefined_filter(name: impl Into<String>, line: usize, column: usize) -> Self {
85        Self::new(ErrorKind::UndefinedFilter {
86            name: name.into(),
87            position: SourcePosition::new(line, column),
88        })
89    }
90
91    /// Create an error for an undefined template name.
92    pub fn undefined_template(name: impl Into<String>) -> Self {
93        Self::new(ErrorKind::UndefinedTemplate {
94            name: name.into(),
95        })
96    }
97
98    /// Create a generic render error.
99    pub fn render(message: impl Into<String>) -> Self {
100        Self::new(ErrorKind::Render {
101            message: message.into(),
102        })
103    }
104
105    /// Create a type error.
106    pub fn r#type(message: impl Into<String>) -> Self {
107        Self::new(ErrorKind::Type {
108            message: message.into(),
109        })
110    }
111
112    /// Attach source information to the error.
113    pub fn with_source(mut self, source: impl Into<String>) -> Self {
114        self.inner.source = Some(source.into());
115        self
116    }
117}
118
119#[cfg(feature = "std")]
120impl From<std::io::Error> for Error {
121    fn from(err: std::io::Error) -> Self {
122        Self::new(ErrorKind::Render {
123            message: err.to_string(),
124        })
125    }
126}
127
128impl fmt::Display for Error {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match &self.inner.kind {
131            ErrorKind::Parse {
132                message,
133            } => {
134                write!(f, "parse error: {message}")
135            }
136            ErrorKind::Syntax {
137                message,
138                position,
139            } => {
140                write!(f, "syntax error at {}:{}: {message}", position.line, position.column)
141            }
142            ErrorKind::UndefinedVariable {
143                name,
144                position,
145            } => {
146                write!(f, "undefined variable `{name}` at {}:{}", position.line, position.column)
147            }
148            ErrorKind::UndefinedFilter {
149                name,
150                position,
151            } => {
152                write!(f, "undefined filter `{name}` at {}:{}", position.line, position.column)
153            }
154            ErrorKind::UndefinedTemplate {
155                name,
156            } => {
157                write!(f, "undefined template `{name}`")
158            }
159            ErrorKind::Render {
160                message,
161            } => {
162                write!(f, "render error: {message}")
163            }
164            ErrorKind::Type {
165                message,
166            } => {
167                write!(f, "type error: {message}")
168            }
169        }
170    }
171}
172
173impl core::error::Error for Error {
174    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
175        None
176    }
177}
178
179/// Error returned when serializing a value fails.
180#[derive(Debug)]
181pub struct SerdeError(pub String);
182
183impl fmt::Display for SerdeError {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        write!(f, "{}", self.0)
186    }
187}
188
189impl core::error::Error for SerdeError {}
190
191#[cfg(feature = "std")]
192impl ser::Error for SerdeError {
193    fn custom<T: fmt::Display>(msg: T) -> Self {
194        SerdeError(msg.to_string())
195    }
196}