1use std::{
2 error::Error,
3 fmt::{self, Display},
4 io,
5 string::FromUtf8Error,
6 sync::Arc,
7};
8
9use codemap::{Span, SpanLoc};
10
11pub type SassResult<T> = Result<T, Box<SassError>>;
12
13#[derive(Debug, Clone)]
29pub struct SassError {
30 kind: SassErrorKind,
31}
32
33impl SassError {
34 #[must_use]
35 pub fn kind(self) -> PublicSassErrorKind {
36 match self.kind {
37 SassErrorKind::ParseError {
38 message,
39 loc,
40 unicode,
41 } => PublicSassErrorKind::ParseError {
42 message,
43 loc,
44 unicode,
45 },
46 SassErrorKind::FromUtf8Error(s) => PublicSassErrorKind::FromUtf8Error(s),
47 SassErrorKind::IoError(io) => PublicSassErrorKind::IoError(io),
48 SassErrorKind::Raw(..) => unreachable!("raw errors should not be accessible by users"),
49 }
50 }
51
52 pub(crate) fn with_argument_name(mut self: Box<Self>, name: &str) -> Box<Self> {
55 if let SassErrorKind::Raw(message, _) = &mut self.kind {
56 *message = format!("${name}: {message}");
57 }
58 self
59 }
60
61 pub(crate) fn raw(self) -> (String, Span) {
62 match self.kind {
63 SassErrorKind::Raw(string, span) => (string, span),
64 e => unreachable!("unable to get raw of {:?}", e),
65 }
66 }
67
68 pub(crate) const fn from_loc(message: String, loc: SpanLoc, unicode: bool) -> Self {
69 SassError {
70 kind: SassErrorKind::ParseError {
71 message,
72 loc,
73 unicode,
74 },
75 }
76 }
77}
78
79#[non_exhaustive]
80#[derive(Debug, Clone)]
81pub enum PublicSassErrorKind {
82 ParseError {
83 message: String,
92 loc: SpanLoc,
93
94 unicode: bool,
99 },
100
101 IoError(Arc<io::Error>),
106
107 FromUtf8Error(String),
109}
110
111#[derive(Debug, Clone)]
112enum SassErrorKind {
113 Raw(String, Span),
117 ParseError {
118 message: String,
119 loc: SpanLoc,
120 unicode: bool,
121 },
122 IoError(Arc<io::Error>),
124 FromUtf8Error(String),
125}
126
127impl Display for SassError {
128 #[inline]
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 let (message, loc, unicode) = match &self.kind {
134 SassErrorKind::ParseError {
135 message,
136 loc,
137 unicode,
138 } => (message, loc, *unicode),
139 SassErrorKind::FromUtf8Error(..) => return writeln!(f, "Error: Invalid UTF-8."),
140 SassErrorKind::IoError(s) => return writeln!(f, "Error: {}", s),
141 SassErrorKind::Raw(..) => unreachable!(),
142 };
143
144 let first_bar = if unicode { '╷' } else { ',' };
145 let second_bar = if unicode { '│' } else { '|' };
146 let third_bar = if unicode { '│' } else { '|' };
147 let fourth_bar = if unicode { '╵' } else { '\'' };
148
149 let line = loc.begin.line + 1;
150 let col = loc.begin.column + 1;
151 writeln!(f, "Error: {}", message)?;
152 let padding = vec![' '; format!("{}", line).len() + 1]
153 .iter()
154 .collect::<String>();
155 writeln!(f, "{}{}", padding, first_bar)?;
156 writeln!(
157 f,
158 "{} {} {}",
159 line,
160 second_bar,
161 loc.file.source_line(loc.begin.line)
162 )?;
163 writeln!(
164 f,
165 "{}{} {}{}",
166 padding,
167 third_bar,
168 vec![' '; loc.begin.column].iter().collect::<String>(),
169 vec!['^'; loc.end.column.max(loc.begin.column) - loc.begin.column.min(loc.end.column)]
170 .iter()
171 .collect::<String>()
172 )?;
173 writeln!(f, "{}{}", padding, fourth_bar)?;
174
175 if unicode {
176 writeln!(f, "./{}:{}:{}", loc.file.name(), line, col)?;
177 } else {
178 writeln!(f, " {} {}:{} root stylesheet", loc.file.name(), line, col)?;
179 }
180 Ok(())
181 }
182}
183
184impl From<io::Error> for Box<SassError> {
185 #[inline]
186 fn from(error: io::Error) -> Box<SassError> {
187 Box::new(SassError {
188 kind: SassErrorKind::IoError(Arc::new(error)),
189 })
190 }
191}
192
193impl From<FromUtf8Error> for Box<SassError> {
194 #[inline]
195 fn from(error: FromUtf8Error) -> Box<SassError> {
196 Box::new(SassError {
197 kind: SassErrorKind::FromUtf8Error(format!(
198 "Invalid UTF-8 character \"\\x{:X?}\"",
199 error.as_bytes()[0]
200 )),
201 })
202 }
203}
204
205impl From<(&str, Span)> for Box<SassError> {
206 #[inline]
207 fn from(error: (&str, Span)) -> Box<SassError> {
208 Box::new(SassError {
209 kind: SassErrorKind::Raw(error.0.to_owned(), error.1),
210 })
211 }
212}
213
214impl From<(String, Span)> for Box<SassError> {
215 #[inline]
216 fn from(error: (String, Span)) -> Box<SassError> {
217 Box::new(SassError {
218 kind: SassErrorKind::Raw(error.0, error.1),
219 })
220 }
221}
222
223impl Error for SassError {
224 #[inline]
225 fn description(&self) -> &'static str {
226 "Sass parsing error"
227 }
228}