1use std::{cell::RefCell, collections::HashSet};
2
3use ariadne::{Color, Label, Report, ReportKind, Source};
4use chumsky::span::SimpleSpan;
5
6use thiserror::Error as ThisError;
7
8use crate::Engine;
9
10#[derive(Debug, Default)]
11pub struct ErrorCollector {
12 errors: RefCell<Vec<Error>>,
13 seen_spans: RefCell<HashSet<SimpleSpan>>,
14}
15
16impl ErrorCollector {
17 pub fn new() -> Self {
18 Self {
19 errors: RefCell::new(Vec::new()),
20 seen_spans: RefCell::new(HashSet::new()),
21 }
22 }
23
24 pub fn add(&self, error: Error) {
25 let seen = match &error {
26 Error::TemplateNotFound { template: _, .. } => false,
27 Error::ParseError { kind: _, span, .. } => self.seen_spans.borrow().contains(span),
28 Error::ResolveError { span, .. } => self.seen_spans.borrow().contains(span),
29 Error::IncludeError { span, .. } => self.seen_spans.borrow().contains(span),
30 };
31 if !seen {
32 let span = error.get_span();
33 if span.is_some() {
34 self.seen_spans.borrow_mut().insert(span.unwrap());
35 }
36
37 self.errors.borrow_mut().push(error);
38 }
39 }
40
41 pub fn is_empty(&self) -> bool {
42 self.errors.borrow().is_empty()
43 }
44
45 pub fn into_inner(self) -> Vec<Error> {
46 self.errors.into_inner()
47 }
48
49 pub fn take(&self) -> Vec<Error> {
50 let mut errors = self.errors.borrow_mut();
51 let mut taken = Vec::new();
52 std::mem::swap(&mut *errors, &mut taken);
53 self.seen_spans.borrow_mut().clear();
54 taken
55 }
56}
57
58#[derive(ThisError, Debug)]
59pub enum Error {
60 #[error("Could not find template: {template}")]
61 TemplateNotFound { template: String, name: String },
62 #[error("Parse Error: {kind}")]
63 ParseError {
64 kind: ParseErrorKind,
65 span: SimpleSpan,
66 name: String,
67 },
68 #[error("Value does not exist in the context")]
69 ResolveError { span: SimpleSpan, name: String },
70 #[error("Failed to include file")]
71 IncludeError { span: SimpleSpan, name: String },
72}
73
74#[derive(Debug, ThisError)]
75pub enum ParseErrorKind {
76 #[error(transparent)]
77 Filter(#[from] FilterError),
78
79 #[error(transparent)]
80 Keyword(#[from] KeywordError),
81
82 #[error(transparent)]
83 Loop(#[from] LoopError),
84
85 #[error(transparent)]
86 BinOp(#[from] BinaryOperatorError),
87
88 #[error(transparent)]
89 If(#[from] IfError),
90}
91
92#[derive(Debug, ThisError)]
93pub enum IfError {
94 #[error("You can only use if conditions with Booleans")]
95 InvalidIfCondition,
96}
97
98#[derive(Debug, ThisError)]
99pub enum BinaryOperatorError {
100 #[error("Cannot apply '{op}' operator between {lhs} and {rhs}")]
101 InvalidBinaryOperatorType {
102 lhs: String,
103 op: String,
104 rhs: String,
105 },
106}
107
108#[derive(Debug, ThisError)]
109pub enum LoopError {
110 #[error("You can only loop over Arrays, Maps and Colors")]
111 LoopOverNonIterableValue,
112 #[error(
113 "For loop over an Array supports only one variable. Key and value iteration (`<* for key, value in map *>`) is only valid for Maps."
114 )]
115 TooManyLoopVariablesArray,
116 #[error("For loop supports only one or two variables")]
117 TooManyLoopVariables,
118}
119
120#[derive(Debug, ThisError)]
121pub enum KeywordError {
122 #[error("The format provided is not valid. Available formats are: {formats:?}")]
123 InvalidFormat { formats: &'static [&'static str] },
124 #[error("Invalid color mode. The color mode can only be one of: [dark, light, default]")]
125 ColorDoesNotExist,
126 #[error("The format for colors is 'colors.<color>.<scheme>.<format>'")]
127 InvalidColorDefinition,
128}
129
130#[derive(Debug, ThisError)]
131pub enum FilterError {
132 #[error("Not enough arguments provided for filter")]
133 NotEnoughArguments,
134 #[error("Found '{actual}' expected '{expected}'")]
135 InvalidArgumentType {
136 span: SimpleSpan,
137 expected: String,
138 actual: String,
139 },
140 #[error("Cannot use color filters on a string filter, consider using the 'to_color' filter")]
141 ColorFilterOnString,
142 #[error("Cannot use color filters on a boolean value")]
143 ColorFilterOnBool,
144 #[error("Could not find the filter: {filter}")]
145 FilterNotFound { filter: String },
146 #[error("Invalid String, expected one of: [{expected}]")]
147 UnexpectedStringValue { expected: String, span: SimpleSpan },
148 #[error("Invalid format for the 'format' filter, expected one of: {expected:?}")]
149 InvalidFormatString {
150 expected: &'static [&'static str],
151 span: SimpleSpan,
152 },
153 #[error(
154 "You should not use the set_alpha filter with a format that doesn't have an alpha channel. Consider using one of these formats instead: [{replacement}]"
155 )]
156 SetAlphaOnNonAlphaFormat { replacement: &'static str },
157}
158
159impl Error {
160 pub fn get_span(&self) -> Option<SimpleSpan> {
161 match self {
162 Error::TemplateNotFound { template: _, .. } => None,
163 Error::ParseError { kind: _, span, .. } => Some(*span),
164 Error::ResolveError { span, .. } => Some(*span),
165 Error::IncludeError { span, .. } => Some(*span),
166 }
167 }
168
169 pub fn get_name(&self) -> String {
170 match self {
171 Error::TemplateNotFound { .. } => "TemplateNotFound".to_owned(),
172 Error::ParseError { kind, .. } => match kind {
173 ParseErrorKind::Filter(e) => format!("ParseError::{}", e.name()),
174 ParseErrorKind::Keyword(e) => format!("ParseError::{}", e.name()),
175 ParseErrorKind::Loop(e) => format!("ParseError::{}", e.name()),
176 ParseErrorKind::BinOp(e) => format!("ParseError::{}", e.name()),
177 ParseErrorKind::If(e) => format!("ParseError::{}", e.name()),
178 },
179 Error::ResolveError { .. } => "ResolveError".to_owned(),
180 Error::IncludeError { .. } => "IncludeError".to_owned(),
181 }
182 }
183
184 pub fn get_file_name(&self) -> &String {
185 match self {
186 Error::TemplateNotFound { name, .. } => name,
187 Error::ParseError { name, .. } => name,
188 Error::ResolveError { name, .. } => name,
189 Error::IncludeError { name, .. } => name,
190 }
191 }
192
193 pub fn emit(&self, engine: &Engine) -> Result<(), color_eyre::Report> {
194 let name = self.get_name();
195 let message = self.to_string();
196 let span = self.get_span();
197 let file_name = self.get_file_name();
198 let source_code = engine.get_source(&file_name)?;
199
200 if let Some(span) = span {
201 Ok(build_report(&name, source_code, message, span, file_name))
202 } else {
203 Ok(eprintln!("{}", message))
204 }
205 }
206}
207
208impl FilterError {
209 pub fn name(&self) -> &str {
210 match self {
211 FilterError::NotEnoughArguments => "NotEnoughArguments",
212 FilterError::InvalidArgumentType { .. } => "InvalidArgumentType",
213 FilterError::ColorFilterOnString => "ColorFilterOnString",
214 FilterError::ColorFilterOnBool => "ColorFilterOnBool",
215 FilterError::FilterNotFound { .. } => "FilterNotFound",
216 FilterError::UnexpectedStringValue { .. } => "UnexpectedStringValue",
217 FilterError::InvalidFormatString { .. } => "InvalidFormatString",
218 FilterError::SetAlphaOnNonAlphaFormat { .. } => "SetAlphaOnNonAlphaFormat",
219 }
220 }
221}
222
223impl KeywordError {
224 pub fn name(&self) -> &str {
225 match self {
226 KeywordError::InvalidFormat { .. } => "InvalidFormat",
227 KeywordError::ColorDoesNotExist => "ColorDoesNotExist",
228 KeywordError::InvalidColorDefinition => "InvalidColorDefinition",
229 }
230 }
231}
232
233impl LoopError {
234 pub fn name(&self) -> &str {
235 match self {
236 LoopError::LoopOverNonIterableValue => "LoopOverNonIterableValue",
237 LoopError::TooManyLoopVariablesArray => "TooManyLoopVariables",
238 LoopError::TooManyLoopVariables => "TooManyLoopVariables",
239 }
240 }
241}
242
243impl BinaryOperatorError {
244 pub fn name(&self) -> &str {
245 match self {
246 BinaryOperatorError::InvalidBinaryOperatorType { .. } => "InvalidBinaryOperatorType",
247 }
248 }
249}
250
251impl IfError {
252 pub fn name(&self) -> &str {
253 match self {
254 IfError::InvalidIfCondition => "InvalidIfCondition",
255 }
256 }
257}
258
259fn build_report(name: &str, source_code: &str, message: String, span: SimpleSpan, file_name: &str) {
260 Report::build(ReportKind::Error, (file_name, span.into_range()))
261 .with_config(ariadne::Config::default().with_index_type(ariadne::IndexType::Byte))
262 .with_message(name)
263 .with_label(
264 Label::new((file_name, span.into_range()))
265 .with_message(message)
266 .with_color(Color::Red),
267 )
268 .finish()
269 .print((file_name, Source::from(&source_code)))
270 .unwrap();
271}