Skip to main content

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}