yolk/templating/
error.rs

1#![allow(deprecated)]
2use std::sync::Arc;
3
4use miette::{Diagnostic, NamedSource, Severity, SourceSpan};
5use winnow::{
6    error::{AddContext, ErrorKind, FromRecoverableError, ParserError},
7    stream::{Location, Stream},
8};
9
10use crate::script::rhai_error::RhaiError;
11
12#[derive(Debug, thiserror::Error, miette::Diagnostic)]
13pub enum TemplateError {
14    #[error("Error evaluating rhai")]
15    Rhai {
16        #[source]
17        error: RhaiError,
18        #[label(primary, "here")]
19        error_span: Option<SourceSpan>,
20    },
21    #[error("Failed to evaluate template")]
22    Multiple(#[related] Vec<TemplateError>),
23}
24
25impl TemplateError {
26    pub fn from_rhai(error: RhaiError, expr_span: impl Into<SourceSpan>) -> Self {
27        match error {
28            RhaiError::SourceError { ref span, .. } => {
29                let expr_span = expr_span.into();
30                let start = expr_span.offset() + span.start;
31                let end = expr_span.offset() + span.end;
32                Self::Rhai {
33                    error,
34                    error_span: Some((start..end).into()),
35                }
36            }
37            error => Self::Rhai {
38                error,
39                error_span: None,
40            },
41        }
42    }
43
44    /// Convert this error into a [`miette::Report`] with the given name and source code attached.
45    pub fn into_report(self, name: impl ToString, source: impl ToString) -> miette::Report {
46        miette::Report::from(self)
47            .with_source_code(NamedSource::new(name.to_string(), source.to_string()))
48    }
49}
50
51#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, thiserror::Error)]
52#[error("Failed to parse yolk template file")]
53pub struct YolkParseFailure {
54    #[source_code]
55    pub input: Arc<miette::NamedSource<String>>,
56    #[related]
57    pub diagnostics: Vec<YolkParseDiagnostic>,
58}
59
60impl YolkParseFailure {
61    pub fn from_errs(errs: Vec<YolkParseError>, input: &str) -> YolkParseFailure {
62        let src = Arc::new(NamedSource::new("file", input.to_string()));
63        YolkParseFailure {
64            input: src.clone(),
65            diagnostics: errs
66                .into_iter()
67                .map(|e| YolkParseDiagnostic {
68                    message: e.message,
69                    input: src.clone(),
70                    span: e.span.unwrap_or_else(|| (0usize..0usize).into()),
71                    label: e.label,
72                    help: e.help,
73                    severity: Severity::Error,
74                })
75                .collect(),
76        }
77    }
78}
79
80#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, thiserror::Error)]
81#[error("{}", message.unwrap_or_else(|| "An unspecified parse error occurred."))]
82pub struct YolkParseDiagnostic {
83    #[source_code]
84    pub input: Arc<NamedSource<String>>,
85
86    /// Offset in chars of the error.
87    #[label("{}", label.unwrap_or_else(|| "here"))]
88    pub span: SourceSpan,
89
90    /// Message
91    pub message: Option<&'static str>,
92
93    /// Label text for this span. Defaults to `"here"`.
94    pub label: Option<&'static str>,
95
96    /// Suggestion for fixing the parser error.
97    #[help]
98    pub help: Option<&'static str>,
99
100    /// Severity level for the Diagnostic.
101    #[diagnostic(severity)]
102    pub severity: miette::Severity,
103}
104
105#[derive(Debug, Clone, Eq, PartialEq)]
106pub struct YolkParseError {
107    pub message: Option<&'static str>,
108    pub span: Option<SourceSpan>,
109    pub label: Option<&'static str>,
110    pub help: Option<&'static str>,
111}
112
113impl<I: Stream> ParserError<I> for YolkParseError {
114    fn from_error_kind(_input: &I, _kind: ErrorKind) -> Self {
115        Self {
116            span: None,
117            label: None,
118            help: None,
119            message: None,
120        }
121    }
122
123    fn append(
124        self,
125        _input: &I,
126        _token_start: &<I as Stream>::Checkpoint,
127        _kind: ErrorKind,
128    ) -> Self {
129        self
130    }
131}
132
133impl<I: Stream> AddContext<I, YolkParseContext> for YolkParseError {
134    fn add_context(
135        mut self,
136        _input: &I,
137        _token_start: &<I as Stream>::Checkpoint,
138        ctx: YolkParseContext,
139    ) -> Self {
140        self.message = ctx.message.or(self.message);
141        self.label = ctx.label.or(self.label);
142        self.help = ctx.help.or(self.help);
143        self
144    }
145}
146
147impl<I: Stream + Location> FromRecoverableError<I, Self> for YolkParseError {
148    #[inline]
149    fn from_recoverable_error(
150        token_start: &<I as Stream>::Checkpoint,
151        _err_start: &<I as Stream>::Checkpoint,
152        input: &I,
153        mut e: Self,
154    ) -> Self {
155        e.span = e
156            .span
157            .or_else(|| Some(span_from_checkpoint(input, token_start)));
158        e
159    }
160}
161
162impl<I: Stream + Location> FromRecoverableError<I, YolkParseContext> for YolkParseError {
163    #[inline]
164    fn from_recoverable_error(
165        token_start: &<I as Stream>::Checkpoint,
166        _err_start: &<I as Stream>::Checkpoint,
167        input: &I,
168        e: YolkParseContext,
169    ) -> Self {
170        YolkParseError {
171            span: Some((input.offset_from(token_start).saturating_sub(1)..input.location()).into()),
172            label: e.label,
173            help: e.help,
174            message: e.message,
175        }
176    }
177}
178
179#[derive(Debug, Clone, Default, Eq, PartialEq)]
180pub(super) struct YolkParseContext {
181    message: Option<&'static str>,
182    label: Option<&'static str>,
183    help: Option<&'static str>,
184}
185
186impl YolkParseContext {
187    pub(super) fn msg(mut self, txt: &'static str) -> Self {
188        self.message = Some(txt);
189        self
190    }
191
192    pub(super) fn lbl(mut self, txt: &'static str) -> Self {
193        self.label = Some(txt);
194        self
195    }
196
197    pub(super) fn hlp(mut self, txt: &'static str) -> Self {
198        self.help = Some(txt);
199        self
200    }
201}
202
203pub(super) fn cx() -> YolkParseContext {
204    Default::default()
205}
206
207fn span_from_checkpoint<I: Stream + Location>(
208    input: &I,
209    start: &<I as Stream>::Checkpoint,
210) -> SourceSpan {
211    let offset = input.offset_from(start);
212    ((input.location() - offset)..input.location()).into()
213}