#[derive(Eq, PartialEq, Clone, Default)]
pub struct JsError {
pub message: String,
pub resource_name: String,
pub source_line: Option<String>,
pub line_number: Option<i64>,
pub start_column: Option<i64>,
pub end_column: Option<i64>,
pub stack: Option<String>,
}
impl JsError {
pub fn message(&self) -> &String {
&self.message
}
pub fn resource_name(&self) -> &String {
&self.resource_name
}
pub fn source_line(&self) -> &Option<String> {
&self.source_line
}
pub fn line_number(&self) -> &Option<i64> {
&self.line_number
}
pub fn start_column(&self) -> &Option<i64> {
&self.start_column
}
pub fn end_column(&self) -> &Option<i64> {
&self.end_column
}
pub fn stack(&self) -> &Option<String> {
&self.stack
}
}
impl JsError {
pub fn from_v8_exception<'a>(
scope: &'a mut v8::HandleScope,
rejection: v8::Local<'a, v8::Value>,
prefix: Option<&str>,
) -> Self {
let scope = &mut v8::HandleScope::new(scope);
let message = v8::Exception::create_message(scope, rejection);
let mut message_value = message
.get(scope)
.to_rust_string_lossy(scope)
.replacen("Uncaught ", "", 1);
if let Some(value) = prefix {
message_value.insert_str(0, value);
}
let resource_name = message.get_script_resource_name(scope).map_or_else(
|| "(unknown)".into(),
|s| s.to_string(scope).unwrap().to_rust_string_lossy(scope),
);
let source_line = message
.get_source_line(scope)
.map(|s| s.to_string(scope).unwrap().to_rust_string_lossy(scope));
let line_number = message.get_line_number(scope).map(|num| num as i64);
let start_column = Some(message.get_start_column() as i64);
let end_column = Some(message.get_end_column() as i64);
let exception = v8::Local::<v8::Object>::try_from(rejection);
let source_line = exception.map(|_| source_line).map(|s| s.unwrap()).ok();
let stack = exception
.map(|exception| {
let stack = v8::String::new(scope, "stack").unwrap();
let stack = exception.get(scope, stack.into());
let stack: Option<v8::Local<v8::String>> =
stack.and_then(|s| s.try_into().ok());
stack.map(|s| s.to_rust_string_lossy(scope))
})
.map(|stack| stack.unwrap_or_default())
.ok();
JsError {
message: message_value,
resource_name,
source_line,
line_number,
start_column,
end_column,
stack,
}
}
}
impl std::error::Error for JsError {}
impl std::fmt::Display for JsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let line = self.line_number.unwrap_or_default();
let column = self.start_column.unwrap_or_default();
write!(
f,
"Uncaught {} ({}:{}:{})",
self.message, self.resource_name, line, column
)
}
}
impl std::fmt::Debug for JsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Uncaught {}", self.message)?;
match self.source_line.as_ref() {
Some(source_line) if !source_line.is_empty() => {
writeln!(f, "\n{source_line}")?;
let start_column = self.start_column.unwrap_or_default();
let end_column = self.end_column.unwrap_or_default();
for _ in 0..start_column {
write!(f, " ")?;
}
for _ in start_column..end_column {
let mark = "^";
write!(f, "{mark}")?;
}
if let Some(stack) = self.stack.as_ref() {
write!(f, "\n{stack}")?;
}
}
_ => {}
};
Ok(())
}
}