// 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 deno_core::ErrBox; use deno_core::ModuleResolutionError; use dlopen; use notify; use reqwest; use rustyline::error::ReadlineError; use std; use std::env::VarError; use std::error::Error; use std::fmt; use std::io; use url; // 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, } #[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()) } } 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 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 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 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 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 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 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::() .map(|e| e.clone().into()) }) .or_else(|| { err_ref .downcast_ref::() .map(|e| e.to_owned().into()) }) .or_else(|| { err_ref .downcast_ref::() .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 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 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 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(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 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 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 for OpError { fn from(error: ErrBox) -> Self { #[cfg(unix)] fn unix_error_kind(err: &ErrBox) -> Option { err.downcast_ref::().map(|e| (*e).into()) } #[cfg(not(unix))] fn unix_error_kind(_: &ErrBox) -> Option { None } None .or_else(|| { error .downcast_ref::() .map(|e| OpError::new(e.kind, e.msg.to_string())) }) .or_else(|| error.downcast_ref::().map(|e| e.into())) .or_else(|| error.downcast_ref::().map(|e| e.into())) .or_else(|| error.downcast_ref::().map(|e| e.into())) .or_else(|| { error .downcast_ref::() .map(|e| e.into()) }) .or_else(|| error.downcast_ref::().map(|e| e.into())) .or_else(|| error.downcast_ref::().map(|e| e.into())) .or_else(|| error.downcast_ref::().map(|e| e.into())) .or_else(|| { error .downcast_ref::() .map(|e| e.into()) }) .or_else(|| error.downcast_ref::().map(|e| e.into())) .or_else(|| error.downcast_ref::().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"); } }