1use std::borrow::Cow;
2use std::fmt;
3use std::sync::Arc;
4
5use crate::compiler::tokens::Span;
6
7pub struct Error {
41 repr: Box<ErrorRepr>,
42}
43
44#[derive(Clone)]
46struct ErrorRepr {
47 kind: ErrorKind,
48 detail: Option<Cow<'static, str>>,
49 name: Option<String>,
50 lineno: usize,
51 span: Option<Span>,
52 source: Option<Arc<dyn std::error::Error + Send + Sync>>,
53 #[cfg(feature = "debug")]
54 debug_info: Option<Arc<crate::debug::DebugInfo>>,
55}
56
57impl fmt::Debug for Error {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 let mut err = f.debug_struct("Error");
60 err.field("kind", &self.kind());
61 if let Some(ref detail) = self.repr.detail {
62 err.field("detail", detail);
63 }
64 if let Some(ref name) = self.name() {
65 err.field("name", name);
66 }
67 if let Some(line) = self.line() {
68 err.field("line", &line);
69 }
70 if let Some(ref source) = std::error::Error::source(self) {
71 err.field("source", source);
72 }
73 ok!(err.finish());
74
75 #[cfg(feature = "debug")]
79 {
80 if !f.alternate() && self.debug_info().is_some() {
81 ok!(writeln!(f));
82 ok!(writeln!(f, "{}", self.display_debug_info()));
83 }
84 }
85
86 Ok(())
87 }
88}
89
90#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
92#[non_exhaustive]
93pub enum ErrorKind {
94 NonPrimitive,
96 NonKey,
98 InvalidOperation,
100 SyntaxError,
102 TemplateNotFound,
104 TooManyArguments,
106 MissingArgument,
108 UnknownFilter,
110 UnknownTest,
112 UnknownFunction,
114 UnknownMethod,
116 BadEscape,
118 UndefinedError,
120 BadSerialization,
122 #[cfg(feature = "deserialization")]
124 CannotDeserialize,
125 BadInclude,
127 EvalBlock,
129 CannotUnpack,
131 WriteFailure,
133 #[cfg(feature = "fuel")]
135 OutOfFuel,
136 #[cfg(feature = "custom_syntax")]
137 InvalidDelimiter,
139 #[cfg(feature = "multi_template")]
141 UnknownBlock,
142}
143
144impl ErrorKind {
145 fn description(self) -> &'static str {
146 match self {
147 ErrorKind::NonPrimitive => "not a primitive",
148 ErrorKind::NonKey => "not a key type",
149 ErrorKind::InvalidOperation => "invalid operation",
150 ErrorKind::SyntaxError => "syntax error",
151 ErrorKind::TemplateNotFound => "template not found",
152 ErrorKind::TooManyArguments => "too many arguments",
153 ErrorKind::MissingArgument => "missing argument",
154 ErrorKind::UnknownFilter => "unknown filter",
155 ErrorKind::UnknownFunction => "unknown function",
156 ErrorKind::UnknownTest => "unknown test",
157 ErrorKind::UnknownMethod => "unknown method",
158 ErrorKind::BadEscape => "bad string escape",
159 ErrorKind::UndefinedError => "undefined value",
160 ErrorKind::BadSerialization => "could not serialize to value",
161 ErrorKind::BadInclude => "could not render include",
162 ErrorKind::EvalBlock => "could not render block",
163 ErrorKind::CannotUnpack => "cannot unpack",
164 ErrorKind::WriteFailure => "failed to write output",
165 #[cfg(feature = "deserialization")]
166 ErrorKind::CannotDeserialize => "cannot deserialize",
167 #[cfg(feature = "fuel")]
168 ErrorKind::OutOfFuel => "engine ran out of fuel",
169 #[cfg(feature = "custom_syntax")]
170 ErrorKind::InvalidDelimiter => "invalid custom delimiters",
171 #[cfg(feature = "multi_template")]
172 ErrorKind::UnknownBlock => "unknown block",
173 }
174 }
175}
176
177impl fmt::Display for ErrorKind {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 write!(f, "{}", self.description())
180 }
181}
182
183impl fmt::Display for Error {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 if let Some(ref detail) = self.repr.detail {
186 ok!(write!(f, "{}: {}", self.kind(), detail));
187 } else {
188 ok!(write!(f, "{}", self.kind()));
189 }
190 if let Some(ref filename) = self.name() {
191 ok!(write!(f, " (in {}:{})", filename, self.line().unwrap_or(0)))
192 }
193 #[cfg(feature = "debug")]
194 {
195 if f.alternate() && self.debug_info().is_some() {
196 ok!(write!(f, "{}", self.display_debug_info()));
197 }
198 }
199 Ok(())
200 }
201}
202
203impl Error {
204 pub fn new<D: Into<Cow<'static, str>>>(kind: ErrorKind, detail: D) -> Error {
206 Error {
207 repr: Box::new(ErrorRepr {
208 kind,
209 detail: Some(detail.into()),
210 name: None,
211 lineno: 0,
212 span: None,
213 source: None,
214 #[cfg(feature = "debug")]
215 debug_info: None,
216 }),
217 }
218 }
219
220 pub(crate) fn internal_clone(&self) -> Error {
221 Error {
222 repr: self.repr.clone(),
223 }
224 }
225
226 pub(crate) fn set_filename_and_line(&mut self, filename: &str, lineno: usize) {
227 self.repr.name = Some(filename.into());
228 self.repr.lineno = lineno;
229 }
230
231 pub(crate) fn set_filename_and_span(&mut self, filename: &str, span: Span) {
232 self.repr.name = Some(filename.into());
233 self.repr.span = Some(span);
234 self.repr.lineno = span.start_line as usize;
235 }
236
237 pub(crate) fn new_not_found(name: &str) -> Error {
238 Error::new(
239 ErrorKind::TemplateNotFound,
240 format!("template {name:?} does not exist"),
241 )
242 }
243
244 pub fn with_source<E: std::error::Error + Send + Sync + 'static>(mut self, source: E) -> Self {
246 self.repr.source = Some(Arc::new(source));
247 self
248 }
249
250 pub fn kind(&self) -> ErrorKind {
252 self.repr.kind
253 }
254
255 pub fn detail(&self) -> Option<&str> {
260 self.repr.detail.as_deref()
261 }
262
263 pub(crate) fn set_detail<D: Into<Cow<'static, str>>>(&mut self, d: D) {
265 self.repr.detail = Some(d.into());
266 }
267
268 pub fn name(&self) -> Option<&str> {
270 self.repr.name.as_deref()
271 }
272
273 pub fn line(&self) -> Option<usize> {
275 if self.repr.lineno > 0 {
276 Some(self.repr.lineno)
277 } else {
278 None
279 }
280 }
281
282 #[cfg(feature = "debug")]
303 #[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
304 pub fn range(&self) -> Option<std::ops::Range<usize>> {
305 self.repr
306 .span
307 .map(|x| x.start_offset as usize..x.end_offset as usize)
308 }
309
310 #[cfg(feature = "debug")]
317 #[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
318 pub fn display_debug_info(&self) -> impl fmt::Display + '_ {
319 struct Proxy<'a>(&'a Error);
320
321 impl fmt::Display for Proxy<'_> {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 if let Some(info) = self.0.debug_info() {
324 crate::debug::render_debug_info(
325 f,
326 self.0.name(),
327 self.0.kind(),
328 self.0.line(),
329 self.0.span(),
330 info,
331 )
332 } else {
333 Ok(())
334 }
335 }
336 }
337
338 Proxy(self)
339 }
340
341 #[cfg(feature = "debug")]
343 #[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
344 pub fn template_source(&self) -> Option<&str> {
345 self.debug_info().and_then(|x| x.source())
346 }
347
348 #[cfg(feature = "debug")]
350 pub(crate) fn span(&self) -> Option<Span> {
351 self.repr.span
352 }
353
354 #[cfg(feature = "debug")]
360 pub(crate) fn debug_info(&self) -> Option<&crate::debug::DebugInfo> {
361 self.repr.debug_info.as_deref()
362 }
363
364 #[cfg(feature = "debug")]
365 #[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
366 pub(crate) fn attach_debug_info(&mut self, value: crate::debug::DebugInfo) {
367 self.repr.debug_info = Some(Arc::new(value));
368 }
369}
370
371impl std::error::Error for Error {
372 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
373 self.repr.source.as_ref().map(|err| err.as_ref() as _)
374 }
375}
376
377impl From<ErrorKind> for Error {
378 fn from(kind: ErrorKind) -> Self {
379 Error {
380 repr: Box::new(ErrorRepr {
381 kind,
382 detail: None,
383 name: None,
384 lineno: 0,
385 span: None,
386 source: None,
387 #[cfg(feature = "debug")]
388 debug_info: None,
389 }),
390 }
391 }
392}
393
394impl From<fmt::Error> for Error {
395 fn from(_: fmt::Error) -> Self {
396 Error::new(ErrorKind::WriteFailure, "formatting failed")
397 }
398}
399
400pub fn attach_basic_debug_info<T>(rv: Result<T, Error>, source: &str) -> Result<T, Error> {
401 #[cfg(feature = "debug")]
402 {
403 match rv {
404 Ok(rv) => Ok(rv),
405 Err(mut err) => {
406 err.repr.debug_info = Some(Arc::new(crate::debug::DebugInfo {
407 template_source: Some(source.to_string()),
408 ..Default::default()
409 }));
410 Err(err)
411 }
412 }
413 }
414 #[cfg(not(feature = "debug"))]
415 {
416 let _source = source;
417 rv
418 }
419}