mirror of
https://github.com/denoland/deno.git
synced 2024-12-27 01:29:14 -05:00
519 lines
13 KiB
Rust
519 lines
13 KiB
Rust
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
|
|
|
//! There are many types of errors in Deno:
|
|
//! - ErrBox: a generic boxed object. This is the super type of all
|
|
//! errors handled in Rust.
|
|
//! - JSError: a container for the error message and stack trace for exceptions
|
|
//! thrown in JavaScript code. We use this to pretty-print stack traces.
|
|
//! - OpError: these are errors that happen during ops, which are passed
|
|
//! back into the runtime, where an exception object is created and thrown.
|
|
//! OpErrors have an integer code associated with them - access this via the
|
|
//! `kind` field.
|
|
//! - Diagnostic: these are errors that originate in TypeScript's compiler.
|
|
//! They're similar to JSError, in that they have line numbers.
|
|
//! But Diagnostics are compile-time type errors, whereas JSErrors are runtime
|
|
//! exceptions.
|
|
|
|
use crate::import_map::ImportMapError;
|
|
use crate::swc_util::SwcDiagnosticBuffer;
|
|
use deno_core::ErrBox;
|
|
use deno_core::ModuleResolutionError;
|
|
use rustyline::error::ReadlineError;
|
|
use std::env::VarError;
|
|
use std::error::Error;
|
|
use std::fmt;
|
|
use std::io;
|
|
|
|
// Warning! The values in this enum are duplicated in js/errors.ts
|
|
// Update carefully!
|
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
|
pub enum ErrorKind {
|
|
NotFound = 1,
|
|
PermissionDenied = 2,
|
|
ConnectionRefused = 3,
|
|
ConnectionReset = 4,
|
|
ConnectionAborted = 5,
|
|
NotConnected = 6,
|
|
AddrInUse = 7,
|
|
AddrNotAvailable = 8,
|
|
BrokenPipe = 9,
|
|
AlreadyExists = 10,
|
|
InvalidData = 13,
|
|
TimedOut = 14,
|
|
Interrupted = 15,
|
|
WriteZero = 16,
|
|
UnexpectedEof = 17,
|
|
BadResource = 18,
|
|
Http = 19,
|
|
URIError = 20,
|
|
TypeError = 21,
|
|
/// This maps to window.Error - ie. a generic error type
|
|
/// if no better context is available.
|
|
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
|
|
Other = 22,
|
|
Busy = 23,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct OpError {
|
|
pub kind: ErrorKind,
|
|
pub msg: String,
|
|
}
|
|
|
|
impl OpError {
|
|
fn new(kind: ErrorKind, msg: String) -> Self {
|
|
Self { kind, msg }
|
|
}
|
|
|
|
pub fn not_found(msg: String) -> Self {
|
|
Self::new(ErrorKind::NotFound, msg)
|
|
}
|
|
|
|
pub fn not_implemented() -> Self {
|
|
Self::other("not implemented".to_string())
|
|
}
|
|
|
|
pub fn other(msg: String) -> Self {
|
|
Self::new(ErrorKind::Other, msg)
|
|
}
|
|
|
|
pub fn type_error(msg: String) -> Self {
|
|
Self::new(ErrorKind::TypeError, msg)
|
|
}
|
|
|
|
pub fn http(msg: String) -> Self {
|
|
Self::new(ErrorKind::Http, msg)
|
|
}
|
|
|
|
pub fn uri_error(msg: String) -> Self {
|
|
Self::new(ErrorKind::URIError, msg)
|
|
}
|
|
|
|
pub fn permission_denied(msg: String) -> OpError {
|
|
Self::new(ErrorKind::PermissionDenied, msg)
|
|
}
|
|
|
|
pub fn bad_resource(msg: String) -> OpError {
|
|
Self::new(ErrorKind::BadResource, msg)
|
|
}
|
|
|
|
// BadResource usually needs no additional detail, hence this helper.
|
|
pub fn bad_resource_id() -> OpError {
|
|
Self::new(ErrorKind::BadResource, "Bad resource ID".to_string())
|
|
}
|
|
|
|
pub fn invalid_utf8() -> OpError {
|
|
Self::new(ErrorKind::InvalidData, "invalid utf8".to_string())
|
|
}
|
|
|
|
pub fn resource_unavailable() -> OpError {
|
|
Self::new(
|
|
ErrorKind::Busy,
|
|
"resource is unavailable because it is in use by a promise".to_string(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Error for OpError {}
|
|
|
|
impl fmt::Display for OpError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.pad(self.msg.as_str())
|
|
}
|
|
}
|
|
|
|
impl From<ImportMapError> for OpError {
|
|
fn from(error: ImportMapError) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&ImportMapError> for OpError {
|
|
fn from(error: &ImportMapError) -> Self {
|
|
Self {
|
|
kind: ErrorKind::Other,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ModuleResolutionError> for OpError {
|
|
fn from(error: ModuleResolutionError) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&ModuleResolutionError> for OpError {
|
|
fn from(error: &ModuleResolutionError) -> Self {
|
|
Self {
|
|
kind: ErrorKind::URIError,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<VarError> for OpError {
|
|
fn from(error: VarError) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&VarError> for OpError {
|
|
fn from(error: &VarError) -> Self {
|
|
use VarError::*;
|
|
let kind = match error {
|
|
NotPresent => ErrorKind::NotFound,
|
|
NotUnicode(..) => ErrorKind::InvalidData,
|
|
};
|
|
|
|
Self {
|
|
kind,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<io::Error> for OpError {
|
|
fn from(error: io::Error) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&io::Error> for OpError {
|
|
fn from(error: &io::Error) -> Self {
|
|
use io::ErrorKind::*;
|
|
let kind = match error.kind() {
|
|
NotFound => ErrorKind::NotFound,
|
|
PermissionDenied => ErrorKind::PermissionDenied,
|
|
ConnectionRefused => ErrorKind::ConnectionRefused,
|
|
ConnectionReset => ErrorKind::ConnectionReset,
|
|
ConnectionAborted => ErrorKind::ConnectionAborted,
|
|
NotConnected => ErrorKind::NotConnected,
|
|
AddrInUse => ErrorKind::AddrInUse,
|
|
AddrNotAvailable => ErrorKind::AddrNotAvailable,
|
|
BrokenPipe => ErrorKind::BrokenPipe,
|
|
AlreadyExists => ErrorKind::AlreadyExists,
|
|
InvalidInput => ErrorKind::TypeError,
|
|
InvalidData => ErrorKind::InvalidData,
|
|
TimedOut => ErrorKind::TimedOut,
|
|
Interrupted => ErrorKind::Interrupted,
|
|
WriteZero => ErrorKind::WriteZero,
|
|
UnexpectedEof => ErrorKind::UnexpectedEof,
|
|
Other => ErrorKind::Other,
|
|
WouldBlock => unreachable!(),
|
|
// Non-exhaustive enum - might add new variants
|
|
// in the future
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
Self {
|
|
kind,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<url::ParseError> for OpError {
|
|
fn from(error: url::ParseError) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&url::ParseError> for OpError {
|
|
fn from(error: &url::ParseError) -> Self {
|
|
Self {
|
|
kind: ErrorKind::URIError,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
impl From<reqwest::Error> for OpError {
|
|
fn from(error: reqwest::Error) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&reqwest::Error> for OpError {
|
|
fn from(error: &reqwest::Error) -> Self {
|
|
match error.source() {
|
|
Some(err_ref) => None
|
|
.or_else(|| {
|
|
err_ref
|
|
.downcast_ref::<url::ParseError>()
|
|
.map(|e| e.clone().into())
|
|
})
|
|
.or_else(|| {
|
|
err_ref
|
|
.downcast_ref::<io::Error>()
|
|
.map(|e| e.to_owned().into())
|
|
})
|
|
.or_else(|| {
|
|
err_ref
|
|
.downcast_ref::<serde_json::error::Error>()
|
|
.map(|e| e.into())
|
|
})
|
|
.unwrap_or_else(|| Self {
|
|
kind: ErrorKind::Http,
|
|
msg: error.to_string(),
|
|
}),
|
|
None => Self {
|
|
kind: ErrorKind::Http,
|
|
msg: error.to_string(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ReadlineError> for OpError {
|
|
fn from(error: ReadlineError) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&ReadlineError> for OpError {
|
|
fn from(error: &ReadlineError) -> Self {
|
|
use ReadlineError::*;
|
|
let kind = match error {
|
|
Io(err) => return OpError::from(err),
|
|
Eof => ErrorKind::UnexpectedEof,
|
|
Interrupted => ErrorKind::Interrupted,
|
|
#[cfg(unix)]
|
|
Errno(err) => return (*err).into(),
|
|
_ => unimplemented!(),
|
|
};
|
|
|
|
Self {
|
|
kind,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<serde_json::error::Error> for OpError {
|
|
fn from(error: serde_json::error::Error) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&serde_json::error::Error> for OpError {
|
|
fn from(error: &serde_json::error::Error) -> Self {
|
|
use serde_json::error::*;
|
|
let kind = match error.classify() {
|
|
Category::Io => ErrorKind::TypeError,
|
|
Category::Syntax => ErrorKind::TypeError,
|
|
Category::Data => ErrorKind::InvalidData,
|
|
Category::Eof => ErrorKind::UnexpectedEof,
|
|
};
|
|
|
|
Self {
|
|
kind,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
impl From<nix::Error> for OpError {
|
|
fn from(error: nix::Error) -> Self {
|
|
use nix::errno::Errno::*;
|
|
let kind = match error {
|
|
nix::Error::Sys(EPERM) => ErrorKind::PermissionDenied,
|
|
nix::Error::Sys(EINVAL) => ErrorKind::TypeError,
|
|
nix::Error::Sys(ENOENT) => ErrorKind::NotFound,
|
|
nix::Error::Sys(ENOTTY) => ErrorKind::BadResource,
|
|
nix::Error::Sys(UnknownErrno) => unreachable!(),
|
|
nix::Error::Sys(_) => unreachable!(),
|
|
nix::Error::InvalidPath => ErrorKind::TypeError,
|
|
nix::Error::InvalidUtf8 => ErrorKind::InvalidData,
|
|
nix::Error::UnsupportedOperation => unreachable!(),
|
|
};
|
|
|
|
Self {
|
|
kind,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<dlopen::Error> for OpError {
|
|
fn from(error: dlopen::Error) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&dlopen::Error> for OpError {
|
|
fn from(error: &dlopen::Error) -> Self {
|
|
use dlopen::Error::*;
|
|
let kind = match error {
|
|
NullCharacter(_) => ErrorKind::Other,
|
|
OpeningLibraryError(e) => return e.into(),
|
|
SymbolGettingError(e) => return e.into(),
|
|
AddrNotMatchingDll(e) => return e.into(),
|
|
NullSymbol => ErrorKind::Other,
|
|
};
|
|
|
|
Self {
|
|
kind,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<notify::Error> for OpError {
|
|
fn from(error: notify::Error) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<¬ify::Error> for OpError {
|
|
fn from(error: ¬ify::Error) -> Self {
|
|
use notify::ErrorKind::*;
|
|
let kind = match error.kind {
|
|
Generic(_) => ErrorKind::Other,
|
|
Io(ref e) => return e.into(),
|
|
PathNotFound => ErrorKind::NotFound,
|
|
WatchNotFound => ErrorKind::NotFound,
|
|
InvalidConfig(_) => ErrorKind::InvalidData,
|
|
};
|
|
|
|
Self {
|
|
kind,
|
|
msg: error.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<SwcDiagnosticBuffer> for OpError {
|
|
fn from(error: SwcDiagnosticBuffer) -> Self {
|
|
OpError::from(&error)
|
|
}
|
|
}
|
|
|
|
impl From<&SwcDiagnosticBuffer> for OpError {
|
|
fn from(error: &SwcDiagnosticBuffer) -> Self {
|
|
Self {
|
|
kind: ErrorKind::Other,
|
|
msg: error.diagnostics.join(", "),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ErrBox> for OpError {
|
|
fn from(error: ErrBox) -> Self {
|
|
#[cfg(unix)]
|
|
fn unix_error_kind(err: &ErrBox) -> Option<OpError> {
|
|
err.downcast_ref::<nix::Error>().map(|e| (*e).into())
|
|
}
|
|
|
|
#[cfg(not(unix))]
|
|
fn unix_error_kind(_: &ErrBox) -> Option<OpError> {
|
|
None
|
|
}
|
|
|
|
None
|
|
.or_else(|| {
|
|
error
|
|
.downcast_ref::<OpError>()
|
|
.map(|e| OpError::new(e.kind, e.msg.to_string()))
|
|
})
|
|
.or_else(|| error.downcast_ref::<reqwest::Error>().map(|e| e.into()))
|
|
.or_else(|| error.downcast_ref::<ImportMapError>().map(|e| e.into()))
|
|
.or_else(|| error.downcast_ref::<io::Error>().map(|e| e.into()))
|
|
.or_else(|| {
|
|
error
|
|
.downcast_ref::<ModuleResolutionError>()
|
|
.map(|e| e.into())
|
|
})
|
|
.or_else(|| error.downcast_ref::<url::ParseError>().map(|e| e.into()))
|
|
.or_else(|| error.downcast_ref::<VarError>().map(|e| e.into()))
|
|
.or_else(|| error.downcast_ref::<ReadlineError>().map(|e| e.into()))
|
|
.or_else(|| {
|
|
error
|
|
.downcast_ref::<serde_json::error::Error>()
|
|
.map(|e| e.into())
|
|
})
|
|
.or_else(|| error.downcast_ref::<dlopen::Error>().map(|e| e.into()))
|
|
.or_else(|| error.downcast_ref::<notify::Error>().map(|e| e.into()))
|
|
.or_else(|| {
|
|
error
|
|
.downcast_ref::<SwcDiagnosticBuffer>()
|
|
.map(|e| e.into())
|
|
})
|
|
.or_else(|| unix_error_kind(&error))
|
|
.unwrap_or_else(|| {
|
|
panic!("Can't downcast {:?} to OpError", error);
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn io_error() -> io::Error {
|
|
io::Error::from(io::ErrorKind::NotFound)
|
|
}
|
|
|
|
fn url_error() -> url::ParseError {
|
|
url::ParseError::EmptyHost
|
|
}
|
|
|
|
fn import_map_error() -> ImportMapError {
|
|
ImportMapError {
|
|
msg: "an import map error".to_string(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_simple_error() {
|
|
let err = OpError::not_found("foo".to_string());
|
|
assert_eq!(err.kind, ErrorKind::NotFound);
|
|
assert_eq!(err.to_string(), "foo");
|
|
}
|
|
|
|
#[test]
|
|
fn test_io_error() {
|
|
let err = OpError::from(io_error());
|
|
assert_eq!(err.kind, ErrorKind::NotFound);
|
|
assert_eq!(err.to_string(), "entity not found");
|
|
}
|
|
|
|
#[test]
|
|
fn test_url_error() {
|
|
let err = OpError::from(url_error());
|
|
assert_eq!(err.kind, ErrorKind::URIError);
|
|
assert_eq!(err.to_string(), "empty host");
|
|
}
|
|
|
|
// TODO find a way to easily test tokio errors and unix errors
|
|
|
|
#[test]
|
|
fn test_import_map_error() {
|
|
let err = OpError::from(import_map_error());
|
|
assert_eq!(err.kind, ErrorKind::Other);
|
|
assert_eq!(err.to_string(), "an import map error");
|
|
}
|
|
|
|
#[test]
|
|
fn test_bad_resource() {
|
|
let err = OpError::bad_resource("Resource has been closed".to_string());
|
|
assert_eq!(err.kind, ErrorKind::BadResource);
|
|
assert_eq!(err.to_string(), "Resource has been closed");
|
|
}
|
|
|
|
#[test]
|
|
fn test_bad_resource_id() {
|
|
let err = OpError::bad_resource_id();
|
|
assert_eq!(err.kind, ErrorKind::BadResource);
|
|
assert_eq!(err.to_string(), "Bad resource ID");
|
|
}
|
|
|
|
#[test]
|
|
fn test_permission_denied() {
|
|
let err = OpError::permission_denied(
|
|
"run again with the --allow-net flag".to_string(),
|
|
);
|
|
assert_eq!(err.kind, ErrorKind::PermissionDenied);
|
|
assert_eq!(err.to_string(), "run again with the --allow-net flag");
|
|
}
|
|
}
|