Skip to main content

template/
error.rs

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