Skip to main content

o_core/
error.rs

1use thiserror::Error;
2
3pub enum JSResult {
4    String(String),
5}
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum JSErrorKind {
9    Execution,
10    Compile,
11    Runtime,
12    Internal,
13}
14
15#[derive(Error, Debug)]
16pub struct JSError {
17    kind: JSErrorKind,
18    msg: String,
19    filename: Option<String>,
20    snippet: Option<String>,
21}
22
23impl JSError {
24    pub fn new(msg: impl Into<String>) -> Self {
25        Self {
26            kind: JSErrorKind::Execution,
27            msg: msg.into(),
28            filename: None,
29            snippet: None,
30        }
31    }
32
33    pub fn compile(msg: impl Into<String>) -> Self {
34        Self::new(msg).with_kind(JSErrorKind::Compile)
35    }
36
37    pub fn runtime(msg: impl Into<String>) -> Self {
38        Self::new(msg).with_kind(JSErrorKind::Runtime)
39    }
40
41    pub fn internal(msg: impl Into<String>) -> Self {
42        Self::new(msg).with_kind(JSErrorKind::Internal)
43    }
44
45    pub fn with_kind(mut self, kind: JSErrorKind) -> Self {
46        self.kind = kind;
47        self
48    }
49
50    pub fn with_filename(mut self, filename: impl Into<String>) -> Self {
51        self.filename = Some(filename.into());
52        self
53    }
54
55    pub fn with_source(mut self, source: impl Into<String>) -> Self {
56        self.snippet = Some(source.into());
57        self
58    }
59
60    pub fn kind(&self) -> JSErrorKind {
61        self.kind
62    }
63
64    pub fn message(&self) -> &str {
65        &self.msg
66    }
67
68    pub fn filename(&self) -> Option<&str> {
69        self.filename.as_deref()
70    }
71
72    pub fn source_snippet(&self) -> Option<&str> {
73        self.snippet.as_deref()
74    }
75}
76
77impl std::fmt::Display for JSError {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        let color = use_color();
80        let red = ansi(color, "1;31");
81        let yellow = ansi(color, "1;33");
82        let cyan = ansi(color, "1;36");
83        let dim = ansi(color, "2");
84        let reset = ansi(color, "0");
85
86        let kind = match self.kind {
87            JSErrorKind::Execution => "Execution Error",
88            JSErrorKind::Compile => "Compile Error",
89            JSErrorKind::Runtime => "Runtime Error",
90            JSErrorKind::Internal => "Internal Error",
91        };
92
93        write!(f, "{red}{kind}{reset}: {}", self.msg)?;
94
95        if let Some(filename) = &self.filename {
96            write!(f, "\n{cyan}--> {reset}{filename}")?;
97        }
98
99        if let Some(source) = self
100            .snippet
101            .as_deref()
102            .map(str::trim)
103            .filter(|s| !s.is_empty())
104        {
105            let snippet = shorten(source, 140);
106            write!(f, "\n{dim} |{reset} {yellow}{snippet}{reset}")?;
107        }
108
109        Ok(())
110    }
111}
112
113fn use_color() -> bool {
114    if std::env::var_os("NO_COLOR").is_some() {
115        return false;
116    }
117    !matches!(std::env::var("TERM").ok().as_deref(), Some("dumb"))
118}
119
120fn ansi(enabled: bool, code: &str) -> &'static str {
121    if !enabled {
122        return "";
123    }
124    match code {
125        "0" => "\x1b[0m",
126        "1;31" => "\x1b[1;31m",
127        "1;33" => "\x1b[1;33m",
128        "1;36" => "\x1b[1;36m",
129        "2" => "\x1b[2m",
130        _ => "",
131    }
132}
133
134fn shorten(input: &str, max_chars: usize) -> String {
135    let mut out = String::new();
136    for ch in input.chars().take(max_chars) {
137        if ch == '\n' || ch == '\r' {
138            out.push(' ');
139        } else {
140            out.push(ch);
141        }
142    }
143    if input.chars().count() > max_chars {
144        out.push_str("...");
145    }
146    out
147}