use std::fmt;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DumplingError {
#[error("Runtime error: {0}")]
Runtime(#[from] boa_engine::JsError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("File not found: {0}")]
FileNotFound(String),
#[error("Module resolution failed: {0}")]
ModuleResolution(String),
#[error("Parse error: {0}")]
Parse(String),
#[error("Build error: {0}")]
Build(String),
#[error("Security vulnerability found: {0}")]
Security(String),
#[error("TypeScript error: {0}")]
TypeScript(String),
#[error("Bundling error: {0}")]
Bundling(String),
#[error("Package error: {0}")]
Package(String),
#[error("Network error: {0}")]
Network(String),
}
#[derive(Debug, Clone)]
pub struct ErrorContext {
pub file: Option<PathBuf>,
pub line: Option<u32>,
pub column: Option<u32>,
pub function: Option<String>,
pub source_code: Option<String>,
}
impl ErrorContext {
pub fn new() -> Self {
Self {
file: None,
line: None,
column: None,
function: None,
source_code: None,
}
}
pub fn with_file(mut self, file: PathBuf) -> Self {
self.file = Some(file);
self
}
pub fn with_line(mut self, line: u32) -> Self {
self.line = Some(line);
self
}
pub fn with_column(mut self, column: u32) -> Self {
self.column = Some(column);
self
}
pub fn with_function(mut self, function: String) -> Self {
self.function = Some(function);
self
}
pub fn with_source_code(mut self, source: String) -> Self {
self.source_code = Some(source);
self
}
pub fn format_location(&self) -> String {
let mut location = String::new();
if let Some(ref file) = self.file {
location.push_str(&file.display().to_string());
if let Some(line) = self.line {
location.push(':');
location.push_str(&line.to_string());
if let Some(column) = self.column {
location.push(':');
location.push_str(&column.to_string());
}
}
} else if let Some(line) = self.line {
location.push_str(&format!("line {}", line));
if let Some(column) = self.column {
location.push_str(&format!(":{}", column));
}
}
if location.is_empty() {
"unknown location".to_string()
} else {
location
}
}
pub fn format_detailed(&self) -> String {
let mut formatted = String::new();
if let Some(ref file) = self.file {
formatted.push_str(&format!("File: {}\n", file.display()));
}
if let Some(line) = self.line {
formatted.push_str(&format!("Line: {}\n", line));
if let Some(column) = self.column {
formatted.push_str(&format!("Column: {}\n", column));
}
}
if let Some(ref function) = self.function {
formatted.push_str(&format!("Function: {}\n", function));
}
if let Some(ref source) = self.source_code {
formatted.push_str("Source:\n");
formatted.push_str(source);
formatted.push('\n');
}
formatted
}
}
#[derive(Debug)]
pub struct ErrorWithContext {
pub error: DumplingError,
pub context: ErrorContext,
}
impl fmt::Display for ErrorWithContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} at {}", self.error, self.context.format_location())
}
}
impl std::error::Error for ErrorWithContext {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.error.source()
}
}
impl DumplingError {
pub fn with_context(self, context: ErrorContext) -> ErrorWithContext {
ErrorWithContext {
error: self,
context,
}
}
pub fn at_file(self, file: PathBuf, line: Option<u32>) -> ErrorWithContext {
let ctx = ErrorContext::new().with_file(file);
let ctx = if let Some(line) = line {
ctx.with_line(line)
} else {
ctx
};
self.with_context(ctx)
}
pub fn in_function(self, function: String) -> ErrorWithContext {
let ctx = ErrorContext::new().with_function(function);
self.with_context(ctx)
}
pub fn with_source(
self,
file: PathBuf,
line: u32,
column: u32,
source: String,
) -> ErrorWithContext {
let ctx = ErrorContext::new()
.with_file(file)
.with_line(line)
.with_column(column)
.with_source_code(source);
self.with_context(ctx)
}
pub fn suggestion(&self) -> Option<String> {
match self {
DumplingError::ModuleResolution(msg) => {
if msg.contains("not found") {
Some(
"Try running 'dumpling install <package>' to install missing dependencies"
.to_string(),
)
} else if msg.contains("circular") {
Some("Circular dependencies detected. Try refactoring your code to remove the cycle".to_string())
} else {
None
}
}
DumplingError::TypeScript(msg) => {
if msg.contains("Cannot find name") {
Some(
"Make sure all imported modules are properly installed and typed"
.to_string(),
)
} else if msg.contains("Property") && msg.contains("does not exist") {
Some(
"Check your TypeScript types or use type assertions if necessary"
.to_string(),
)
} else {
None
}
}
DumplingError::Build(msg) => {
if msg.contains("EMFILE") || msg.contains("ENFILE") {
Some("Too many open files. Try increasing your file descriptor limit or closing some programs".to_string())
} else if msg.contains("permission") {
Some("Check file permissions or run with appropriate privileges".to_string())
} else {
None
}
}
DumplingError::Network(msg) => {
if msg.contains("timeout") {
Some(
"Network timeout. Check your internet connection and try again".to_string(),
)
} else if msg.contains("certificate") {
Some(
"SSL certificate issue. Check your system clock or certificate store"
.to_string(),
)
} else {
None
}
}
_ => None,
}
}
pub fn generate_report(&self, context: Option<&ErrorContext>) -> String {
let mut report = String::new();
report.push_str("Error Report\n");
report.push_str("============\n\n");
report.push_str(&format!("Error: {}\n\n", self));
if let Some(ctx) = context {
report.push_str("Context:\n");
report.push_str("--------\n");
report.push_str(&ctx.format_detailed());
}
if let Some(suggestion) = self.suggestion() {
report.push_str("Suggestion:\n");
report.push_str("----------\n");
report.push_str(&suggestion);
report.push('\n');
}
report.push_str("\nTroubleshooting:\n");
report.push_str("---------------\n");
report
.push_str("Check the documentation at https://github.com/yingkitw/dumpling for help\n");
report
}
}
impl From<ErrorWithContext> for DumplingError {
fn from(ewc: ErrorWithContext) -> Self {
DumplingError::Build(format!("{} at {}", ewc.error, ewc.context.format_location()))
}
}
pub type Result<T> = std::result::Result<T, DumplingError>;