diff --git a/Cargo.lock b/Cargo.lock index dd77a33133..dbcded2da4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2047,6 +2047,7 @@ dependencies = [ "signal-hook-registry", "tempfile", "test_server", + "thiserror", "tokio", "tokio-metrics", "twox-hash", diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index dcb6b1ade6..14580a8a64 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -121,6 +121,7 @@ serde.workspace = true signal-hook = "0.3.17" signal-hook-registry = "1.4.0" tempfile.workspace = true +thiserror.workspace = true tokio.workspace = true tokio-metrics.workspace = true twox-hash.workspace = true diff --git a/runtime/errors.rs b/runtime/errors.rs index 9fc84407a9..869f97968e 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -9,6 +9,14 @@ //! Diagnostics are compile-time type errors, whereas JsErrors are runtime //! exceptions. +use crate::ops::fs_events::FsEventsError; +use crate::ops::http::HttpStartError; +use crate::ops::os::OsError; +use crate::ops::process::ProcessError; +use crate::ops::signal::SignalError; +use crate::ops::tty::TtyError; +use crate::ops::web_worker::SyncFetchError; +use crate::ops::worker_host::CreateWorkerError; use deno_broadcast_channel::BroadcastChannelError; use deno_cache::CacheError; use deno_canvas::CanvasError; @@ -49,6 +57,7 @@ use deno_web::WebError; use deno_websocket::HandshakeError; use deno_websocket::WebsocketError; use deno_webstorage::WebStorageError; +use rustyline::error::ReadlineError; use std::env; use std::error::Error; use std::io; @@ -806,6 +815,102 @@ fn get_net_map_error(error: &deno_net::io::MapError) -> &'static str { } } +fn get_create_worker_error(error: &CreateWorkerError) -> &'static str { + match error { + CreateWorkerError::ClassicWorkers => "DOMExceptionNotSupportedError", + CreateWorkerError::Permission(e) => { + get_error_class_name(e).unwrap_or("Error") + } + CreateWorkerError::ModuleResolution(e) => { + get_module_resolution_error_class(e) + } + CreateWorkerError::Io(e) => get_io_error_class(e), + CreateWorkerError::MessagePort(e) => get_web_message_port_error_class(e), + } +} + +fn get_tty_error(error: &TtyError) -> &'static str { + match error { + TtyError::Resource(e) | TtyError::Other(e) => { + get_error_class_name(e).unwrap_or("Error") + } + TtyError::Io(e) => get_io_error_class(e), + #[cfg(unix)] + TtyError::Nix(e) => get_nix_error_class(e), + } +} + +fn get_readline_error(error: &ReadlineError) -> &'static str { + match error { + ReadlineError::Io(e) => get_io_error_class(e), + ReadlineError::Eof => "Error", + ReadlineError::Interrupted => "Error", + #[cfg(unix)] + ReadlineError::Errno(e) => get_nix_error_class(e), + ReadlineError::WindowResized => "Error", + #[cfg(windows)] + ReadlineError::Decode(_) => "Error", + #[cfg(windows)] + ReadlineError::SystemError(_) => "Error", + _ => "Error", + } +} + +fn get_signal_error(error: &SignalError) -> &'static str { + match error { + SignalError::InvalidSignalStr(_) => "TypeError", + SignalError::InvalidSignalInt(_) => "TypeError", + SignalError::SignalNotAllowed(_) => "TypeError", + SignalError::Io(e) => get_io_error_class(e), + } +} + +fn get_fs_events_error(error: &FsEventsError) -> &'static str { + match error { + FsEventsError::Resource(e) | FsEventsError::Permission(e) => { + get_error_class_name(e).unwrap_or("Error") + } + FsEventsError::Notify(e) => get_notify_error_class(e), + FsEventsError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + } +} + +fn get_http_start_error(error: &HttpStartError) -> &'static str { + match error { + HttpStartError::TcpStreamInUse => "Busy", + HttpStartError::TlsStreamInUse => "Busy", + HttpStartError::UnixSocketInUse => "Busy", + HttpStartError::ReuniteTcp(_) => "Error", + #[cfg(unix)] + HttpStartError::ReuniteUnix(_) => "Error", + HttpStartError::Io(e) => get_io_error_class(e), + HttpStartError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + } +} + +fn get_process_error(error: &ProcessError) -> &'static str { + match error { + ProcessError::SpawnFailed { error, .. } => get_process_error(error), + ProcessError::FailedResolvingCwd(e) | ProcessError::Io(e) => { + get_io_error_class(e) + } + ProcessError::Permission(e) | ProcessError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + ProcessError::BorrowMut(_) => "Error", + ProcessError::Which(_) => "Error", + ProcessError::ChildProcessAlreadyTerminated => "TypeError", + ProcessError::Signal(e) => get_signal_error(e), + ProcessError::MissingCmd => "Error", + ProcessError::InvalidPid => "TypeError", + #[cfg(unix)] + ProcessError::Nix(e) => get_nix_error_class(e), + } +} + fn get_http_error(error: &HttpError) -> &'static str { match error { HttpError::Canceled(e) => { @@ -859,10 +964,50 @@ fn get_websocket_upgrade_error(error: &WebSocketUpgradeError) -> &'static str { } } +fn get_os_error(error: &OsError) -> &'static str { + match error { + OsError::Permission(e) => get_error_class_name(e).unwrap_or("Error"), + OsError::InvalidUtf8(_) => "InvalidData", + OsError::EnvEmptyKey => "TypeError", + OsError::EnvInvalidKey(_) => "TypeError", + OsError::EnvInvalidValue(_) => "TypeError", + OsError::Io(e) => get_io_error_class(e), + OsError::Var(e) => get_env_var_error_class(e), + } +} + +fn get_sync_fetch_error(error: &SyncFetchError) -> &'static str { + match error { + SyncFetchError::BlobUrlsNotSupportedInContext => "TypeError", + SyncFetchError::Io(e) => get_io_error_class(e), + SyncFetchError::InvalidScriptUrl => "TypeError", + SyncFetchError::InvalidStatusCode(_) => "TypeError", + SyncFetchError::ClassicScriptSchemeUnsupportedInWorkers(_) => "TypeError", + SyncFetchError::InvalidUri(_) => "Error", + SyncFetchError::InvalidMimeType(_) => "DOMExceptionNetworkError", + SyncFetchError::MissingMimeType => "DOMExceptionNetworkError", + SyncFetchError::Fetch(e) => get_fetch_error(e), + SyncFetchError::Join(_) => "Error", + SyncFetchError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + } +} + pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { deno_core::error::get_custom_error_class(e) .or_else(|| e.downcast_ref::().map(get_napi_error_class)) .or_else(|| e.downcast_ref::().map(get_web_error_class)) + .or_else(|| { + e.downcast_ref::() + .map(get_create_worker_error) + }) + .or_else(|| e.downcast_ref::().map(get_tty_error)) + .or_else(|| e.downcast_ref::().map(get_readline_error)) + .or_else(|| e.downcast_ref::().map(get_signal_error)) + .or_else(|| e.downcast_ref::().map(get_fs_events_error)) + .or_else(|| e.downcast_ref::().map(get_http_start_error)) + .or_else(|| e.downcast_ref::().map(get_process_error)) + .or_else(|| e.downcast_ref::().map(get_os_error)) + .or_else(|| e.downcast_ref::().map(get_sync_fetch_error)) .or_else(|| { e.downcast_ref::() .map(get_web_compression_error_class) diff --git a/runtime/ops/fs_events.rs b/runtime/ops/fs_events.rs index d88a32d917..89a0606dbb 100644 --- a/runtime/ops/fs_events.rs +++ b/runtime/ops/fs_events.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::AsyncRefCell; use deno_core::CancelFuture; @@ -37,7 +36,7 @@ deno_core::extension!( struct FsEventsResource { #[allow(unused)] watcher: RecommendedWatcher, - receiver: AsyncRefCell>>, + receiver: AsyncRefCell>>, cancel: CancelHandle, } @@ -93,6 +92,18 @@ impl From for FsEvent { } } +#[derive(Debug, thiserror::Error)] +pub enum FsEventsError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[error(transparent)] + Notify(#[from] NotifyError), + #[error(transparent)] + Canceled(#[from] deno_core::Canceled), +} + #[derive(Deserialize)] pub struct OpenArgs { recursive: bool, @@ -104,12 +115,12 @@ pub struct OpenArgs { fn op_fs_events_open( state: &mut OpState, #[serde] args: OpenArgs, -) -> Result { - let (sender, receiver) = mpsc::channel::>(16); +) -> Result { + let (sender, receiver) = mpsc::channel::>(16); let sender = Mutex::new(sender); let mut watcher: RecommendedWatcher = Watcher::new( move |res: Result| { - let res2 = res.map(FsEvent::from).map_err(AnyError::from); + let res2 = res.map(FsEvent::from); let sender = sender.lock(); // Ignore result, if send failed it means that watcher was already closed, // but not all messages have been flushed. @@ -125,7 +136,8 @@ fn op_fs_events_open( for path in &args.paths { let path = state .borrow_mut::() - .check_read(path, "Deno.watchFs()")?; + .check_read(path, "Deno.watchFs()") + .map_err(FsEventsError::Permission)?; watcher.watch(&path, recursive_mode)?; } let resource = FsEventsResource { @@ -142,14 +154,18 @@ fn op_fs_events_open( async fn op_fs_events_poll( state: Rc>, #[smi] rid: ResourceId, -) -> Result, AnyError> { - let resource = state.borrow().resource_table.get::(rid)?; +) -> Result, FsEventsError> { + let resource = state + .borrow() + .resource_table + .get::(rid) + .map_err(FsEventsError::Resource)?; let mut receiver = RcRef::map(&resource, |r| &r.receiver).borrow_mut().await; let cancel = RcRef::map(resource, |r| &r.cancel); let maybe_result = receiver.recv().or_cancel(cancel).await?; match maybe_result { Some(Ok(value)) => Ok(Some(value)), - Some(Err(err)) => Err(err), + Some(Err(err)) => Err(FsEventsError::Notify(err)), None => Ok(None), } } diff --git a/runtime/ops/http.rs b/runtime/ops/http.rs index cbabbe22c5..6e31576686 100644 --- a/runtime/ops/http.rs +++ b/runtime/ops/http.rs @@ -2,9 +2,6 @@ use std::rc::Rc; -use deno_core::error::bad_resource_id; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::ResourceId; @@ -16,12 +13,31 @@ pub const UNSTABLE_FEATURE_NAME: &str = "http"; deno_core::extension!(deno_http_runtime, ops = [op_http_start],); +#[derive(Debug, thiserror::Error)] +pub enum HttpStartError { + #[error("TCP stream is currently in use")] + TcpStreamInUse, + #[error("TLS stream is currently in use")] + TlsStreamInUse, + #[error("Unix socket is currently in use")] + UnixSocketInUse, + #[error(transparent)] + ReuniteTcp(#[from] tokio::net::tcp::ReuniteError), + #[cfg(unix)] + #[error(transparent)] + ReuniteUnix(#[from] tokio::net::unix::ReuniteError), + #[error("{0}")] + Io(#[from] std::io::Error), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + #[op2(fast)] #[smi] fn op_http_start( state: &mut OpState, #[smi] tcp_stream_rid: ResourceId, -) -> Result { +) -> Result { if let Ok(resource_rc) = state .resource_table .take::(tcp_stream_rid) @@ -30,7 +46,7 @@ fn op_http_start( // process of starting a HTTP server on top of this TCP connection, so we just return a Busy error. // See also: https://github.com/denoland/deno/pull/16242 let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| custom_error("Busy", "TCP stream is currently in use"))?; + .map_err(|_| HttpStartError::TcpStreamInUse)?; let (read_half, write_half) = resource.into_inner(); let tcp_stream = read_half.reunite(write_half)?; let addr = tcp_stream.local_addr()?; @@ -45,7 +61,7 @@ fn op_http_start( // process of starting a HTTP server on top of this TLS connection, so we just return a Busy error. // See also: https://github.com/denoland/deno/pull/16242 let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| custom_error("Busy", "TLS stream is currently in use"))?; + .map_err(|_| HttpStartError::TlsStreamInUse)?; let (read_half, write_half) = resource.into_inner(); let tls_stream = read_half.unsplit(write_half); let addr = tls_stream.local_addr()?; @@ -61,7 +77,7 @@ fn op_http_start( // process of starting a HTTP server on top of this UNIX socket, so we just return a Busy error. // See also: https://github.com/denoland/deno/pull/16242 let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| custom_error("Busy", "Unix socket is currently in use"))?; + .map_err(|_| HttpStartError::UnixSocketInUse)?; let (read_half, write_half) = resource.into_inner(); let unix_stream = read_half.reunite(write_half)?; let addr = unix_stream.local_addr()?; @@ -73,5 +89,5 @@ fn op_http_start( )); } - Err(bad_resource_id()) + Err(HttpStartError::Other(deno_core::error::bad_resource_id())) } diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs index feed5052bb..67065b901b 100644 --- a/runtime/ops/mod.rs +++ b/runtime/ops/mod.rs @@ -9,7 +9,6 @@ pub mod process; pub mod runtime; pub mod signal; pub mod tty; -mod utils; pub mod web_worker; pub mod worker_host; diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index bd9260e97e..24b0389e16 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -1,9 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use super::utils::into_string; use crate::worker::ExitCode; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::v8; use deno_core::OpState; @@ -73,17 +70,39 @@ deno_core::extension!( }, ); +#[derive(Debug, thiserror::Error)] +pub enum OsError { + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[error("File name or path {0:?} is not valid UTF-8")] + InvalidUtf8(std::ffi::OsString), + #[error("Key is an empty string.")] + EnvEmptyKey, + #[error("Key contains invalid characters: {0:?}")] + EnvInvalidKey(String), + #[error("Value contains invalid characters: {0:?}")] + EnvInvalidValue(String), + #[error(transparent)] + Var(#[from] env::VarError), + #[error("{0}")] + Io(#[from] std::io::Error), +} + #[op2] #[string] -fn op_exec_path(state: &mut OpState) -> Result { +fn op_exec_path(state: &mut OpState) -> Result { let current_exe = env::current_exe().unwrap(); state .borrow_mut::() - .check_read_blind(¤t_exe, "exec_path", "Deno.execPath()")?; + .check_read_blind(¤t_exe, "exec_path", "Deno.execPath()") + .map_err(OsError::Permission)?; // normalize path so it doesn't include '.' or '..' components let path = normalize_path(current_exe); - into_string(path.into_os_string()) + path + .into_os_string() + .into_string() + .map_err(OsError::InvalidUtf8) } #[op2(fast)] @@ -91,20 +110,19 @@ fn op_set_env( state: &mut OpState, #[string] key: &str, #[string] value: &str, -) -> Result<(), AnyError> { - state.borrow_mut::().check_env(key)?; +) -> Result<(), OsError> { + state + .borrow_mut::() + .check_env(key) + .map_err(OsError::Permission)?; if key.is_empty() { - return Err(type_error("Key is an empty string.")); + return Err(OsError::EnvEmptyKey); } if key.contains(&['=', '\0'] as &[char]) { - return Err(type_error(format!( - "Key contains invalid characters: {key:?}" - ))); + return Err(OsError::EnvInvalidKey(key.to_string())); } if value.contains('\0') { - return Err(type_error(format!( - "Value contains invalid characters: {value:?}" - ))); + return Err(OsError::EnvInvalidValue(value.to_string())); } env::set_var(key, value); Ok(()) @@ -112,7 +130,9 @@ fn op_set_env( #[op2] #[serde] -fn op_env(state: &mut OpState) -> Result, AnyError> { +fn op_env( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state.borrow_mut::().check_env_all()?; Ok(env::vars().collect()) } @@ -122,21 +142,22 @@ fn op_env(state: &mut OpState) -> Result, AnyError> { fn op_get_env( state: &mut OpState, #[string] key: String, -) -> Result, AnyError> { +) -> Result, OsError> { let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key); if !skip_permission_check { - state.borrow_mut::().check_env(&key)?; + state + .borrow_mut::() + .check_env(&key) + .map_err(OsError::Permission)?; } if key.is_empty() { - return Err(type_error("Key is an empty string.")); + return Err(OsError::EnvEmptyKey); } if key.contains(&['=', '\0'] as &[char]) { - return Err(type_error(format!( - "Key contains invalid characters: {key:?}" - ))); + return Err(OsError::EnvInvalidKey(key.to_string())); } let r = match env::var(key) { @@ -150,10 +171,13 @@ fn op_get_env( fn op_delete_env( state: &mut OpState, #[string] key: String, -) -> Result<(), AnyError> { - state.borrow_mut::().check_env(&key)?; +) -> Result<(), OsError> { + state + .borrow_mut::() + .check_env(&key) + .map_err(OsError::Permission)?; if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { - return Err(type_error("Key contains invalid characters.")); + return Err(OsError::EnvInvalidKey(key.to_string())); } env::remove_var(key); Ok(()) @@ -178,7 +202,9 @@ fn op_exit(state: &mut OpState) { #[op2] #[serde] -fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { +fn op_loadavg( + state: &mut OpState, +) -> Result<(f64, f64, f64), deno_core::error::AnyError> { state .borrow_mut::() .check_sys("loadavg", "Deno.loadavg()")?; @@ -187,7 +213,9 @@ fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { #[op2] #[string] -fn op_hostname(state: &mut OpState) -> Result { +fn op_hostname( + state: &mut OpState, +) -> Result { state .borrow_mut::() .check_sys("hostname", "Deno.hostname()")?; @@ -196,7 +224,9 @@ fn op_hostname(state: &mut OpState) -> Result { #[op2] #[string] -fn op_os_release(state: &mut OpState) -> Result { +fn op_os_release( + state: &mut OpState, +) -> Result { state .borrow_mut::() .check_sys("osRelease", "Deno.osRelease()")?; @@ -207,10 +237,11 @@ fn op_os_release(state: &mut OpState) -> Result { #[serde] fn op_network_interfaces( state: &mut OpState, -) -> Result, AnyError> { +) -> Result, OsError> { state .borrow_mut::() - .check_sys("networkInterfaces", "Deno.networkInterfaces()")?; + .check_sys("networkInterfaces", "Deno.networkInterfaces()") + .map_err(OsError::Permission)?; Ok(netif::up()?.map(NetworkInterface::from).collect()) } @@ -259,7 +290,7 @@ impl From for NetworkInterface { #[serde] fn op_system_memory_info( state: &mut OpState, -) -> Result, AnyError> { +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?; @@ -269,7 +300,9 @@ fn op_system_memory_info( #[cfg(not(windows))] #[op2] #[smi] -fn op_gid(state: &mut OpState) -> Result, AnyError> { +fn op_gid( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("gid", "Deno.gid()")?; @@ -283,7 +316,9 @@ fn op_gid(state: &mut OpState) -> Result, AnyError> { #[cfg(windows)] #[op2] #[smi] -fn op_gid(state: &mut OpState) -> Result, AnyError> { +fn op_gid( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("gid", "Deno.gid()")?; @@ -293,7 +328,9 @@ fn op_gid(state: &mut OpState) -> Result, AnyError> { #[cfg(not(windows))] #[op2] #[smi] -fn op_uid(state: &mut OpState) -> Result, AnyError> { +fn op_uid( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("uid", "Deno.uid()")?; @@ -307,7 +344,9 @@ fn op_uid(state: &mut OpState) -> Result, AnyError> { #[cfg(windows)] #[op2] #[smi] -fn op_uid(state: &mut OpState) -> Result, AnyError> { +fn op_uid( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("uid", "Deno.uid()")?; @@ -485,7 +524,7 @@ fn rss() -> usize { } } -fn os_uptime(state: &mut OpState) -> Result { +fn os_uptime(state: &mut OpState) -> Result { state .borrow_mut::() .check_sys("osUptime", "Deno.osUptime()")?; @@ -494,6 +533,8 @@ fn os_uptime(state: &mut OpState) -> Result { #[op2(fast)] #[number] -fn op_os_uptime(state: &mut OpState) -> Result { +fn op_os_uptime( + state: &mut OpState, +) -> Result { os_uptime(state) } diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index f6555e9324..28913f7c16 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -1,8 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::anyhow::Context; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::serde_json; use deno_core::AsyncMutFuture; @@ -35,6 +32,7 @@ use tokio::process::Command; #[cfg(windows)] use std::os::windows::process::CommandExt; +use crate::ops::signal::SignalError; #[cfg(unix)] use std::os::unix::prelude::ExitStatusExt; #[cfg(unix)] @@ -105,11 +103,12 @@ impl StdioOrRid { pub fn as_stdio( &self, state: &mut OpState, - ) -> Result { + ) -> Result { match &self { StdioOrRid::Stdio(val) => Ok(val.as_stdio()), StdioOrRid::Rid(rid) => { FileResource::with_file(state, *rid, |file| Ok(file.as_stdio()?)) + .map_err(ProcessError::Resource) } } } @@ -191,6 +190,39 @@ pub struct SpawnArgs { needs_npm_process_state: bool, } +#[derive(Debug, thiserror::Error)] +pub enum ProcessError { + #[error("Failed to spawn '{command}': {error}")] + SpawnFailed { + command: String, + #[source] + error: Box, + }, + #[error("{0}")] + Io(#[from] std::io::Error), + #[cfg(unix)] + #[error(transparent)] + Nix(nix::Error), + #[error("failed resolving cwd: {0}")] + FailedResolvingCwd(#[source] std::io::Error), + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + BorrowMut(std::cell::BorrowMutError), + #[error(transparent)] + Which(which::Error), + #[error("Child process has already terminated.")] + ChildProcessAlreadyTerminated, + #[error("Invalid pid")] + InvalidPid, + #[error(transparent)] + Signal(#[from] SignalError), + #[error("Missing cmd")] + MissingCmd, // only for Deno.run +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChildStdio { @@ -208,7 +240,7 @@ pub struct ChildStatus { } impl TryFrom for ChildStatus { - type Error = AnyError; + type Error = SignalError; fn try_from(status: ExitStatus) -> Result { let code = status.code(); @@ -259,7 +291,7 @@ type CreateCommand = ( pub fn npm_process_state_tempfile( contents: &[u8], -) -> Result { +) -> Result { let mut temp_file = tempfile::tempfile()?; temp_file.write_all(contents)?; let handle = temp_file.into_raw_io_handle(); @@ -301,7 +333,7 @@ fn create_command( state: &mut OpState, mut args: SpawnArgs, api_name: &str, -) -> Result { +) -> Result { let maybe_npm_process_state = if args.needs_npm_process_state { let provider = state.borrow::(); let process_state = provider.get_npm_process_state(); @@ -505,7 +537,7 @@ fn spawn_child( ipc_pipe_rid: Option, extra_pipe_rids: Vec>, detached: bool, -) -> Result { +) -> Result { let mut command = tokio::process::Command::from(command); // TODO(@crowlkats): allow detaching processes. // currently deno will orphan a process when exiting with an error or Deno.exit() @@ -554,10 +586,10 @@ fn spawn_child( } } - return Err(AnyError::from(err).context(format!( - "Failed to spawn '{}'", - command.get_program().to_string_lossy() - ))); + return Err(ProcessError::SpawnFailed { + command: command.get_program().to_string_lossy().to_string(), + error: Box::new(err.into()), + }); } }; @@ -600,11 +632,19 @@ fn compute_run_cmd_and_check_permissions( arg_clear_env: bool, state: &mut OpState, api_name: &str, -) -> Result<(PathBuf, RunEnv), AnyError> { - let run_env = compute_run_env(arg_cwd, arg_envs, arg_clear_env) - .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; - let cmd = resolve_cmd(arg_cmd, &run_env) - .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; +) -> Result<(PathBuf, RunEnv), ProcessError> { + let run_env = + compute_run_env(arg_cwd, arg_envs, arg_clear_env).map_err(|e| { + ProcessError::SpawnFailed { + command: arg_cmd.to_string(), + error: Box::new(e), + } + })?; + let cmd = + resolve_cmd(arg_cmd, &run_env).map_err(|e| ProcessError::SpawnFailed { + command: arg_cmd.to_string(), + error: Box::new(e), + })?; check_run_permission( state, &RunQueryDescriptor::Path { @@ -613,7 +653,8 @@ fn compute_run_cmd_and_check_permissions( }, &run_env, api_name, - )?; + ) + .map_err(ProcessError::Permission)?; Ok((cmd, run_env)) } @@ -631,9 +672,10 @@ fn compute_run_env( arg_cwd: Option<&str>, arg_envs: &[(String, String)], arg_clear_env: bool, -) -> Result { +) -> Result { #[allow(clippy::disallowed_methods)] - let cwd = std::env::current_dir().context("failed resolving cwd")?; + let cwd = + std::env::current_dir().map_err(ProcessError::FailedResolvingCwd)?; let cwd = arg_cwd .map(|cwd_arg| resolve_path(cwd_arg, &cwd)) .unwrap_or(cwd); @@ -670,7 +712,7 @@ fn compute_run_env( Ok(RunEnv { envs, cwd }) } -fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result { +fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result { let is_path = cmd.contains('/'); #[cfg(windows)] let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute(); @@ -683,7 +725,7 @@ fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result { Err(which::Error::CannotFindBinaryPath) => { Err(std::io::Error::from(std::io::ErrorKind::NotFound).into()) } - Err(err) => Err(err.into()), + Err(err) => Err(ProcessError::Which(err)), } } } @@ -697,7 +739,7 @@ fn check_run_permission( cmd: &RunQueryDescriptor, run_env: &RunEnv, api_name: &str, -) -> Result<(), AnyError> { +) -> Result<(), deno_core::error::AnyError> { let permissions = state.borrow_mut::(); if !permissions.query_run_all(api_name) { // error the same on all platforms @@ -754,7 +796,7 @@ fn op_spawn_child( state: &mut OpState, #[serde] args: SpawnArgs, #[string] api_name: String, -) -> Result { +) -> Result { let detached = args.detached; let (command, pipe_rid, extra_pipe_rids, handles_to_close) = create_command(state, args, &api_name)?; @@ -771,16 +813,23 @@ fn op_spawn_child( async fn op_spawn_wait( state: Rc>, #[smi] rid: ResourceId, -) -> Result { +) -> Result { let resource = state .borrow_mut() .resource_table - .get::(rid)?; - let result = resource.0.try_borrow_mut()?.wait().await?.try_into(); + .get::(rid) + .map_err(ProcessError::Resource)?; + let result = resource + .0 + .try_borrow_mut() + .map_err(ProcessError::BorrowMut)? + .wait() + .await? + .try_into()?; if let Ok(resource) = state.borrow_mut().resource_table.take_any(rid) { resource.close(); } - result + Ok(result) } #[op2] @@ -788,16 +837,14 @@ async fn op_spawn_wait( fn op_spawn_sync( state: &mut OpState, #[serde] args: SpawnArgs, -) -> Result { +) -> Result { let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped)); let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped)); let (mut command, _, _, _) = create_command(state, args, "Deno.Command().outputSync()")?; - let output = command.output().with_context(|| { - format!( - "Failed to spawn '{}'", - command.get_program().to_string_lossy() - ) + let output = command.output().map_err(|e| ProcessError::SpawnFailed { + command: command.get_program().to_string_lossy().to_string(), + error: Box::new(e.into()), })?; Ok(SpawnOutput { @@ -820,17 +867,15 @@ fn op_spawn_kill( state: &mut OpState, #[smi] rid: ResourceId, #[string] signal: String, -) -> Result<(), AnyError> { +) -> Result<(), ProcessError> { if let Ok(child_resource) = state.resource_table.get::(rid) { deprecated::kill(child_resource.1 as i32, &signal)?; return Ok(()); } - Err(type_error("Child process has already terminated.")) + Err(ProcessError::ChildProcessAlreadyTerminated) } mod deprecated { - use deno_core::anyhow; - use super::*; #[derive(Deserialize)] @@ -876,9 +921,9 @@ mod deprecated { pub fn op_run( state: &mut OpState, #[serde] run_args: RunArgs, - ) -> Result { + ) -> Result { let args = run_args.cmd; - let cmd = args.first().ok_or_else(|| anyhow::anyhow!("Missing cmd"))?; + let cmd = args.first().ok_or(ProcessError::MissingCmd)?; let (cmd, run_env) = compute_run_cmd_and_check_permissions( cmd, run_args.cwd.as_deref(), @@ -990,11 +1035,12 @@ mod deprecated { pub async fn op_run_status( state: Rc>, #[smi] rid: ResourceId, - ) -> Result { + ) -> Result { let resource = state .borrow_mut() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(ProcessError::Resource)?; let mut child = resource.borrow_mut().await; let run_status = child.wait().await?; let code = run_status.code(); @@ -1017,17 +1063,17 @@ mod deprecated { } #[cfg(unix)] - pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> { + pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> { let signo = super::super::signal::signal_str_to_int(signal)?; use nix::sys::signal::kill as unix_kill; use nix::sys::signal::Signal; use nix::unistd::Pid; - let sig = Signal::try_from(signo)?; - unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(AnyError::from) + let sig = Signal::try_from(signo).map_err(ProcessError::Nix)?; + unix_kill(Pid::from_raw(pid), Some(sig)).map_err(ProcessError::Nix) } #[cfg(not(unix))] - pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> { + pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> { use std::io::Error; use std::io::ErrorKind::NotFound; use winapi::shared::minwindef::DWORD; @@ -1041,9 +1087,9 @@ mod deprecated { use winapi::um::winnt::PROCESS_TERMINATE; if !matches!(signal, "SIGKILL" | "SIGTERM") { - Err(type_error(format!("Invalid signal: {signal}"))) + Err(SignalError::InvalidSignalStr(signal.to_string()).into()) } else if pid <= 0 { - Err(type_error("Invalid pid")) + Err(ProcessError::InvalidPid) } else { let handle = // SAFETY: winapi call @@ -1077,11 +1123,11 @@ mod deprecated { #[smi] pid: i32, #[string] signal: String, #[string] api_name: String, - ) -> Result<(), AnyError> { + ) -> Result<(), ProcessError> { state .borrow_mut::() - .check_run_all(&api_name)?; - kill(pid, &signal)?; - Ok(()) + .check_run_all(&api_name) + .map_err(ProcessError::Permission)?; + kill(pid, &signal) } } diff --git a/runtime/ops/runtime.rs b/runtime/ops/runtime.rs index 419274ebd9..8d54783fc9 100644 --- a/runtime/ops/runtime.rs +++ b/runtime/ops/runtime.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::op2; use deno_core::ModuleSpecifier; use deno_core::OpState; @@ -16,10 +15,9 @@ deno_core::extension!( #[op2] #[string] -fn op_main_module(state: &mut OpState) -> Result { +fn op_main_module(state: &mut OpState) -> String { let main_url = state.borrow::(); - let main_path = main_url.to_string(); - Ok(main_path) + main_url.to_string() } /// This is an op instead of being done at initialization time because diff --git a/runtime/ops/signal.rs b/runtime/ops/signal.rs index 3bf43d6e15..e1e4ab68bc 100644 --- a/runtime/ops/signal.rs +++ b/runtime/ops/signal.rs @@ -1,6 +1,4 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::AsyncRefCell; use deno_core::CancelFuture; @@ -46,6 +44,42 @@ deno_core::extension!( } ); +#[derive(Debug, thiserror::Error)] +pub enum SignalError { + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "openbsd", + target_os = "openbsd", + target_os = "macos", + target_os = "solaris", + target_os = "illumos" + ))] + #[error("Invalid signal: {0}")] + InvalidSignalStr(String), + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "openbsd", + target_os = "openbsd", + target_os = "macos", + target_os = "solaris", + target_os = "illumos" + ))] + #[error("Invalid signal: {0}")] + InvalidSignalInt(libc::c_int), + #[cfg(target_os = "windows")] + #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")] + InvalidSignalStr(String), + #[cfg(target_os = "windows")] + #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")] + InvalidSignalInt(libc::c_int), + #[error("Binding to signal '{0}' is not allowed")] + SignalNotAllowed(String), + #[error("{0}")] + Io(#[from] std::io::Error), +} + #[cfg(unix)] #[derive(Default)] struct SignalState { @@ -153,18 +187,18 @@ macro_rules! first_literal { }; } macro_rules! signal_dict { - ($error_msg:expr, $(($number:literal, $($name:literal)|+)),*) => { - pub fn signal_str_to_int(s: &str) -> Result { + ($(($number:literal, $($name:literal)|+)),*) => { + pub fn signal_str_to_int(s: &str) -> Result { match s { $($($name)|* => Ok($number),)* - _ => Err(type_error($error_msg(s))), + _ => Err(SignalError::InvalidSignalStr(s.to_string())), } } - pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { + pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, SignalError> { match s { $($number => Ok(first_literal!($($name),+)),)* - _ => Err(type_error($error_msg(s))), + _ => Err(SignalError::InvalidSignalInt(s)), } } } @@ -172,7 +206,6 @@ macro_rules! signal_dict { #[cfg(target_os = "freebsd")] signal_dict!( - |s| { format!("Invalid signal : {}", s) }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -210,7 +243,6 @@ signal_dict!( #[cfg(target_os = "openbsd")] signal_dict!( - |s| { format!("Invalid signal : {}", s) }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -246,7 +278,6 @@ signal_dict!( #[cfg(any(target_os = "android", target_os = "linux"))] signal_dict!( - |s| { format!("Invalid signal : {s}") }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -282,7 +313,6 @@ signal_dict!( #[cfg(target_os = "macos")] signal_dict!( - |s| { format!("Invalid signal : {s}") }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -318,7 +348,6 @@ signal_dict!( #[cfg(any(target_os = "solaris", target_os = "illumos"))] signal_dict!( - |s| { format!("Invalid signal : {s}") }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -362,11 +391,7 @@ signal_dict!( ); #[cfg(target_os = "windows")] -signal_dict!( - |_| { "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK)." }, - (2, "SIGINT"), - (21, "SIGBREAK") -); +signal_dict!((2, "SIGINT"), (21, "SIGBREAK")); #[cfg(unix)] #[op2(fast)] @@ -374,12 +399,10 @@ signal_dict!( fn op_signal_bind( state: &mut OpState, #[string] sig: &str, -) -> Result { +) -> Result { let signo = signal_str_to_int(sig)?; if signal_hook_registry::FORBIDDEN.contains(&signo) { - return Err(type_error(format!( - "Binding to signal '{sig}' is not allowed", - ))); + return Err(SignalError::SignalNotAllowed(sig.to_string())); } let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?); @@ -413,7 +436,7 @@ fn op_signal_bind( fn op_signal_bind( state: &mut OpState, #[string] sig: &str, -) -> Result { +) -> Result { let signo = signal_str_to_int(sig)?; let resource = SignalStreamResource { signal: AsyncRefCell::new(match signo { @@ -437,7 +460,7 @@ fn op_signal_bind( async fn op_signal_poll( state: Rc>, #[smi] rid: ResourceId, -) -> Result { +) -> Result { let resource = state .borrow_mut() .resource_table @@ -456,7 +479,7 @@ async fn op_signal_poll( pub fn op_signal_unbind( state: &mut OpState, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { +) -> Result<(), deno_core::error::AnyError> { let resource = state.resource_table.take::(rid)?; #[cfg(unix)] diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs index 77e1330b51..7849185faa 100644 --- a/runtime/ops/tty.rs +++ b/runtime/ops/tty.rs @@ -2,7 +2,6 @@ use std::io::Error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use rustyline::config::Configurer; @@ -64,6 +63,19 @@ deno_core::extension!( }, ); +#[derive(Debug, thiserror::Error)] +pub enum TtyError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("{0}")] + Io(#[from] std::io::Error), + #[cfg(unix)] + #[error(transparent)] + Nix(nix::Error), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + // ref: #[cfg(windows)] const COOKED_MODE: DWORD = @@ -90,8 +102,11 @@ fn op_set_raw( rid: u32, is_raw: bool, cbreak: bool, -) -> Result<(), AnyError> { - let handle_or_fd = state.resource_table.get_fd(rid)?; +) -> Result<(), TtyError> { + let handle_or_fd = state + .resource_table + .get_fd(rid) + .map_err(TtyError::Resource)?; // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs // and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs @@ -107,7 +122,7 @@ fn op_set_raw( let handle = handle_or_fd; if cbreak { - return Err(deno_core::error::not_supported()); + return Err(TtyError::Other(deno_core::error::not_supported())); } let mut original_mode: DWORD = 0; @@ -115,7 +130,7 @@ fn op_set_raw( if unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) } == FALSE { - return Err(Error::last_os_error().into()); + return Err(TtyError::Io(Error::last_os_error())); } let new_mode = if is_raw { @@ -185,7 +200,7 @@ fn op_set_raw( winapi::um::wincon::WriteConsoleInputW(handle, &record, 1, &mut 0) } == FALSE { - return Err(Error::last_os_error().into()); + return Err(TtyError::Io(Error::last_os_error())); } /* Wait for read thread to acknowledge the cancellation to ensure that nothing @@ -199,7 +214,7 @@ fn op_set_raw( // SAFETY: winapi call if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == FALSE { - return Err(Error::last_os_error().into()); + return Err(TtyError::Io(Error::last_os_error())); } Ok(()) @@ -252,7 +267,8 @@ fn op_set_raw( Some(mode) => mode, None => { // Save original mode. - let original_mode = termios::tcgetattr(raw_fd)?; + let original_mode = + termios::tcgetattr(raw_fd).map_err(TtyError::Nix)?; tty_mode_store.set(rid, original_mode.clone()); original_mode } @@ -274,11 +290,13 @@ fn op_set_raw( } raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; - termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?; + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw) + .map_err(TtyError::Nix)?; } else { // Try restore saved mode. if let Some(mode) = tty_mode_store.take(rid) { - termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)?; + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode) + .map_err(TtyError::Nix)?; } } @@ -290,13 +308,16 @@ fn op_set_raw( fn op_console_size( state: &mut OpState, #[buffer] result: &mut [u32], -) -> Result<(), AnyError> { +) -> Result<(), TtyError> { fn check_console_size( state: &mut OpState, result: &mut [u32], rid: u32, - ) -> Result<(), AnyError> { - let fd = state.resource_table.get_fd(rid)?; + ) -> Result<(), TtyError> { + let fd = state + .resource_table + .get_fd(rid) + .map_err(TtyError::Resource)?; let size = console_size_from_fd(fd)?; result[0] = size.cols; result[1] = size.rows; @@ -419,7 +440,7 @@ mod tests { pub fn op_read_line_prompt( #[string] prompt_text: &str, #[string] default_value: &str, -) -> Result, AnyError> { +) -> Result, ReadlineError> { let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new() .expect("Failed to create editor."); @@ -439,6 +460,6 @@ pub fn op_read_line_prompt( Ok(None) } Err(ReadlineError::Eof) => Ok(None), - Err(err) => Err(err.into()), + Err(err) => Err(err), } } diff --git a/runtime/ops/utils.rs b/runtime/ops/utils.rs deleted file mode 100644 index d5ce61c1fa..0000000000 --- a/runtime/ops/utils.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_core::error::custom_error; -use deno_core::error::AnyError; - -/// A utility function to map OsStrings to Strings -pub fn into_string(s: std::ffi::OsString) -> Result { - s.into_string().map_err(|s| { - let message = format!("File name or path {s:?} is not valid UTF-8"); - custom_error("InvalidData", message) - }) -} diff --git a/runtime/ops/web_worker.rs b/runtime/ops/web_worker.rs index e28bf2192f..d0c3eea668 100644 --- a/runtime/ops/web_worker.rs +++ b/runtime/ops/web_worker.rs @@ -4,15 +4,16 @@ mod sync_fetch; use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerType; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::CancelFuture; use deno_core::OpState; use deno_web::JsMessageData; +use deno_web::MessagePortError; use std::cell::RefCell; use std::rc::Rc; use self::sync_fetch::op_worker_sync_fetch; +pub use sync_fetch::SyncFetchError; deno_core::extension!( deno_web_worker, @@ -30,17 +31,16 @@ deno_core::extension!( fn op_worker_post_message( state: &mut OpState, #[serde] data: JsMessageData, -) -> Result<(), AnyError> { +) -> Result<(), MessagePortError> { let handle = state.borrow::().clone(); - handle.port.send(state, data)?; - Ok(()) + handle.port.send(state, data) } #[op2(async(lazy), fast)] #[serde] async fn op_worker_recv_message( state: Rc>, -) -> Result, AnyError> { +) -> Result, MessagePortError> { let handle = { let state = state.borrow(); state.borrow::().clone() @@ -50,7 +50,6 @@ async fn op_worker_recv_message( .recv(state.clone()) .or_cancel(handle.cancel) .await? - .map_err(|e| e.into()) } #[op2(fast)] diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs index bd55a5fc8c..d1f133d3d2 100644 --- a/runtime/ops/web_worker/sync_fetch.rs +++ b/runtime/ops/web_worker/sync_fetch.rs @@ -4,14 +4,12 @@ use std::sync::Arc; use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerType; -use deno_core::error::custom_error; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::futures::StreamExt; use deno_core::op2; use deno_core::url::Url; use deno_core::OpState; use deno_fetch::data_url::DataUrl; +use deno_fetch::FetchError; use deno_web::BlobStore; use http_body_util::BodyExt; use hyper::body::Bytes; @@ -27,6 +25,32 @@ fn mime_type_essence(mime_type: &str) -> String { essence.trim().to_ascii_lowercase() } +#[derive(Debug, thiserror::Error)] +pub enum SyncFetchError { + #[error("Blob URLs are not supported in this context.")] + BlobUrlsNotSupportedInContext, + #[error("{0}")] + Io(#[from] std::io::Error), + #[error("Invalid script URL")] + InvalidScriptUrl, + #[error("http status error: {0}")] + InvalidStatusCode(http::StatusCode), + #[error("Classic scripts with scheme {0}: are not supported in workers")] + ClassicScriptSchemeUnsupportedInWorkers(String), + #[error("{0}")] + InvalidUri(#[from] http::uri::InvalidUri), + #[error("Invalid MIME type {0:?}.")] + InvalidMimeType(String), + #[error("Missing MIME type.")] + MissingMimeType, + #[error(transparent)] + Fetch(#[from] FetchError), + #[error(transparent)] + Join(#[from] tokio::task::JoinError), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SyncFetchScript { @@ -40,21 +64,22 @@ pub fn op_worker_sync_fetch( state: &mut OpState, #[serde] scripts: Vec, loose_mime_checks: bool, -) -> Result, AnyError> { +) -> Result, SyncFetchError> { let handle = state.borrow::().clone(); assert_eq!(handle.worker_type, WebWorkerType::Classic); // it's not safe to share a client across tokio runtimes, so create a fresh one // https://github.com/seanmonstar/reqwest/issues/1148#issuecomment-910868788 let options = state.borrow::().clone(); - let client = deno_fetch::create_client_from_options(&options)?; + let client = deno_fetch::create_client_from_options(&options) + .map_err(FetchError::ClientCreate)?; // TODO(andreubotella) It's not good to throw an exception related to blob // URLs when none of the script URLs use the blob scheme. // Also, in which contexts are blob URLs not supported? let blob_store = state .try_borrow::>() - .ok_or_else(|| type_error("Blob URLs are not supported in this context."))? + .ok_or(SyncFetchError::BlobUrlsNotSupportedInContext)? .clone(); // TODO(andreubotella): make the below thread into a resource that can be @@ -74,7 +99,7 @@ pub fn op_worker_sync_fetch( let blob_store = blob_store.clone(); deno_core::unsync::spawn(async move { let script_url = Url::parse(&script) - .map_err(|_| type_error("Invalid script URL"))?; + .map_err(|_| SyncFetchError::InvalidScriptUrl)?; let mut loose_mime_checks = loose_mime_checks; let (body, mime_type, res_url) = match script_url.scheme() { @@ -86,15 +111,13 @@ pub fn op_worker_sync_fetch( ); *req.uri_mut() = script_url.as_str().parse()?; - let resp = client.send(req).await?; + let resp = + client.send(req).await.map_err(FetchError::ClientSend)?; if resp.status().is_client_error() || resp.status().is_server_error() { - return Err(type_error(format!( - "http status error: {}", - resp.status() - ))); + return Err(SyncFetchError::InvalidStatusCode(resp.status())); } // TODO(andreubotella) Properly run fetch's "extract a MIME type". @@ -107,30 +130,32 @@ pub fn op_worker_sync_fetch( // Always check the MIME type with HTTP(S). loose_mime_checks = false; - let body = resp.collect().await?.to_bytes(); + let body = resp + .collect() + .await + .map_err(SyncFetchError::Other)? + .to_bytes(); (body, mime_type, script) } "data" => { - let data_url = DataUrl::process(&script) - .map_err(|e| type_error(format!("{e:?}")))?; + let data_url = + DataUrl::process(&script).map_err(FetchError::DataUrl)?; let mime_type = { let mime = data_url.mime_type(); format!("{}/{}", mime.type_, mime.subtype) }; - let (body, _) = data_url - .decode_to_vec() - .map_err(|e| type_error(format!("{e:?}")))?; + let (body, _) = + data_url.decode_to_vec().map_err(FetchError::Base64)?; (Bytes::from(body), Some(mime_type), script) } "blob" => { - let blob = - blob_store.get_object_url(script_url).ok_or_else(|| { - type_error("Blob for the given URL not found.") - })?; + let blob = blob_store + .get_object_url(script_url) + .ok_or(FetchError::BlobNotFound)?; let mime_type = mime_type_essence(&blob.media_type); @@ -139,10 +164,11 @@ pub fn op_worker_sync_fetch( (Bytes::from(body), Some(mime_type), script) } _ => { - return Err(type_error(format!( - "Classic scripts with scheme {}: are not supported in workers.", - script_url.scheme() - ))) + return Err( + SyncFetchError::ClassicScriptSchemeUnsupportedInWorkers( + script_url.scheme().to_string(), + ), + ) } }; @@ -151,17 +177,11 @@ pub fn op_worker_sync_fetch( match mime_type.as_deref() { Some("application/javascript" | "text/javascript") => {} Some(mime_type) => { - return Err(custom_error( - "DOMExceptionNetworkError", - format!("Invalid MIME type {mime_type:?}."), - )) - } - None => { - return Err(custom_error( - "DOMExceptionNetworkError", - "Missing MIME type.", + return Err(SyncFetchError::InvalidMimeType( + mime_type.to_string(), )) } + None => return Err(SyncFetchError::MissingMimeType), } } diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs index d85541d51d..82cc949242 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -10,8 +10,6 @@ use crate::web_worker::WorkerControlEvent; use crate::web_worker::WorkerId; use crate::web_worker::WorkerMetadata; use crate::worker::FormatJsErrorFn; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::serde::Deserialize; use deno_core::CancelFuture; @@ -22,6 +20,7 @@ use deno_permissions::ChildPermissionsArg; use deno_permissions::PermissionsContainer; use deno_web::deserialize_js_transferables; use deno_web::JsMessageData; +use deno_web::MessagePortError; use log::debug; use std::cell::RefCell; use std::collections::HashMap; @@ -119,6 +118,20 @@ pub struct CreateWorkerArgs { close_on_idle: bool, } +#[derive(Debug, thiserror::Error)] +pub enum CreateWorkerError { + #[error("Classic workers are not supported.")] + ClassicWorkers, + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[error(transparent)] + ModuleResolution(#[from] deno_core::ModuleResolutionError), + #[error(transparent)] + MessagePort(#[from] MessagePortError), + #[error("{0}")] + Io(#[from] std::io::Error), +} + /// Create worker as the host #[op2] #[serde] @@ -126,7 +139,7 @@ fn op_create_worker( state: &mut OpState, #[serde] args: CreateWorkerArgs, #[serde] maybe_worker_metadata: Option, -) -> Result { +) -> Result { let specifier = args.specifier.clone(); let maybe_source_code = if args.has_source_code { Some(args.source_code.clone()) @@ -137,10 +150,7 @@ fn op_create_worker( let worker_type = args.worker_type; if let WebWorkerType::Classic = worker_type { if let TestingFeaturesEnabled(false) = state.borrow() { - return Err(custom_error( - "DOMExceptionNotSupportedError", - "Classic workers are not supported.", - )); + return Err(CreateWorkerError::ClassicWorkers); } } @@ -154,7 +164,9 @@ fn op_create_worker( let parent_permissions = state.borrow_mut::(); let worker_permissions = if let Some(child_permissions_arg) = args.permissions { - parent_permissions.create_child_permissions(child_permissions_arg)? + parent_permissions + .create_child_permissions(child_permissions_arg) + .map_err(CreateWorkerError::Permission)? } else { parent_permissions.clone() }; @@ -166,9 +178,8 @@ fn op_create_worker( let module_specifier = deno_core::resolve_url(&specifier)?; let worker_name = args_name.unwrap_or_default(); - let (handle_sender, handle_receiver) = std::sync::mpsc::sync_channel::< - Result, - >(1); + let (handle_sender, handle_receiver) = + std::sync::mpsc::sync_channel::(1); // Setup new thread let thread_builder = std::thread::Builder::new().name(format!("{worker_id}")); @@ -202,7 +213,7 @@ fn op_create_worker( }); // Send thread safe handle from newly created worker to host thread - handle_sender.send(Ok(external_handle)).unwrap(); + handle_sender.send(external_handle).unwrap(); drop(handle_sender); // At this point the only method of communication with host @@ -218,7 +229,7 @@ fn op_create_worker( })?; // Receive WebWorkerHandle from newly created worker - let worker_handle = handle_receiver.recv().unwrap()?; + let worker_handle = handle_receiver.recv().unwrap(); let worker_thread = WorkerThread { worker_handle: worker_handle.into(), @@ -291,7 +302,7 @@ fn close_channel( async fn op_host_recv_ctrl( state: Rc>, #[serde] id: WorkerId, -) -> Result { +) -> WorkerControlEvent { let (worker_handle, cancel_handle) = { let state = state.borrow(); let workers_table = state.borrow::(); @@ -300,7 +311,7 @@ async fn op_host_recv_ctrl( (handle.worker_handle.clone(), handle.cancel_handle.clone()) } else { // If handle was not found it means worker has already shutdown - return Ok(WorkerControlEvent::Close); + return WorkerControlEvent::Close; } }; @@ -309,22 +320,21 @@ async fn op_host_recv_ctrl( .or_cancel(cancel_handle) .await; match maybe_event { - Ok(Ok(Some(event))) => { + Ok(Some(event)) => { // Terminal error means that worker should be removed from worker table. if let WorkerControlEvent::TerminalError(_) = &event { close_channel(state, id, WorkerChannel::Ctrl); } - Ok(event) + event } - Ok(Ok(None)) => { + Ok(None) => { // If there was no event from worker it means it has already been closed. close_channel(state, id, WorkerChannel::Ctrl); - Ok(WorkerControlEvent::Close) + WorkerControlEvent::Close } - Ok(Err(err)) => Err(err), Err(_) => { // The worker was terminated. - Ok(WorkerControlEvent::Close) + WorkerControlEvent::Close } } } @@ -334,7 +344,7 @@ async fn op_host_recv_ctrl( async fn op_host_recv_message( state: Rc>, #[serde] id: WorkerId, -) -> Result, AnyError> { +) -> Result, MessagePortError> { let (worker_handle, cancel_handle) = { let s = state.borrow(); let workers_table = s.borrow::(); @@ -359,7 +369,7 @@ async fn op_host_recv_message( } Ok(ret) } - Ok(Err(err)) => Err(err.into()), + Ok(Err(err)) => Err(err), Err(_) => { // The worker was terminated. Ok(None) @@ -373,7 +383,7 @@ fn op_host_post_message( state: &mut OpState, #[serde] id: WorkerId, #[serde] data: JsMessageData, -) -> Result<(), AnyError> { +) -> Result<(), MessagePortError> { if let Some(worker_thread) = state.borrow::().get(&id) { debug!("post message to worker {}", id); let worker_handle = worker_thread.worker_handle.clone(); diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index f560ce17ec..04cd3305e5 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -166,7 +166,10 @@ pub struct WebWorkerInternalHandle { impl WebWorkerInternalHandle { /// Post WorkerEvent to parent as a worker - pub fn post_event(&self, event: WorkerControlEvent) -> Result<(), AnyError> { + pub fn post_event( + &self, + event: WorkerControlEvent, + ) -> Result<(), mpsc::TrySendError> { let mut sender = self.sender.clone(); // If the channel is closed, // the worker must have terminated but the termination message has not yet been received. @@ -176,8 +179,7 @@ impl WebWorkerInternalHandle { self.has_terminated.store(true, Ordering::SeqCst); return Ok(()); } - sender.try_send(event)?; - Ok(()) + sender.try_send(event) } /// Check if this worker is terminated or being terminated @@ -263,11 +265,9 @@ impl WebWorkerHandle { /// Get the WorkerEvent with lock /// Return error if more than one listener tries to get event #[allow(clippy::await_holding_refcell_ref)] // TODO(ry) remove! - pub async fn get_control_event( - &self, - ) -> Result, AnyError> { + pub async fn get_control_event(&self) -> Option { let mut receiver = self.receiver.borrow_mut(); - Ok(receiver.next().await) + receiver.next().await } /// Terminate the worker diff --git a/tests/unit/signal_test.ts b/tests/unit/signal_test.ts index 65b5ba78e1..8923aa75bf 100644 --- a/tests/unit/signal_test.ts +++ b/tests/unit/signal_test.ts @@ -5,101 +5,101 @@ Deno.test( { ignore: Deno.build.os !== "windows" }, function signalsNotImplemented() { const msg = - "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK)."; + "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got "; assertThrows( () => { Deno.addSignalListener("SIGALRM", () => {}); }, Error, - msg, + msg + "SIGALRM", ); assertThrows( () => { Deno.addSignalListener("SIGCHLD", () => {}); }, Error, - msg, + msg + "SIGCHLD", ); assertThrows( () => { Deno.addSignalListener("SIGHUP", () => {}); }, Error, - msg, + msg + "SIGHUP", ); assertThrows( () => { Deno.addSignalListener("SIGIO", () => {}); }, Error, - msg, + msg + "SIGIO", ); assertThrows( () => { Deno.addSignalListener("SIGPIPE", () => {}); }, Error, - msg, + msg + "SIGPIPE", ); assertThrows( () => { Deno.addSignalListener("SIGQUIT", () => {}); }, Error, - msg, + msg + "SIGQUIT", ); assertThrows( () => { Deno.addSignalListener("SIGTERM", () => {}); }, Error, - msg, + msg + "SIGTERM", ); assertThrows( () => { Deno.addSignalListener("SIGUSR1", () => {}); }, Error, - msg, + msg + "SIGUSR1", ); assertThrows( () => { Deno.addSignalListener("SIGUSR2", () => {}); }, Error, - msg, + msg + "SIGUSR2", ); assertThrows( () => { Deno.addSignalListener("SIGWINCH", () => {}); }, Error, - msg, + msg + "SIGWINCH", ); assertThrows( () => Deno.addSignalListener("SIGKILL", () => {}), Error, - msg, + msg + "SIGKILL", ); assertThrows( () => Deno.addSignalListener("SIGSTOP", () => {}), Error, - msg, + msg + "SIGSTOP", ); assertThrows( () => Deno.addSignalListener("SIGILL", () => {}), Error, - msg, + msg + "SIGILL", ); assertThrows( () => Deno.addSignalListener("SIGFPE", () => {}), Error, - msg, + msg + "SIGFPE", ); assertThrows( () => Deno.addSignalListener("SIGSEGV", () => {}), Error, - msg, + msg + "SIGSEGV", ); }, );