use std::{
error::Error,
fmt::{self, Display},
io,
string::FromUtf8Error,
sync::Arc,
};
use codemap::{Span, SpanLoc};
pub type SassResult<T> = Result<T, Box<SassError>>;
#[derive(Debug, Clone)]
pub struct SassError {
kind: SassErrorKind,
}
impl SassError {
#[must_use]
pub fn kind(self) -> PublicSassErrorKind {
match self.kind {
SassErrorKind::ParseError {
message,
loc,
unicode,
} => PublicSassErrorKind::ParseError {
message,
loc,
unicode,
},
SassErrorKind::FromUtf8Error(s) => PublicSassErrorKind::FromUtf8Error(s),
SassErrorKind::IoError(io) => PublicSassErrorKind::IoError(io),
SassErrorKind::Raw(..) => unreachable!("raw errors should not be accessible by users"),
}
}
pub(crate) fn raw(self) -> (String, Span) {
match self.kind {
SassErrorKind::Raw(string, span) => (string, span),
e => unreachable!("unable to get raw of {:?}", e),
}
}
pub(crate) const fn from_loc(message: String, loc: SpanLoc, unicode: bool) -> Self {
SassError {
kind: SassErrorKind::ParseError {
message,
loc,
unicode,
},
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum PublicSassErrorKind {
ParseError {
message: String,
loc: SpanLoc,
unicode: bool,
},
IoError(Arc<io::Error>),
FromUtf8Error(String),
}
#[derive(Debug, Clone)]
enum SassErrorKind {
Raw(String, Span),
ParseError {
message: String,
loc: SpanLoc,
unicode: bool,
},
IoError(Arc<io::Error>),
FromUtf8Error(String),
}
impl Display for SassError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (message, loc, unicode) = match &self.kind {
SassErrorKind::ParseError {
message,
loc,
unicode,
} => (message, loc, *unicode),
SassErrorKind::FromUtf8Error(..) => return writeln!(f, "Error: Invalid UTF-8."),
SassErrorKind::IoError(s) => return writeln!(f, "Error: {}", s),
SassErrorKind::Raw(..) => unreachable!(),
};
let first_bar = if unicode { '╷' } else { ',' };
let second_bar = if unicode { '│' } else { '|' };
let third_bar = if unicode { '│' } else { '|' };
let fourth_bar = if unicode { '╵' } else { '\'' };
let line = loc.begin.line + 1;
let col = loc.begin.column + 1;
writeln!(f, "Error: {}", message)?;
let padding = vec![' '; format!("{}", line).len() + 1]
.iter()
.collect::<String>();
writeln!(f, "{}{}", padding, first_bar)?;
writeln!(
f,
"{} {} {}",
line,
second_bar,
loc.file.source_line(loc.begin.line)
)?;
writeln!(
f,
"{}{} {}{}",
padding,
third_bar,
vec![' '; loc.begin.column].iter().collect::<String>(),
vec!['^'; loc.end.column.max(loc.begin.column) - loc.begin.column.min(loc.end.column)]
.iter()
.collect::<String>()
)?;
writeln!(f, "{}{}", padding, fourth_bar)?;
if unicode {
writeln!(f, "./{}:{}:{}", loc.file.name(), line, col)?;
} else {
writeln!(f, " {} {}:{} root stylesheet", loc.file.name(), line, col)?;
}
Ok(())
}
}
impl From<io::Error> for Box<SassError> {
#[inline]
fn from(error: io::Error) -> Box<SassError> {
Box::new(SassError {
kind: SassErrorKind::IoError(Arc::new(error)),
})
}
}
impl From<FromUtf8Error> for Box<SassError> {
#[inline]
fn from(error: FromUtf8Error) -> Box<SassError> {
Box::new(SassError {
kind: SassErrorKind::FromUtf8Error(format!(
"Invalid UTF-8 character \"\\x{:X?}\"",
error.as_bytes()[0]
)),
})
}
}
impl From<(&str, Span)> for Box<SassError> {
#[inline]
fn from(error: (&str, Span)) -> Box<SassError> {
Box::new(SassError {
kind: SassErrorKind::Raw(error.0.to_owned(), error.1),
})
}
}
impl From<(String, Span)> for Box<SassError> {
#[inline]
fn from(error: (String, Span)) -> Box<SassError> {
Box::new(SassError {
kind: SassErrorKind::Raw(error.0, error.1),
})
}
}
impl Error for SassError {
#[inline]
fn description(&self) -> &'static str {
"Sass parsing error"
}
}