Skip to main content

brioche_ducc/
error.rs

1use ffi;
2use std::any::TypeId;
3use std::error::Error as StdError;
4use std::{fmt, result};
5
6pub type Result<T> = result::Result<T, Error>;
7
8#[derive(Debug)]
9pub struct Error {
10    /// The underlying type of error.
11    pub kind: ErrorKind,
12    /// An optional list of context messages describing the error. This corresponds to the
13    /// JavaScript `Error`'s `message` property.
14    pub context: Vec<String>,
15}
16
17#[derive(Debug)]
18pub enum ErrorKind {
19    /// A Rust value could not be converted to a JavaScript value.
20    ToJsConversionError {
21        /// Name of the Rust type that could not be converted.
22        from: &'static str,
23        /// Name of the JavaScript type that could not be created.
24        to: &'static str,
25    },
26    /// A JavaScript value could not be converted to the expected Rust type.
27    FromJsConversionError {
28        /// Name of the JavaScript type that could not be converted.
29        from: &'static str,
30        /// Name of the Rust type that could not be created.
31        to: &'static str,
32    },
33    /// An error that occurred within the scripting environment.
34    RuntimeError {
35        /// A code representing what type of error occurred.
36        code: RuntimeErrorCode,
37        /// A string representation of the type of error.
38        name: String,
39    },
40    /// A mutable callback has triggered JavaScript code that has called the same mutable callback
41    /// again.
42    ///
43    /// This is an error because a mutable callback can only be borrowed mutably once.
44    RecursiveMutCallback,
45    /// A custom error that occurs during runtime.
46    ///
47    /// This can be used for returning user-defined errors from callbacks.
48    ExternalError(Box<dyn RuntimeError + 'static>),
49    /// An error specifying the variable that was called as a function was not a function.
50    NotAFunction,
51}
52
53impl Error {
54    /// Creates an `Error` from any type that implements `RuntimeError`.
55    pub fn external<T: RuntimeError + 'static>(error: T) -> Error {
56        Error {
57            kind: ErrorKind::ExternalError(Box::new(error)),
58            context: vec![],
59        }
60    }
61
62    pub fn from_js_conversion(from: &'static str, to: &'static str) -> Error {
63        Error {
64            kind: ErrorKind::FromJsConversionError { from, to },
65            context: vec![],
66        }
67    }
68
69    pub fn to_js_conversion(from: &'static str, to: &'static str) -> Error {
70        Error {
71            kind: ErrorKind::ToJsConversionError { from, to },
72            context: vec![],
73        }
74    }
75
76    pub fn recursive_mut_callback() -> Error {
77        Error { kind: ErrorKind::RecursiveMutCallback, context: vec![] }
78    }
79
80    pub fn not_a_function() -> Error {
81        Error { kind: ErrorKind::NotAFunction, context: vec![] }
82    }
83
84    pub(crate) fn into_runtime_error_desc(self) -> RuntimeErrorDesc {
85        RuntimeErrorDesc {
86            code: self.runtime_code(),
87            name: self.runtime_name(),
88            message: self.runtime_message(),
89            cause: Box::new(self),
90        }
91    }
92
93    fn runtime_code(&self) -> RuntimeErrorCode {
94        match &self.kind {
95            ErrorKind::ToJsConversionError { .. } => RuntimeErrorCode::TypeError,
96            ErrorKind::FromJsConversionError { .. } => RuntimeErrorCode::TypeError,
97            ErrorKind::NotAFunction => RuntimeErrorCode::TypeError,
98            ErrorKind::ExternalError(err) => err.code(),
99            _ => RuntimeErrorCode::Error
100        }
101    }
102
103    fn runtime_name(&self) -> String {
104        match &self.kind {
105            ErrorKind::ExternalError(err) => err.name(),
106            _ => self.runtime_code().to_string()
107        }
108    }
109
110    fn runtime_message(&self) -> Option<String> {
111        let mut message = String::new();
112
113        for context in self.context.iter().rev() {
114            if !message.is_empty() {
115                message.push_str(": ");
116            }
117
118            message.push_str(context);
119        }
120
121        if let ErrorKind::ExternalError(ref error) = self.kind {
122            if let Some(ref ext_message) = error.message() {
123                if !message.is_empty() {
124                    message.push_str(": ");
125                }
126
127                message.push_str(ext_message);
128            }
129        }
130
131        if !message.is_empty() {
132            Some(message)
133        } else {
134            None
135        }
136    }
137}
138
139impl StdError for Error {
140    fn description(&self) -> &'static str {
141        "JavaScript execution error"
142    }
143}
144
145impl fmt::Display for Error {
146    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
147        for context in self.context.iter().rev() {
148            write!(fmt, "{}: ", context)?;
149        }
150
151        match self.kind {
152            ErrorKind::ToJsConversionError { from, to } => {
153                write!(fmt, "error converting {} to JavaScript {}", from, to)
154            },
155            ErrorKind::FromJsConversionError { from, to } => {
156                write!(fmt, "error converting JavaScript {} to {}", from, to)
157            },
158            ErrorKind::RuntimeError { ref name, .. } => {
159                write!(fmt, "JavaScript runtime error ({})", name)
160            },
161            ErrorKind::RecursiveMutCallback => write!(fmt, "mutable callback called recursively"),
162            ErrorKind::NotAFunction => write!(fmt, "tried to a call a non-function"),
163            ErrorKind::ExternalError(ref err) => err.fmt(fmt),
164        }
165    }
166}
167
168pub trait ResultExt {
169    fn js_err_context<D: fmt::Display>(self, context: D) -> Self;
170    fn js_err_context_with<D: fmt::Display, F: FnOnce(&Error) -> D>(self, op: F) -> Self;
171}
172
173impl<T> ResultExt for result::Result<T, Error> {
174    fn js_err_context<D: fmt::Display>(self, context: D) -> Self {
175        match self {
176            Err(mut err) => {
177                err.context.push(context.to_string());
178                Err(err)
179            },
180            result => result,
181        }
182    }
183
184    fn js_err_context_with<D: fmt::Display, F: FnOnce(&Error) -> D>(self, op: F) -> Self {
185        match self {
186            Err(mut err) => {
187                let context = op(&err).to_string();
188                err.context.push(context);
189                Err(err)
190            },
191            result => result,
192        }
193    }
194}
195
196impl ResultExt for Error {
197    fn js_err_context<D: fmt::Display>(mut self, context: D) -> Self {
198        self.context.push(context.to_string());
199        self
200    }
201
202    fn js_err_context_with<D: fmt::Display, F: FnOnce(&Error) -> D>(mut self, op: F) -> Self {
203        let context = op(&self).to_string();
204        self.context.push(context);
205        self
206    }
207}
208
209pub(crate) struct RuntimeErrorDesc {
210    pub code: RuntimeErrorCode,
211    pub name: String,
212    pub message: Option<String>,
213    pub cause: Box<Error>,
214}
215
216/// Represents the various types of JavaScript errors that can occur. This corresponds to the
217/// `prototype` of the JavaScript error object, and the `name` field is typically derived from it.
218#[derive(Clone, Debug, PartialEq)]
219pub enum RuntimeErrorCode {
220    Error,
221    EvalError,
222    RangeError,
223    ReferenceError,
224    SyntaxError,
225    TypeError,
226    UriError,
227}
228
229impl RuntimeErrorCode {
230    pub(crate) fn from_duk_errcode(code: ffi::duk_errcode_t) -> RuntimeErrorCode {
231        match code as u32 {
232            ffi::DUK_ERR_ERROR => RuntimeErrorCode::Error,
233            ffi::DUK_ERR_EVAL_ERROR => RuntimeErrorCode::EvalError,
234            ffi::DUK_ERR_RANGE_ERROR => RuntimeErrorCode::RangeError,
235            ffi::DUK_ERR_REFERENCE_ERROR => RuntimeErrorCode::ReferenceError,
236            ffi::DUK_ERR_SYNTAX_ERROR => RuntimeErrorCode::SyntaxError,
237            ffi::DUK_ERR_TYPE_ERROR => RuntimeErrorCode::TypeError,
238            ffi::DUK_ERR_URI_ERROR => RuntimeErrorCode::UriError,
239            _ => RuntimeErrorCode::Error,
240        }
241    }
242
243    pub(crate) fn to_duk_errcode(&self) -> ffi::duk_errcode_t {
244        (match *self {
245            RuntimeErrorCode::Error => ffi::DUK_ERR_ERROR,
246            RuntimeErrorCode::EvalError => ffi::DUK_ERR_EVAL_ERROR,
247            RuntimeErrorCode::RangeError => ffi::DUK_ERR_RANGE_ERROR,
248            RuntimeErrorCode::ReferenceError => ffi::DUK_ERR_REFERENCE_ERROR,
249            RuntimeErrorCode::SyntaxError => ffi::DUK_ERR_SYNTAX_ERROR,
250            RuntimeErrorCode::TypeError => ffi::DUK_ERR_TYPE_ERROR,
251            RuntimeErrorCode::UriError => ffi::DUK_ERR_URI_ERROR,
252        }) as ffi::duk_errcode_t
253    }
254}
255
256impl fmt::Display for RuntimeErrorCode {
257    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258        match *self {
259            RuntimeErrorCode::Error => write!(f, "Error"),
260            RuntimeErrorCode::EvalError => write!(f, "EvalError"),
261            RuntimeErrorCode::RangeError => write!(f, "RangeError"),
262            RuntimeErrorCode::ReferenceError => write!(f, "ReferenceError"),
263            RuntimeErrorCode::SyntaxError => write!(f, "SyntaxError"),
264            RuntimeErrorCode::TypeError => write!(f, "TypeError"),
265            RuntimeErrorCode::UriError => write!(f, "URIError"),
266        }
267    }
268}
269
270/// A Rust error that can be transformed into a JavaScript error.
271pub trait RuntimeError: fmt::Debug {
272    /// The prototypical JavaScript error code.
273    ///
274    /// By default, this method returns `RuntimeErrorCode::Error`.
275    fn code(&self) -> RuntimeErrorCode {
276        RuntimeErrorCode::Error
277    }
278
279    /// The name of the error corresponding to the JavaScript error's `name` property.
280    ///
281    /// By default, this method returns the string name corresponding to this object's `code()`
282    /// return value.
283    fn name(&self) -> String {
284        self.code().to_string()
285    }
286
287    /// An optional message that is set on the JavaScript error's `message` property. This is
288    /// automatically appended to the parent `Error`'s `context` field.
289    ///
290    /// By default, this method returns `None`.
291    fn message(&self) -> Option<String> {
292        None
293    }
294
295    // TODO: Should we support modifying the error object?
296    // fn customize<'ducc>(&self, ducc: &'ducc Ducc, object: &'ducc Object<'ducc>) {
297    //     let _ = ducc;
298    //     let _ = object;
299    // }
300
301    #[doc(hidden)]
302    fn __private_get_type_id__(&self) -> TypeId where Self: 'static {
303        TypeId::of::<Self>()
304    }
305}
306
307impl dyn RuntimeError {
308    /// Attempts to downcast this failure to a concrete type by reference.
309    ///
310    /// If the underlying error is not of type `T`, this will return `None`.
311    pub fn downcast_ref<T: RuntimeError + 'static>(&self) -> Option<&T> {
312        if self.__private_get_type_id__() == TypeId::of::<T>() {
313            unsafe { Some(&*(self as *const dyn RuntimeError as *const T)) }
314        } else {
315            None
316        }
317    }
318}
319
320impl RuntimeError for () {
321}
322
323impl RuntimeError for String {
324    fn message(&self) -> Option<String> {
325        Some(self.clone())
326    }
327}
328
329impl<'a> RuntimeError for &'a str {
330    fn message(&self) -> Option<String> {
331        Some(self.to_string())
332    }
333}
334
335impl<T: RuntimeError + 'static> From<T> for Error {
336    fn from(error: T) -> Error {
337        Error::external(error)
338    }
339}
340
341impl From<ErrorKind> for Error {
342    fn from(error: ErrorKind) -> Error {
343        Error {
344            kind: error,
345            context: vec![],
346        }
347    }
348}