dumpling 0.1.0

A fast JavaScript runtime and bundler in Rust
Documentation
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 {
    /// Format error with optional file/line context for better diagnostics
    pub fn with_context(self, context: ErrorContext) -> ErrorWithContext {
        ErrorWithContext {
            error: self,
            context,
        }
    }

    /// Create an error with a specific file and line number
    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)
    }

    /// Create an error with a specific function context
    pub fn in_function(self, function: String) -> ErrorWithContext {
        let ctx = ErrorContext::new().with_function(function);
        self.with_context(ctx)
    }

    /// Create a rich error with source code context
    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)
    }

    /// Generate a helpful suggestion based on the error type
    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,
        }
    }

    /// Generate a formatted error report
    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>;