somni_template/error.rs
1//! Template errors.
2//!
3//! All errors carry a [`Location`] into the **original template** source (or the compile-time
4//! source arena when includes are involved), regardless of whether they originated while
5//! scanning/parsing the template or while evaluating the generated Somni program (in the
6//! latter case the location is translated back through the [source map](crate::transpile)).
7
8use std::fmt::{Debug, Display};
9
10use somni_expr::error::MarkInSource;
11use somni_parser::Location;
12
13/// An error produced while compiling or rendering a template.
14///
15/// The [`location`](TemplateError::location) always refers to the original template source
16/// (or the shared main+includes arena).
17#[derive(Clone, PartialEq, Eq)]
18pub struct TemplateError {
19 /// A human-readable description of the error.
20 pub message: Box<str>,
21 /// The location in the original template source where the error occurred.
22 pub location: Location,
23 /// When set, [`display_with`](Self::display_with) prefers this over the caller-provided
24 /// source — used for locations that fall in included template bodies.
25 diagnostic_source: Option<Box<str>>,
26}
27
28impl TemplateError {
29 /// Creates a new error at the given template location.
30 pub fn new(message: impl Into<Box<str>>, location: Location) -> Self {
31 Self {
32 message: message.into(),
33 location,
34 diagnostic_source: None,
35 }
36 }
37
38 /// Attaches a diagnostic source used by [`display_with`](Self::display_with).
39 pub(crate) fn with_diagnostic_source(mut self, source: impl Into<Box<str>>) -> Self {
40 self.diagnostic_source = Some(source.into());
41 self
42 }
43
44 /// Renders the error as a caret-marked snippet against the given template source.
45 ///
46 /// If this error carries an attached diagnostic source (e.g. the compile-time arena that
47 /// includes loaded bodies), that source is used instead of `template`.
48 pub fn display_with<'s>(&'s self, template: &'s str) -> impl Display + 's {
49 MarkedTemplateError {
50 error: self,
51 template: self.diagnostic_source.as_deref().unwrap_or(template),
52 }
53 }
54}
55
56impl Display for TemplateError {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 write!(f, "Template error: {}", self.message)
59 }
60}
61
62impl Debug for TemplateError {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 write!(
65 f,
66 "TemplateError {{ message: {:?}, location: {:?} }}",
67 self.message, self.location
68 )
69 }
70}
71
72/// A [`TemplateError`] paired with the template source, for caret-marked rendering.
73struct MarkedTemplateError<'s> {
74 error: &'s TemplateError,
75 template: &'s str,
76}
77
78impl Display for MarkedTemplateError<'_> {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 MarkInSource(
81 self.template,
82 self.error.location,
83 "Template error",
84 &self.error.message,
85 )
86 .fmt(f)
87 }
88}