diff --git a/Cargo.lock b/Cargo.lock index 9e6b6bdb7f..17e581016b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3996,6 +3996,7 @@ dependencies = [ "thiserror 2.0.12", "typetag", "web-time", + "windows 0.56.0", "windows-sys 0.48.0", ] diff --git a/crates/nu-cli/src/commands/history/history_.rs b/crates/nu-cli/src/commands/history/history_.rs index ff6746527d..10f0a211de 100644 --- a/crates/nu-cli/src/commands/history/history_.rs +++ b/crates/nu-cli/src/commands/history/history_.rs @@ -1,5 +1,8 @@ use nu_engine::command_prelude::*; -use nu_protocol::{HistoryFileFormat, shell_error::io::IoError}; +use nu_protocol::{ + HistoryFileFormat, + shell_error::{self, io::IoError}, +}; use reedline::{ FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery, SqliteBackedHistory, @@ -94,7 +97,7 @@ impl Command for History { }) }) .ok_or(IoError::new( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::FileNotFound, head, history_path, ))? @@ -110,7 +113,7 @@ impl Command for History { }) }) .ok_or(IoError::new( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::FileNotFound, head, history_path, ))? diff --git a/crates/nu-cli/src/commands/history/history_import.rs b/crates/nu-cli/src/commands/history/history_import.rs index 422de40422..360273adc9 100644 --- a/crates/nu-cli/src/commands/history/history_import.rs +++ b/crates/nu-cli/src/commands/history/history_import.rs @@ -287,7 +287,7 @@ fn backup(path: &Path, span: Span) -> Result, ShellError> { Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None), Err(e) => { return Err(IoError::new_internal( - e.kind(), + e, "Could not get metadata", nu_protocol::location!(), ) @@ -297,7 +297,7 @@ fn backup(path: &Path, span: Span) -> Result, ShellError> { let bak_path = find_backup_path(path, span)?; std::fs::copy(path, &bak_path).map_err(|err| { IoError::new_internal( - err.kind(), + err.not_found_as(NotFound::File), "Could not copy backup", nu_protocol::location!(), ) diff --git a/crates/nu-cli/src/commands/keybindings_listen.rs b/crates/nu-cli/src/commands/keybindings_listen.rs index 366d19b1dd..2232b9d4ad 100644 --- a/crates/nu-cli/src/commands/keybindings_listen.rs +++ b/crates/nu-cli/src/commands/keybindings_listen.rs @@ -42,7 +42,7 @@ impl Command for KeybindingsListen { Err(e) => { terminal::disable_raw_mode().map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not disable raw mode", nu_protocol::location!(), ) @@ -71,18 +71,10 @@ pub fn print_events(engine_state: &EngineState) -> Result { let config = engine_state.get_config(); stdout().flush().map_err(|err| { - IoError::new_internal( - err.kind(), - "Could not flush stdout", - nu_protocol::location!(), - ) + IoError::new_internal(err, "Could not flush stdout", nu_protocol::location!()) })?; terminal::enable_raw_mode().map_err(|err| { - IoError::new_internal( - err.kind(), - "Could not enable raw mode", - nu_protocol::location!(), - ) + IoError::new_internal(err, "Could not enable raw mode", nu_protocol::location!()) })?; if config.use_kitty_protocol { @@ -114,7 +106,7 @@ pub fn print_events(engine_state: &EngineState) -> Result { loop { let event = crossterm::event::read().map_err(|err| { - IoError::new_internal(err.kind(), "Could not read event", nu_protocol::location!()) + IoError::new_internal(err, "Could not read event", nu_protocol::location!()) })?; if event == Event::Key(KeyCode::Esc.into()) { break; @@ -136,7 +128,7 @@ pub fn print_events(engine_state: &EngineState) -> Result { }; stdout.queue(crossterm::style::Print(o)).map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not print output record", nu_protocol::location!(), ) @@ -144,14 +136,10 @@ pub fn print_events(engine_state: &EngineState) -> Result { stdout .queue(crossterm::style::Print("\r\n")) .map_err(|err| { - IoError::new_internal( - err.kind(), - "Could not print linebreak", - nu_protocol::location!(), - ) + IoError::new_internal(err, "Could not print linebreak", nu_protocol::location!()) })?; stdout.flush().map_err(|err| { - IoError::new_internal(err.kind(), "Could not flush", nu_protocol::location!()) + IoError::new_internal(err, "Could not flush", nu_protocol::location!()) })?; } @@ -163,11 +151,7 @@ pub fn print_events(engine_state: &EngineState) -> Result { } terminal::disable_raw_mode().map_err(|err| { - IoError::new_internal( - err.kind(), - "Could not disable raw mode", - nu_protocol::location!(), - ) + IoError::new_internal(err, "Could not disable raw mode", nu_protocol::location!()) })?; Ok(Value::nothing(Span::unknown())) diff --git a/crates/nu-cli/src/config_files.rs b/crates/nu-cli/src/config_files.rs index 9bd46afd10..89b93111ab 100644 --- a/crates/nu-cli/src/config_files.rs +++ b/crates/nu-cli/src/config_files.rs @@ -80,7 +80,7 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option bool { if let Err(err) = std::fs::File::create(&new_plugin_file_path) .map_err(|err| { IoError::new_internal_with_path( - err.kind(), + err, "Could not create new plugin file", nu_protocol::location!(), new_plugin_file_path.clone(), diff --git a/crates/nu-cli/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs index 70444a2623..14d3e3981b 100644 --- a/crates/nu-cli/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -28,7 +28,7 @@ pub fn evaluate_file( let file_path = canonicalize_with(&path, cwd).map_err(|err| { IoError::new_internal_with_path( - err.kind().not_found_as(NotFound::File), + err.not_found_as(NotFound::File), "Could not access file", nu_protocol::location!(), PathBuf::from(&path), @@ -47,7 +47,7 @@ pub fn evaluate_file( let file = std::fs::read(&file_path).map_err(|err| { IoError::new_internal_with_path( - err.kind().not_found_as(NotFound::File), + err.not_found_as(NotFound::File), "Could not read file", nu_protocol::location!(), file_path.clone(), diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 7cfb3adcab..a4ca2903cc 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -22,6 +22,7 @@ use nu_color_config::StyleComputer; use nu_engine::env_to_strings; use nu_engine::exit::cleanup_exit; use nu_parser::{lex, parse, trim_quotes_str}; +use nu_protocol::shell_error; use nu_protocol::shell_error::io::IoError; use nu_protocol::{ HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value, @@ -854,7 +855,7 @@ fn do_auto_cd( report_shell_error( engine_state, &ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::DirectoryNotFound, span, PathBuf::from(&path), "Cannot change directory", @@ -868,7 +869,7 @@ fn do_auto_cd( report_shell_error( engine_state, &ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::PermissionDenied, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::PermissionDenied), span, PathBuf::from(path), "Cannot change directory", diff --git a/crates/nu-cmd-extra/src/extra/strings/format/bits.rs b/crates/nu-cmd-extra/src/extra/strings/format/bits.rs index d4ec473879..f8d930914c 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/bits.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/bits.rs @@ -134,7 +134,7 @@ fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream { let mut byte = [0]; if reader .read(&mut byte[..]) - .map_err(|err| IoError::new(err.kind(), head, None))? + .map_err(|err| IoError::new(err, head, None))? > 0 { // Format the byte as bits diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index 6b43f0a3fb..6c94f9eaa1 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -107,14 +107,14 @@ impl Command for Do { let mut buf = Vec::new(); stdout.read_to_end(&mut buf).map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not read stdout to end", nu_protocol::location!(), ) })?; Ok::<_, ShellError>(buf) }) - .map_err(|err| IoError::new(err.kind(), head, None)) + .map_err(|err| IoError::new(err, head, None)) }) .transpose()?; @@ -126,7 +126,7 @@ impl Command for Do { let mut buf = String::new(); stderr .read_to_string(&mut buf) - .map_err(|err| IoError::new(err.kind(), span, None))?; + .map_err(|err| IoError::new(err, span, None))?; buf } }; diff --git a/crates/nu-cmd-plugin/src/commands/plugin/add.rs b/crates/nu-cmd-plugin/src/commands/plugin/add.rs index 355112f673..1e9b4411ad 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/add.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/add.rs @@ -88,13 +88,19 @@ apparent the next time `nu` is next launched with that plugin registry file. let filename_expanded = nu_path::locate_in_dirs(&filename.item, &cwd, || { get_plugin_dirs(engine_state, stack) }) - .map_err(|err| IoError::new(err.kind(), filename.span, PathBuf::from(filename.item)))?; + .map_err(|err| { + IoError::new( + err.not_found_as(NotFound::File), + filename.span, + PathBuf::from(filename.item), + ) + })?; let shell_expanded = shell .as_ref() .map(|s| { nu_path::canonicalize_with(&s.item, &cwd) - .map_err(|err| IoError::new(err.kind(), s.span, None)) + .map_err(|err| IoError::new(err, s.span, None)) }) .transpose()?; diff --git a/crates/nu-cmd-plugin/src/util.rs b/crates/nu-cmd-plugin/src/util.rs index e1e39c7a17..223e93b955 100644 --- a/crates/nu-cmd-plugin/src/util.rs +++ b/crates/nu-cmd-plugin/src/util.rs @@ -1,6 +1,10 @@ #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir}; -use nu_protocol::{PluginRegistryFile, engine::StateWorkingSet, shell_error::io::IoError}; +use nu_protocol::{ + PluginRegistryFile, + engine::StateWorkingSet, + shell_error::{self, io::IoError}, +}; use std::{ fs::{self, File}, path::PathBuf, @@ -46,12 +50,12 @@ pub(crate) fn read_plugin_file( if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) { PluginRegistryFile::read_from( File::open(&plugin_registry_file_path) - .map_err(|err| IoError::new(err.kind(), file_span, plugin_registry_file_path))?, + .map_err(|err| IoError::new(err, file_span, plugin_registry_file_path))?, Some(file_span), ) } else if let Some(path) = custom_path { Err(ShellError::Io(IoError::new( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::FileNotFound, path.span, PathBuf::from(&path.item), ))) @@ -75,9 +79,8 @@ pub(crate) fn modify_plugin_file( // Try to read the plugin file if it exists let mut contents = if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) { PluginRegistryFile::read_from( - File::open(&plugin_registry_file_path).map_err(|err| { - IoError::new(err.kind(), file_span, plugin_registry_file_path.clone()) - })?, + File::open(&plugin_registry_file_path) + .map_err(|err| IoError::new(err, file_span, plugin_registry_file_path.clone()))?, Some(file_span), )? } else { @@ -90,7 +93,7 @@ pub(crate) fn modify_plugin_file( // Save the modified file on success contents.write_to( File::create(&plugin_registry_file_path) - .map_err(|err| IoError::new(err.kind(), file_span, plugin_registry_file_path))?, + .map_err(|err| IoError::new(err, file_span, plugin_registry_file_path))?, Some(span), )?; diff --git a/crates/nu-command/src/bytes/ends_with.rs b/crates/nu-command/src/bytes/ends_with.rs index af4c4bffc4..4047ce5186 100644 --- a/crates/nu-command/src/bytes/ends_with.rs +++ b/crates/nu-command/src/bytes/ends_with.rs @@ -77,7 +77,7 @@ impl Command for BytesEndsWith { Ok(&[]) => break, Ok(buf) => buf, Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, - Err(e) => return Err(IoError::new(e.kind(), span, None).into()), + Err(e) => return Err(IoError::new(e, span, None).into()), }; let len = buf.len(); if len >= cap { diff --git a/crates/nu-command/src/bytes/starts_with.rs b/crates/nu-command/src/bytes/starts_with.rs index 69cb0a3d76..0bbed2c032 100644 --- a/crates/nu-command/src/bytes/starts_with.rs +++ b/crates/nu-command/src/bytes/starts_with.rs @@ -72,7 +72,7 @@ impl Command for BytesStartsWith { reader .take(pattern.len() as u64) .read_to_end(&mut start) - .map_err(|err| IoError::new(err.kind(), span, None))?; + .map_err(|err| IoError::new(err, span, None))?; Ok(Value::bool(start == pattern, head).into_pipeline_data()) } else { diff --git a/crates/nu-command/src/database/values/sqlite.rs b/crates/nu-command/src/database/values/sqlite.rs index 642aea1753..1bfa870b8f 100644 --- a/crates/nu-command/src/database/values/sqlite.rs +++ b/crates/nu-command/src/database/values/sqlite.rs @@ -41,12 +41,11 @@ impl SQLiteDatabase { } pub fn try_from_path(path: &Path, span: Span, signals: Signals) -> Result { - let mut file = - File::open(path).map_err(|e| IoError::new(e.kind(), span, PathBuf::from(path)))?; + let mut file = File::open(path).map_err(|e| IoError::new(e, span, PathBuf::from(path)))?; let mut buf: [u8; 16] = [0; 16]; file.read_exact(&mut buf) - .map_err(|e| ShellError::Io(IoError::new(e.kind(), span, PathBuf::from(path)))) + .map_err(|e| ShellError::Io(IoError::new(e, span, PathBuf::from(path)))) .and_then(|_| { if buf == SQLITE_MAGIC_BYTES { Ok(SQLiteDatabase::new(path, signals)) diff --git a/crates/nu-command/src/env/config/config_.rs b/crates/nu-command/src/env/config/config_.rs index 11fe86ee72..fd13768290 100644 --- a/crates/nu-command/src/env/config/config_.rs +++ b/crates/nu-command/src/env/config/config_.rs @@ -115,7 +115,7 @@ pub(super) fn start_editor( let child = child.map_err(|err| { IoError::new_with_additional_context( - err.kind(), + err, call.head, None, "Could not spawn foreground child", diff --git a/crates/nu-command/src/env/config/config_reset.rs b/crates/nu-command/src/env/config/config_reset.rs index eeed056812..ac73aef7c3 100644 --- a/crates/nu-command/src/env/config/config_reset.rs +++ b/crates/nu-command/src/env/config/config_reset.rs @@ -59,7 +59,7 @@ impl Command for ConfigReset { )); if let Err(err) = std::fs::rename(nu_config.clone(), &backup_path) { return Err(ShellError::Io(IoError::new_with_additional_context( - err.kind().not_found_as(NotFound::Directory), + err.not_found_as(NotFound::Directory), span, PathBuf::from(backup_path), "config.nu could not be backed up", @@ -69,7 +69,7 @@ impl Command for ConfigReset { if let Ok(mut file) = std::fs::File::create(&nu_config) { if let Err(err) = writeln!(&mut file, "{config_file}") { return Err(ShellError::Io(IoError::new_with_additional_context( - err.kind().not_found_as(NotFound::File), + err.not_found_as(NotFound::File), span, PathBuf::from(nu_config), "config.nu could not be written to", @@ -86,7 +86,7 @@ impl Command for ConfigReset { backup_path.push(format!("oldenv-{}.nu", Local::now().format("%F-%H-%M-%S"),)); if let Err(err) = std::fs::rename(env_config.clone(), &backup_path) { return Err(ShellError::Io(IoError::new_with_additional_context( - err.kind().not_found_as(NotFound::Directory), + err.not_found_as(NotFound::Directory), span, PathBuf::from(backup_path), "env.nu could not be backed up", @@ -96,7 +96,7 @@ impl Command for ConfigReset { if let Ok(mut file) = std::fs::File::create(&env_config) { if let Err(err) = writeln!(&mut file, "{config_file}") { return Err(ShellError::Io(IoError::new_with_additional_context( - err.kind().not_found_as(NotFound::File), + err.not_found_as(NotFound::File), span, PathBuf::from(env_config), "env.nu could not be written to", diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index b00dcde8e7..4f6394cf54 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -2,7 +2,11 @@ use nu_engine::{ command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return, redirect_env, }; -use nu_protocol::{BlockId, engine::CommandType, shell_error::io::IoError}; +use nu_protocol::{ + BlockId, + engine::CommandType, + shell_error::{self, io::IoError}, +}; use std::path::PathBuf; /// Source a file for environment variables. @@ -66,7 +70,7 @@ impl Command for SourceEnv { PathBuf::from(&path) } else { return Err(ShellError::Io(IoError::new( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::FileNotFound, source_filename.span, PathBuf::from(source_filename.item), ))); diff --git a/crates/nu-command/src/experimental/job_kill.rs b/crates/nu-command/src/experimental/job_kill.rs index 140130d769..316b27a0d7 100644 --- a/crates/nu-command/src/experimental/job_kill.rs +++ b/crates/nu-command/src/experimental/job_kill.rs @@ -53,7 +53,7 @@ impl Command for JobKill { jobs.kill_and_remove(id).map_err(|err| { ShellError::Io(IoError::new_internal( - err.kind(), + err, "Failed to kill the requested job", nu_protocol::location!(), )) diff --git a/crates/nu-command/src/experimental/job_spawn.rs b/crates/nu-command/src/experimental/job_spawn.rs index 05c1d8885e..169d4ce7f9 100644 --- a/crates/nu-command/src/experimental/job_spawn.rs +++ b/crates/nu-command/src/experimental/job_spawn.rs @@ -121,7 +121,7 @@ impl Command for JobSpawn { Err(err) => { jobs.remove_job(id); Err(ShellError::Io(IoError::new_with_additional_context( - err.kind(), + err, call.head, None, "Failed to spawn thread for job", diff --git a/crates/nu-command/src/experimental/job_unfreeze.rs b/crates/nu-command/src/experimental/job_unfreeze.rs index 157f5a8ca8..5b34652ec8 100644 --- a/crates/nu-command/src/experimental/job_unfreeze.rs +++ b/crates/nu-command/src/experimental/job_unfreeze.rs @@ -3,7 +3,6 @@ use nu_protocol::{ JobId, engine::{FrozenJob, Job, ThreadJob}, process::check_ok, - shell_error, }; use nu_system::{ForegroundWaitStatus, kill_by_pid}; @@ -123,7 +122,7 @@ fn unfreeze_job( if !thread_job.try_add_pid(pid) { kill_by_pid(pid.into()).map_err(|err| { ShellError::Io(IoError::new_internal( - err.kind(), + err, "job was interrupted; could not kill foreground process", nu_protocol::location!(), )) @@ -163,7 +162,7 @@ fn unfreeze_job( Ok(ForegroundWaitStatus::Finished(status)) => check_ok(status, false, span), Err(err) => Err(ShellError::Io(IoError::new_internal( - shell_error::io::ErrorKind::Std(err.kind()), + err, "Failed to unfreeze foreground process", nu_protocol::location!(), ))), diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 4d75b767a6..0dc69fb42c 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -77,7 +77,7 @@ impl Command for Cd { if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) { if !path.is_dir() { return Err(shell_error::io::IoError::new( - shell_error::io::ErrorKind::Std( + shell_error::io::ErrorKind::from_std( std::io::ErrorKind::NotADirectory, ), v.span, @@ -106,7 +106,9 @@ impl Command for Cd { }; if !path.is_dir() { return Err(shell_error::io::IoError::new( - shell_error::io::ErrorKind::Std(std::io::ErrorKind::NotADirectory), + shell_error::io::ErrorKind::from_std( + std::io::ErrorKind::NotADirectory, + ), v.span, path, ) @@ -132,9 +134,12 @@ impl Command for Cd { stack.set_cwd(path)?; Ok(PipelineData::empty()) } - PermissionResult::PermissionDenied => { - Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into()) - } + PermissionResult::PermissionDenied => Err(IoError::new( + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::PermissionDenied), + call.head, + path, + ) + .into()), } } diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 57bf8554d0..e887ccfdbf 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -5,7 +5,10 @@ use nu_engine::glob_from; use nu_engine::{command_prelude::*, env::current_dir}; use nu_glob::MatchOptions; use nu_path::{expand_path_with, expand_to_real_path}; -use nu_protocol::{DataSource, NuGlob, PipelineMetadata, Signals, shell_error::io::IoError}; +use nu_protocol::{ + DataSource, NuGlob, PipelineMetadata, Signals, + shell_error::{self, io::IoError}, +}; use pathdiff::diff_paths; use rayon::prelude::*; #[cfg(unix)] @@ -252,7 +255,7 @@ fn ls_for_one_pattern( // it makes no sense to list an empty string. if path.item.as_ref().is_empty() { return Err(ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::NotFound), path.span, PathBuf::from(path.item.to_string()), "empty string('') directory or file does not exist", @@ -357,7 +360,7 @@ fn ls_for_one_pattern( let count = std::thread::available_parallelism() .map_err(|err| { IoError::new_with_additional_context( - err.kind(), + err, call_span, None, "Could not get available parallelism", @@ -793,6 +796,7 @@ fn unix_time_to_local_date_time(secs: i64) -> Option> { mod windows_helper { use super::*; + use nu_protocol::shell_error; use std::os::windows::prelude::OsStrExt; use windows::Win32::Foundation::FILETIME; use windows::Win32::Storage::FileSystem::{ @@ -928,7 +932,7 @@ mod windows_helper { Ok(find_data) } Err(e) => Err(ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::Other, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other), span, PathBuf::from(filename), format!("Could not read metadata: {e}"), @@ -973,11 +977,11 @@ fn read_dir( let signals_clone = signals.clone(); let items = f .read_dir() - .map_err(|err| IoError::new(err.kind(), span, f.clone()))? + .map_err(|err| IoError::new(err, span, f.clone()))? .map(move |d| { signals_clone.check(span)?; d.map(|r| r.path()) - .map_err(|err| IoError::new(err.kind(), span, f.clone())) + .map_err(|err| IoError::new(err, span, f.clone())) .map_err(ShellError::from) }); if !use_threads { diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index ca9e3fc478..521301b530 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -121,7 +121,7 @@ impl Command for Open { if permission_denied(path) { let err = IoError::new( - std::io::ErrorKind::PermissionDenied, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::PermissionDenied), arg_span, PathBuf::from(path), ); @@ -162,14 +162,18 @@ impl Command for Open { // At least under windows this check ensures that we don't get a // permission denied error on directories return Err(ShellError::Io(IoError::new( - shell_error::io::ErrorKind::Std(std::io::ErrorKind::IsADirectory), + #[allow( + deprecated, + reason = "we don't have a IsADirectory variant here, so we provide one" + )] + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::IsADirectory), arg_span, PathBuf::from(path), ))); } let file = std::fs::File::open(path) - .map_err(|err| IoError::new(err.kind(), arg_span, PathBuf::from(path)))?; + .map_err(|err| IoError::new(err, arg_span, PathBuf::from(path)))?; // No content_type by default - Is added later if no converter is found let stream = PipelineData::ByteStream( diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index ac0867420d..67e2402697 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -304,7 +304,7 @@ fn rm( && matches!( e, ShellError::Io(IoError { - kind: shell_error::io::ErrorKind::Std(std::io::ErrorKind::NotFound), + kind: shell_error::io::ErrorKind::Std(std::io::ErrorKind::NotFound, ..), .. }) )) @@ -420,7 +420,7 @@ fn rm( }; if let Err(e) = result { - Err(ShellError::Io(IoError::new(e.kind(), span, f))) + Err(ShellError::Io(IoError::new(e, span, f))) } else if verbose { let msg = if interactive && !confirmed { "not deleted" diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 0d50b63cc7..e8eb175c37 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -130,7 +130,7 @@ impl Command for Save { io::copy(&mut tee, &mut io::stderr()) } } - .map_err(|err| IoError::new(err.kind(), span, None))?; + .map_err(|err| IoError::new(err, span, None))?; } Ok(()) } @@ -428,20 +428,24 @@ fn open_file(path: &Path, span: Span, append: bool) -> Result (true, true) => std::fs::OpenOptions::new() .append(true) .open(path) - .map_err(|err| err.kind().into()), + .map_err(|err| err.into()), _ => { // This is a temporary solution until `std::fs::File::create` is fixed on Windows (rust-lang/rust#134893) // A TOCTOU problem exists here, which may cause wrong error message to be shown #[cfg(target_os = "windows")] if path.is_dir() { - Err(nu_protocol::shell_error::io::ErrorKind::Std( + #[allow( + deprecated, + reason = "we don't get a IsADirectory error, so we need to provide it" + )] + Err(nu_protocol::shell_error::io::ErrorKind::from_std( std::io::ErrorKind::IsADirectory, )) } else { - std::fs::File::create(path).map_err(|err| err.kind().into()) + std::fs::File::create(path).map_err(|err| err.into()) } #[cfg(not(target_os = "windows"))] - std::fs::File::create(path).map_err(|err| err.kind().into()) + std::fs::File::create(path).map_err(|err| err.into()) } }; diff --git a/crates/nu-command/src/filesystem/ucp.rs b/crates/nu-command/src/filesystem/ucp.rs index eaa4c4f626..673f4e34d9 100644 --- a/crates/nu-command/src/filesystem/ucp.rs +++ b/crates/nu-command/src/filesystem/ucp.rs @@ -1,6 +1,9 @@ #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir}; -use nu_protocol::{NuGlob, shell_error::io::IoError}; +use nu_protocol::{ + NuGlob, + shell_error::{self, io::IoError}, +}; use std::path::PathBuf; use uu_cp::{BackupMode, CopyMode, UpdateMode}; @@ -198,7 +201,7 @@ impl Command for UCp { .collect(); if exp_files.is_empty() { return Err(ShellError::Io(IoError::new( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::FileNotFound, p.span, PathBuf::from(p.item.to_string()), ))); diff --git a/crates/nu-command/src/filesystem/umv.rs b/crates/nu-command/src/filesystem/umv.rs index 6a7d6965d8..edee8e3b84 100644 --- a/crates/nu-command/src/filesystem/umv.rs +++ b/crates/nu-command/src/filesystem/umv.rs @@ -1,7 +1,10 @@ #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir}; use nu_path::expand_path_with; -use nu_protocol::{NuGlob, shell_error::io::IoError}; +use nu_protocol::{ + NuGlob, + shell_error::{self, io::IoError}, +}; use std::{ffi::OsString, path::PathBuf}; use uu_mv::{BackupMode, UpdateMode}; @@ -139,7 +142,7 @@ impl Command for UMv { .collect(); if exp_files.is_empty() { return Err(ShellError::Io(IoError::new( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::FileNotFound, p.span, PathBuf::from(p.item.to_string()), ))); diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs index c724b59646..fa261d5377 100644 --- a/crates/nu-command/src/filesystem/utouch.rs +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -227,7 +227,7 @@ impl Command for UTouch { TouchError::ReferenceFileInaccessible(reference_path, io_err) => { let span = reference_span.expect("touch should've been given a reference file"); ShellError::Io(IoError::new_with_additional_context( - io_err.kind(), + io_err, span, reference_path, "failed to read metadata", diff --git a/crates/nu-command/src/filesystem/watch.rs b/crates/nu-command/src/filesystem/watch.rs index 3eba5f5312..c6cc717fa8 100644 --- a/crates/nu-command/src/filesystem/watch.rs +++ b/crates/nu-command/src/filesystem/watch.rs @@ -86,7 +86,7 @@ impl Command for Watch { Ok(p) => p, Err(err) => { return Err(ShellError::Io(IoError::new( - err.kind(), + err, path_arg.span, PathBuf::from(path_no_whitespace), ))); diff --git a/crates/nu-command/src/filters/empty.rs b/crates/nu-command/src/filters/empty.rs index 6758d1f269..18271ef40c 100644 --- a/crates/nu-command/src/filters/empty.rs +++ b/crates/nu-command/src/filters/empty.rs @@ -37,7 +37,7 @@ pub fn empty( .bytes() .next() .transpose() - .map_err(|err| IoError::new(err.kind(), span, None))? + .map_err(|err| IoError::new(err, span, None))? .is_none(); if negate { Ok(Value::bool(!is_empty, head).into_pipeline_data()) diff --git a/crates/nu-command/src/filters/first.rs b/crates/nu-command/src/filters/first.rs index 78706e4432..4df5798016 100644 --- a/crates/nu-command/src/filters/first.rs +++ b/crates/nu-command/src/filters/first.rs @@ -182,7 +182,7 @@ fn first_helper( let mut byte = [0u8]; if reader .read(&mut byte) - .map_err(|err| IoError::new(err.kind(), span, None))? + .map_err(|err| IoError::new(err, span, None))? > 0 { Ok(Value::int(byte[0] as i64, head).into_pipeline_data()) diff --git a/crates/nu-command/src/filters/interleave.rs b/crates/nu-command/src/filters/interleave.rs index f643c957c2..499800d7b0 100644 --- a/crates/nu-command/src/filters/interleave.rs +++ b/crates/nu-command/src/filters/interleave.rs @@ -137,7 +137,7 @@ interleave } }) .map(|_| ()) - .map_err(|err| IoError::new(err.kind(), head, None).into()) + .map_err(|err| IoError::new(err, head, None).into()) }) })?; diff --git a/crates/nu-command/src/filters/last.rs b/crates/nu-command/src/filters/last.rs index 77b57880f6..b44bb98b5a 100644 --- a/crates/nu-command/src/filters/last.rs +++ b/crates/nu-command/src/filters/last.rs @@ -166,7 +166,7 @@ impl Command for Last { let mut buf = VecDeque::with_capacity(rows + TAKE as usize); loop { let taken = std::io::copy(&mut (&mut reader).take(TAKE), &mut buf) - .map_err(|err| IoError::new(err.kind(), span, None))?; + .map_err(|err| IoError::new(err, span, None))?; if buf.len() > rows { buf.drain(..(buf.len() - rows)); } diff --git a/crates/nu-command/src/filters/tee.rs b/crates/nu-command/src/filters/tee.rs index 8d7e29ed95..1b3179070f 100644 --- a/crates/nu-command/src/filters/tee.rs +++ b/crates/nu-command/src/filters/tee.rs @@ -1,6 +1,8 @@ use nu_engine::{command_prelude::*, get_eval_block_with_early_return}; #[cfg(feature = "os")] use nu_protocol::process::ChildPipe; +#[cfg(test)] +use nu_protocol::shell_error; use nu_protocol::{ ByteStream, ByteStreamSource, OutDest, PipelineMetadata, Signals, byte_stream::copy_with_signals, engine::Closure, report_shell_error, shell_error::io::IoError, @@ -440,7 +442,7 @@ fn spawn_tee( eval_block(PipelineData::ByteStream(stream, info.metadata)) }) .map_err(|err| { - IoError::new_with_additional_context(err.kind(), info.span, None, "Could not spawn tee") + IoError::new_with_additional_context(err, info.span, None, "Could not spawn tee") })?; Ok(TeeThread { sender, thread }) @@ -481,13 +483,8 @@ fn copy_on_thread( Ok(()) }) .map_err(|err| { - IoError::new_with_additional_context( - err.kind(), - span, - None, - "Could not spawn stderr copier", - ) - .into() + IoError::new_with_additional_context(err, span, None, "Could not spawn stderr copier") + .into() }) } @@ -532,7 +529,7 @@ fn tee_forwards_errors_back_immediately() { let slow_input = (0..100).inspect(|_| std::thread::sleep(Duration::from_millis(1))); let iter = tee(slow_input, |_| { Err(ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::Other, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other), Span::test_data(), None, "test", @@ -564,7 +561,7 @@ fn tee_waits_for_the_other_thread() { std::thread::sleep(Duration::from_millis(10)); waited_clone.store(true, Ordering::Relaxed); Err(ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::Other, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other), Span::test_data(), None, "test", diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index ba3995c38e..d76caacceb 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -134,7 +134,7 @@ fn read_json_lines( .lines() .filter(|line| line.as_ref().is_ok_and(|line| !line.trim().is_empty()) || line.is_err()) .map(move |line| { - let line = line.map_err(|err| IoError::new(err.kind(), span, None))?; + let line = line.map_err(|err| IoError::new(err, span, None))?; if strict { convert_string_to_value_strict(&line, span) } else { diff --git a/crates/nu-command/src/formats/to/delimited.rs b/crates/nu-command/src/formats/to/delimited.rs index b8303cb0a8..1062b7e6ca 100644 --- a/crates/nu-command/src/formats/to/delimited.rs +++ b/crates/nu-command/src/formats/to/delimited.rs @@ -8,7 +8,7 @@ use std::{iter, sync::Arc}; fn make_csv_error(error: csv::Error, format_name: &str, head: Span) -> ShellError { if let csv::ErrorKind::Io(error) = error.kind() { - IoError::new(error.kind(), head, None).into() + IoError::new(error, head, None).into() } else { ShellError::GenericError { error: format!("Failed to generate {format_name} data"), diff --git a/crates/nu-command/src/formats/to/msgpack.rs b/crates/nu-command/src/formats/to/msgpack.rs index 7f072a87a3..cb99824107 100644 --- a/crates/nu-command/src/formats/to/msgpack.rs +++ b/crates/nu-command/src/formats/to/msgpack.rs @@ -152,7 +152,7 @@ impl From for ShellError { help: None, inner: vec![], }, - WriteError::Io(err, span) => ShellError::Io(IoError::new(err.kind(), span, None)), + WriteError::Io(err, span) => ShellError::Io(IoError::new(err, span, None)), WriteError::Shell(err) => *err, } } diff --git a/crates/nu-command/src/formats/to/msgpackz.rs b/crates/nu-command/src/formats/to/msgpackz.rs index c76aa17d60..3ccc0e166b 100644 --- a/crates/nu-command/src/formats/to/msgpackz.rs +++ b/crates/nu-command/src/formats/to/msgpackz.rs @@ -95,7 +95,7 @@ impl Command for ToMsgpackz { serialize_types, )?; out.flush() - .map_err(|err| IoError::new(err.kind(), call.head, None))?; + .map_err(|err| IoError::new(err, call.head, None))?; drop(out); Ok(Value::binary(out_buf, call.head).into_pipeline_data()) diff --git a/crates/nu-command/src/misc/source.rs b/crates/nu-command/src/misc/source.rs index c098fee9b0..237356d74d 100644 --- a/crates/nu-command/src/misc/source.rs +++ b/crates/nu-command/src/misc/source.rs @@ -55,13 +55,8 @@ impl Command for Source { let cwd = engine_state.cwd_as_string(Some(stack))?; let pb = std::path::PathBuf::from(block_id_name); let parent = pb.parent().unwrap_or(std::path::Path::new("")); - let file_path = canonicalize_with(pb.as_path(), cwd).map_err(|err| { - IoError::new( - err.kind().not_found_as(NotFound::File), - call.head, - pb.clone(), - ) - })?; + let file_path = canonicalize_with(pb.as_path(), cwd) + .map_err(|err| IoError::new(err.not_found_as(NotFound::File), call.head, pb.clone()))?; // Note: We intentionally left out PROCESS_PATH since it's supposed to // to work like argv[0] in C, which is the name of the program being executed. diff --git a/crates/nu-command/src/network/http/client.rs b/crates/nu-command/src/network/http/client.rs index d5281812bd..4d5529b48c 100644 --- a/crates/nu-command/src/network/http/client.rs +++ b/crates/nu-command/src/network/http/client.rs @@ -374,9 +374,7 @@ fn send_multipart_request( let mut builder = MultipartWriter::new(); let err = |e: std::io::Error| { - ShellErrorOrRequestError::ShellError( - IoError::new_with_additional_context(e.kind(), span, None, e).into(), - ) + ShellErrorOrRequestError::ShellError(IoError::new(e, span, None).into()) }; for (col, val) in val.into_owned() { @@ -466,12 +464,7 @@ fn send_cancellable_request( let _ = tx.send(ret); // may fail if the user has cancelled the operation }) .map_err(|err| { - IoError::new_with_additional_context( - err.kind(), - span, - None, - "Could not spawn HTTP requester", - ) + IoError::new_with_additional_context(err, span, None, "Could not spawn HTTP requester") }) .map_err(ShellError::from)?; @@ -529,12 +522,7 @@ fn send_cancellable_request_bytes( let _ = tx.send(ret); }) .map_err(|err| { - IoError::new_with_additional_context( - err.kind(), - span, - None, - "Could not spawn HTTP requester", - ) + IoError::new_with_additional_context(err, span, None, "Could not spawn HTTP requester") }) .map_err(ShellError::from)?; @@ -685,7 +673,7 @@ fn handle_response_error(span: Span, requested_url: &str, response_err: Error) - break 'io generic_network_failure(); }; - ShellError::Io(IoError::new(io_error.kind(), span, None)) + ShellError::Io(IoError::new(io_error, span, None)) } _ => generic_network_failure(), } diff --git a/crates/nu-command/src/network/port.rs b/crates/nu-command/src/network/port.rs index 28d52c07f0..bdf391af92 100644 --- a/crates/nu-command/src/network/port.rs +++ b/crates/nu-command/src/network/port.rs @@ -132,7 +132,7 @@ fn get_free_port( } Err(IoError::new_with_additional_context( - last_err.expect("range not empty, validated before").kind(), + last_err.expect("range not empty, validated before"), range_span, None, "Every port has been tried, but no valid one was found", diff --git a/crates/nu-command/src/path/exists.rs b/crates/nu-command/src/path/exists.rs index d90800f518..acae7a7801 100644 --- a/crates/nu-command/src/path/exists.rs +++ b/crates/nu-command/src/path/exists.rs @@ -153,7 +153,7 @@ fn exists(path: &Path, span: Span, args: &Arguments) -> Value { Value::bool( match exists { Ok(exists) => exists, - Err(err) => return Value::error(IoError::new(err.kind(), span, path).into(), span), + Err(err) => return Value::error(IoError::new(err, span, path).into(), span), }, span, ) diff --git a/crates/nu-command/src/path/self_.rs b/crates/nu-command/src/path/self_.rs index 4ed74dce16..7c241b1185 100644 --- a/crates/nu-command/src/path/self_.rs +++ b/crates/nu-command/src/path/self_.rs @@ -1,6 +1,9 @@ use nu_engine::command_prelude::*; use nu_path::expand_path_with; -use nu_protocol::{engine::StateWorkingSet, shell_error::io::IoError}; +use nu_protocol::{ + engine::StateWorkingSet, + shell_error::{self, io::IoError}, +}; #[derive(Clone)] pub struct PathSelf; @@ -56,7 +59,7 @@ impl Command for PathSelf { let cwd = working_set.permanent_state.cwd(None)?; let current_file = working_set.files.top().ok_or_else(|| { IoError::new_with_additional_context( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::FileNotFound, call.head, None, "Couldn't find current file", @@ -67,7 +70,7 @@ impl Command for PathSelf { let dir = expand_path_with( current_file.parent().ok_or_else(|| { IoError::new_with_additional_context( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::FileNotFound, call.head, current_file.to_owned(), "Couldn't find current file's parent.", diff --git a/crates/nu-command/src/path/type.rs b/crates/nu-command/src/path/type.rs index bdd5d49966..d837719cd1 100644 --- a/crates/nu-command/src/path/type.rs +++ b/crates/nu-command/src/path/type.rs @@ -108,7 +108,7 @@ fn path_type(path: &Path, span: Span, args: &Arguments) -> Value { match path.symlink_metadata() { Ok(metadata) => Value::string(get_file_type(&metadata), span), Err(err) if err.kind() == io::ErrorKind::NotFound => Value::nothing(span), - Err(err) => Value::error(IoError::new(err.kind(), span, None).into(), span), + Err(err) => Value::error(IoError::new(err, span, None).into(), span), } } diff --git a/crates/nu-command/src/platform/dir_info.rs b/crates/nu-command/src/platform/dir_info.rs index 51e19f4315..6f64007019 100644 --- a/crates/nu-command/src/platform/dir_info.rs +++ b/crates/nu-command/src/platform/dir_info.rs @@ -77,7 +77,7 @@ impl FileInfo { long, }) } - Err(e) => Err(IoError::new(e.kind(), tag, path).into()), + Err(e) => Err(IoError::new(e, tag, path).into()), } } } diff --git a/crates/nu-command/src/platform/input/input_.rs b/crates/nu-command/src/platform/input/input_.rs index 5e088098d2..ff59a4f975 100644 --- a/crates/nu-command/src/platform/input/input_.rs +++ b/crates/nu-command/src/platform/input/input_.rs @@ -7,7 +7,7 @@ use crossterm::{ }; use itertools::Itertools; use nu_engine::command_prelude::*; -use nu_protocol::shell_error::io::IoError; +use nu_protocol::shell_error::{self, io::IoError}; use std::{io::Write, time::Duration}; @@ -116,7 +116,9 @@ impl Command for Input { crossterm::terminal::disable_raw_mode() .map_err(&from_io_error)?; return Err(IoError::new( - std::io::ErrorKind::Interrupted, + shell_error::io::ErrorKind::from_std( + std::io::ErrorKind::Interrupted, + ), call.head, None, ) @@ -156,7 +158,7 @@ impl Command for Input { terminal::Clear(ClearType::CurrentLine), cursor::MoveToColumn(0), ) - .map_err(|err| IoError::new(err.kind(), call.head, None))?; + .map_err(|err| IoError::new(err, call.head, None))?; if let Some(prompt) = &prompt { execute!(std::io::stdout(), Print(prompt.to_string())) .map_err(&from_io_error)?; diff --git a/crates/nu-command/src/platform/input/input_listen.rs b/crates/nu-command/src/platform/input/input_listen.rs index 56241d6689..2fccaeb7c3 100644 --- a/crates/nu-command/src/platform/input/input_listen.rs +++ b/crates/nu-command/src/platform/input/input_listen.rs @@ -84,7 +84,7 @@ There are 4 `key_type` variants: let add_raw = call.has_flag(engine_state, stack, "raw")?; let config = engine_state.get_config(); - terminal::enable_raw_mode().map_err(|err| IoError::new(err.kind(), head, None))?; + terminal::enable_raw_mode().map_err(|err| IoError::new(err, head, None))?; if config.use_kitty_protocol { if let Ok(false) = crossterm::terminal::supports_keyboard_enhancement() { @@ -123,7 +123,7 @@ There are 4 `key_type` variants: })?; let event = parse_event(head, &event, &event_type_filter, add_raw); if let Some(event) = event { - terminal::disable_raw_mode().map_err(|err| IoError::new(err.kind(), head, None))?; + terminal::disable_raw_mode().map_err(|err| IoError::new(err, head, None))?; if config.use_kitty_protocol { let _ = execute!( std::io::stdout(), @@ -230,17 +230,17 @@ impl EventTypeFilter { fn enable_events(&self, span: Span) -> Result { if self.listen_mouse { crossterm::execute!(stdout(), EnableMouseCapture) - .map_err(|err| IoError::new(err.kind(), span, None))?; + .map_err(|err| IoError::new(err, span, None))?; } if self.listen_paste { crossterm::execute!(stdout(), EnableBracketedPaste) - .map_err(|err| IoError::new(err.kind(), span, None))?; + .map_err(|err| IoError::new(err, span, None))?; } if self.listen_focus { crossterm::execute!(stdout(), crossterm::event::EnableFocusChange) - .map_err(|err| IoError::new(err.kind(), span, None))?; + .map_err(|err| IoError::new(err, span, None))?; } Ok(DeferredConsoleRestore { diff --git a/crates/nu-command/src/platform/input/list.rs b/crates/nu-command/src/platform/input/list.rs index a2557ef986..ab12d3a36a 100644 --- a/crates/nu-command/src/platform/input/list.rs +++ b/crates/nu-command/src/platform/input/list.rs @@ -142,12 +142,7 @@ impl Command for InputList { .report(false) .interact_on_opt(&Term::stderr()) .map_err(|dialoguer::Error::IO(err)| { - IoError::new_with_additional_context( - err.kind(), - call.head, - None, - INTERACT_ERROR, - ) + IoError::new_with_additional_context(err, call.head, None, INTERACT_ERROR) })?, ) } else if fuzzy { @@ -164,12 +159,7 @@ impl Command for InputList { .report(false) .interact_on_opt(&Term::stderr()) .map_err(|dialoguer::Error::IO(err)| { - IoError::new_with_additional_context( - err.kind(), - call.head, - None, - INTERACT_ERROR, - ) + IoError::new_with_additional_context(err, call.head, None, INTERACT_ERROR) })?, ) } else { @@ -185,12 +175,7 @@ impl Command for InputList { .report(false) .interact_on_opt(&Term::stderr()) .map_err(|dialoguer::Error::IO(err)| { - IoError::new_with_additional_context( - err.kind(), - call.head, - None, - INTERACT_ERROR, - ) + IoError::new_with_additional_context(err, call.head, None, INTERACT_ERROR) })?, ) }; diff --git a/crates/nu-command/src/platform/term/term_query.rs b/crates/nu-command/src/platform/term/term_query.rs index 167f660c22..0678872a0d 100644 --- a/crates/nu-command/src/platform/term/term_query.rs +++ b/crates/nu-command/src/platform/term/term_query.rs @@ -99,19 +99,17 @@ The `prefix` is not included in the output." let prefix = prefix.unwrap_or_default(); let terminator: Option> = call.get_flag(engine_state, stack, "terminator")?; - crossterm::terminal::enable_raw_mode() - .map_err(|err| IoError::new(err.kind(), call.head, None))?; + crossterm::terminal::enable_raw_mode().map_err(|err| IoError::new(err, call.head, None))?; scopeguard::defer! { let _ = crossterm::terminal::disable_raw_mode(); } // clear terminal events while crossterm::event::poll(Duration::from_secs(0)) - .map_err(|err| IoError::new(err.kind(), call.head, None))? + .map_err(|err| IoError::new(err, call.head, None))? { // If there's an event, read it to remove it from the queue - let _ = crossterm::event::read() - .map_err(|err| IoError::new(err.kind(), call.head, None))?; + let _ = crossterm::event::read().map_err(|err| IoError::new(err, call.head, None))?; } let mut b = [0u8; 1]; @@ -122,17 +120,17 @@ The `prefix` is not included in the output." let mut stdout = std::io::stdout().lock(); stdout .write_all(&query) - .map_err(|err| IoError::new(err.kind(), call.head, None))?; + .map_err(|err| IoError::new(err, call.head, None))?; stdout .flush() - .map_err(|err| IoError::new(err.kind(), call.head, None))?; + .map_err(|err| IoError::new(err, call.head, None))?; } // Validate and skip prefix for bc in prefix { stdin .read_exact(&mut b) - .map_err(|err| IoError::new(err.kind(), call.head, None))?; + .map_err(|err| IoError::new(err, call.head, None))?; if b[0] != bc { return Err(ShellError::GenericError { error: "Input did not begin with expected sequence".into(), @@ -151,7 +149,7 @@ The `prefix` is not included in the output." loop { stdin .read_exact(&mut b) - .map_err(|err| IoError::new(err.kind(), call.head, None))?; + .map_err(|err| IoError::new(err, call.head, None))?; if b[0] == CTRL_C { return Err(ShellError::InterruptedByUser { @@ -173,7 +171,7 @@ The `prefix` is not included in the output." loop { stdin .read_exact(&mut b) - .map_err(|err| IoError::new(err.kind(), call.head, None))?; + .map_err(|err| IoError::new(err, call.head, None))?; if b[0] == CTRL_C { break; diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index 01c36a6945..cf9e6cf121 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -255,7 +255,7 @@ fn parse_file_script( match std::fs::read(path) { Ok(contents) => parse_script(working_set, Some(&filename), &contents, is_debug, call_head), Err(err) => Err(ShellError::Io(IoError::new( - err.kind().not_found_as(NotFound::File), + err.not_found_as(NotFound::File), path_span, PathBuf::from(path), ))), diff --git a/crates/nu-command/src/system/registry_query.rs b/crates/nu-command/src/system/registry_query.rs index f71209d884..2fae0d2606 100644 --- a/crates/nu-command/src/system/registry_query.rs +++ b/crates/nu-command/src/system/registry_query.rs @@ -93,7 +93,7 @@ fn registry_query( let reg_hive = get_reg_hive(engine_state, stack, call)?; let reg_key = reg_hive .open_subkey(registry_key.item) - .map_err(|err| IoError::new(err.kind(), *registry_key_span, None))?; + .map_err(|err| IoError::new(err, *registry_key_span, None))?; if registry_value.is_none() { let mut reg_values = vec![]; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 38bbde7da2..6170cb6c5e 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -205,11 +205,11 @@ impl Command for External { let stderr = stack.stderr(); let merged_stream = if matches!(stdout, OutDest::Pipe) && matches!(stderr, OutDest::Pipe) { let (reader, writer) = - os_pipe::pipe().map_err(|err| IoError::new(err.kind(), call.head, None))?; + os_pipe::pipe().map_err(|err| IoError::new(err, call.head, None))?; command.stdout( writer .try_clone() - .map_err(|err| IoError::new(err.kind(), call.head, None))?, + .map_err(|err| IoError::new(err, call.head, None))?, ); command.stderr(writer); Some(reader) @@ -220,8 +220,7 @@ impl Command for External { command.stdout(Stdio::null()); } else { command.stdout( - Stdio::try_from(stdout) - .map_err(|err| IoError::new(err.kind(), call.head, None))?, + Stdio::try_from(stdout).map_err(|err| IoError::new(err, call.head, None))?, ); } @@ -231,8 +230,7 @@ impl Command for External { command.stderr(Stdio::null()); } else { command.stderr( - Stdio::try_from(stderr) - .map_err(|err| IoError::new(err.kind(), call.head, None))?, + Stdio::try_from(stderr).map_err(|err| IoError::new(err, call.head, None))?, ); } @@ -280,7 +278,7 @@ impl Command for External { let mut child = child.map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not spawn foreground child", nu_protocol::location!(), ) @@ -290,7 +288,7 @@ impl Command for External { if !thread_job.try_add_pid(child.pid()) { kill_by_pid(child.pid().into()).map_err(|err| { ShellError::Io(IoError::new_internal( - err.kind(), + err, "Could not spawn external stdin worker", nu_protocol::location!(), )) @@ -310,7 +308,7 @@ impl Command for External { }) .map_err(|err| { IoError::new_with_additional_context( - err.kind(), + err, call.head, None, "Could not spawn external stdin worker", @@ -498,7 +496,7 @@ fn write_pipeline_data( } else if let PipelineData::Value(Value::Binary { val, .. }, ..) = data { writer.write_all(&val).map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not write pipeline data", nu_protocol::location!(), ) @@ -518,7 +516,7 @@ fn write_pipeline_data( let bytes = value.coerce_into_binary()?; writer.write_all(&bytes).map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not write pipeline data", nu_protocol::location!(), ) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 599b192409..2d4588c3e6 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -518,7 +518,7 @@ fn pretty_hex_stream(stream: ByteStream, span: Span) -> ByteStream { (&mut reader) .take(cfg.width as u64) .read_to_end(&mut read_buf) - .map_err(|err| IoError::new(err.kind(), span, None))?; + .map_err(|err| IoError::new(err, span, None))?; if !read_buf.is_empty() { nu_pretty_hex::hex_write(&mut write_buf, &read_buf, cfg, Some(true)) diff --git a/crates/nu-command/tests/commands/path/self_.rs b/crates/nu-command/tests/commands/path/self_.rs index 6a70f72c77..8b4556e6cc 100644 --- a/crates/nu-command/tests/commands/path/self_.rs +++ b/crates/nu-command/tests/commands/path/self_.rs @@ -60,5 +60,5 @@ fn self_path_runtime() { fn self_path_repl() { let actual = nu!("const foo = path self; $foo"); assert!(!actual.status.success()); - assert!(actual.err.contains("nu::shell::io::not_found")); + assert!(actual.err.contains("nu::shell::io::file_not_found")); } diff --git a/crates/nu-command/tests/commands/rm.rs b/crates/nu-command/tests/commands/rm.rs index 44e8c8b40f..0db4ad248f 100644 --- a/crates/nu-command/tests/commands/rm.rs +++ b/crates/nu-command/tests/commands/rm.rs @@ -6,6 +6,8 @@ use nu_test_support::playground::Playground; use rstest::rstest; #[cfg(not(windows))] use std::fs; +#[cfg(windows)] +use std::{fs::OpenOptions, os::windows::fs::OpenOptionsExt}; #[test] fn removes_a_file() { @@ -562,3 +564,26 @@ fn rm_with_tilde() { assert!(!files_exist_at(&["~tilde"], dirs.test())); }) } + +#[test] +#[cfg(windows)] +fn rm_already_in_use() { + Playground::setup("rm_already_in_use", |dirs, sandbox| { + sandbox.with_files(&[EmptyFile("i_will_be_used.txt")]); + + let file_path = dirs.root().join("rm_already_in_use/i_will_be_used.txt"); + let _file = OpenOptions::new() + .read(true) + .write(false) + .share_mode(0) // deny all sharing + .open(file_path) + .unwrap(); + + let outcome = nu!( + cwd: dirs.root(), + "rm rm_already_in_use/i_will_be_used.txt" + ); + + assert!(outcome.err.contains("nu::shell::io::already_in_use")); + }) +} diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 088037fc44..e02d76249b 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -4,7 +4,7 @@ use nu_protocol::{ ShellError, Span, Type, Value, VarId, ast::Expr, engine::{Call, EngineState, Stack, StateWorkingSet}, - shell_error::io::{ErrorKindExt, IoError, NotFound}, + shell_error::io::{IoError, IoErrorExt, NotFound}, }; use std::{ collections::HashMap, @@ -221,7 +221,7 @@ pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result Result, path: &Value, append: bool) -> Result p, Err(err) => { - return Err(IoError::new(err.kind(), pattern_span, path).into()); + return Err(IoError::new(err, pattern_span, path).into()); } }; (path.parent().map(|parent| parent.to_path_buf()), path) diff --git a/crates/nu-plugin-core/src/communication_mode/mod.rs b/crates/nu-plugin-core/src/communication_mode/mod.rs index 343be07b1d..8e96511b2f 100644 --- a/crates/nu-plugin-core/src/communication_mode/mod.rs +++ b/crates/nu-plugin-core/src/communication_mode/mod.rs @@ -87,7 +87,7 @@ impl CommunicationMode { .and_then(|name| ListenerOptions::new().name(name).create_sync()) .map_err(|err| { IoError::new_internal( - err.kind(), + err, format!( "Could not interpret local socket name {:?}", name.to_string_lossy() @@ -117,7 +117,7 @@ impl CommunicationMode { .and_then(|name| ls::Stream::connect(name)) .map_err(|err| { ShellError::Io(IoError::new_internal( - err.kind(), + err, format!( "Could not interpret local socket name {:?}", name.to_string_lossy() @@ -190,7 +190,7 @@ impl PreparedServerCommunication { .set_nonblocking(ListenerNonblockingMode::Accept) .map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not set non-blocking mode accept for listener", nu_protocol::location!(), ) @@ -204,7 +204,7 @@ impl PreparedServerCommunication { // good measure. Had an issue without this on macOS. stream.set_nonblocking(false).map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not disable non-blocking mode for listener", nu_protocol::location!(), ) @@ -217,7 +217,7 @@ impl PreparedServerCommunication { // `WouldBlock` is ok, just means it's not ready yet, but some other // kind of error should be reported return Err(ShellError::Io(IoError::new_internal( - err.kind(), + err, "Accepting new data from listener failed", nu_protocol::location!(), ))); diff --git a/crates/nu-plugin-core/src/interface/mod.rs b/crates/nu-plugin-core/src/interface/mod.rs index 4c0db1a32b..b493b84e14 100644 --- a/crates/nu-plugin-core/src/interface/mod.rs +++ b/crates/nu-plugin-core/src/interface/mod.rs @@ -82,7 +82,7 @@ where fn flush(&self) -> Result<(), ShellError> { self.0.lock().flush().map_err(|err| { ShellError::Io(IoError::new_internal( - err.kind(), + err, "PluginWrite could not flush", nu_protocol::location!(), )) @@ -112,7 +112,7 @@ where })?; lock.flush().map_err(|err| { ShellError::Io(IoError::new_internal( - err.kind(), + err, "PluginWrite could not flush", nu_protocol::location!(), )) @@ -340,7 +340,7 @@ where writer.write_all(std::iter::from_fn(move || match reader.read(buf) { Ok(0) => None, Ok(len) => Some(Ok(buf[..len].to_vec())), - Err(err) => Some(Err(ShellError::from(IoError::new(err.kind(), span, None)))), + Err(err) => Some(Err(ShellError::from(IoError::new(err, span, None)))), }))?; Ok(()) } @@ -368,7 +368,7 @@ where }) .map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not spawn plugin stream background writer", nu_protocol::location!(), ) diff --git a/crates/nu-plugin-core/src/interface/tests.rs b/crates/nu-plugin-core/src/interface/tests.rs index ed9e6b2d47..392d50ec5d 100644 --- a/crates/nu-plugin-core/src/interface/tests.rs +++ b/crates/nu-plugin-core/src/interface/tests.rs @@ -246,7 +246,7 @@ fn read_pipeline_data_byte_stream() -> Result<(), ShellError> { ByteStreamSource::Read(mut read) => { let mut buf = Vec::new(); read.read_to_end(&mut buf) - .map_err(|err| IoError::new(err.kind(), test_span, None))?; + .map_err(|err| IoError::new(err, test_span, None))?; let iter = buf.chunks_exact(out_pattern.len()); assert_eq!(iter.len(), iterations); for chunk in iter { diff --git a/crates/nu-plugin-core/src/serializers/json.rs b/crates/nu-plugin-core/src/serializers/json.rs index 3a0756bd5c..fc8757b864 100644 --- a/crates/nu-plugin-core/src/serializers/json.rs +++ b/crates/nu-plugin-core/src/serializers/json.rs @@ -1,5 +1,8 @@ use nu_plugin_protocol::{PluginInput, PluginOutput}; -use nu_protocol::{ShellError, location, shell_error::io::IoError}; +use nu_protocol::{ + ShellError, location, + shell_error::{self, io::IoError}, +}; use serde::Deserialize; use crate::{Encoder, PluginEncoder}; @@ -28,7 +31,7 @@ impl Encoder for JsonSerializer { serde_json::to_writer(&mut *writer, plugin_input).map_err(json_encode_err)?; writer.write_all(b"\n").map_err(|err| { ShellError::Io(IoError::new_internal( - err.kind(), + err, "Failed to write final line break", location!(), )) @@ -55,7 +58,7 @@ impl Encoder for JsonSerializer { serde_json::to_writer(&mut *writer, plugin_output).map_err(json_encode_err)?; writer.write_all(b"\n").map_err(|err| { ShellError::Io(IoError::new_internal( - err.kind(), + err, "JsonSerializer could not encode linebreak", nu_protocol::location!(), )) @@ -77,7 +80,7 @@ impl Encoder for JsonSerializer { fn json_encode_err(err: serde_json::Error) -> ShellError { if err.is_io() { ShellError::Io(IoError::new_internal( - err.io_error_kind().expect("is io"), + shell_error::io::ErrorKind::from_std(err.io_error_kind().expect("is io")), "Could not encode with json", nu_protocol::location!(), )) @@ -94,7 +97,7 @@ fn json_decode_err(err: serde_json::Error) -> Result, ShellError> { Ok(None) } else if err.is_io() { Err(ShellError::Io(IoError::new_internal( - err.io_error_kind().expect("is io"), + shell_error::io::ErrorKind::from_std(err.io_error_kind().expect("is io")), "Could not decode with json", nu_protocol::location!(), ))) diff --git a/crates/nu-plugin-core/src/serializers/msgpack.rs b/crates/nu-plugin-core/src/serializers/msgpack.rs index 2378cbd5be..d727fb78f3 100644 --- a/crates/nu-plugin-core/src/serializers/msgpack.rs +++ b/crates/nu-plugin-core/src/serializers/msgpack.rs @@ -1,7 +1,10 @@ use std::io::ErrorKind; use nu_plugin_protocol::{PluginInput, PluginOutput}; -use nu_protocol::{ShellError, shell_error::io::IoError}; +use nu_protocol::{ + ShellError, + shell_error::{self, io::IoError}, +}; use serde::Deserialize; use crate::{Encoder, PluginEncoder}; @@ -66,7 +69,7 @@ fn rmp_encode_err(err: rmp_serde::encode::Error) -> ShellError { // I/O error ShellError::Io(IoError::new_internal( // TODO: get a better kind here - std::io::ErrorKind::Other, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other), "Could not encode with rmp", nu_protocol::location!(), )) @@ -92,7 +95,7 @@ fn rmp_decode_err(err: rmp_serde::decode::Error) -> Result, ShellEr // I/O error Err(ShellError::Io(IoError::new_internal( // TODO: get a better kind here - std::io::ErrorKind::Other, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other), "Could not decode with rmp", nu_protocol::location!(), ))) diff --git a/crates/nu-plugin-core/src/serializers/tests.rs b/crates/nu-plugin-core/src/serializers/tests.rs index 76503fa96e..1f51bab157 100644 --- a/crates/nu-plugin-core/src/serializers/tests.rs +++ b/crates/nu-plugin-core/src/serializers/tests.rs @@ -379,7 +379,7 @@ macro_rules! generate_tests { .with_help("some help") .with_label("msg", Span::new(2, 30)) .with_inner(ShellError::Io(IoError::new( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::NotFound), Span::test_data(), None, ))); diff --git a/crates/nu-plugin-engine/src/interface/tests.rs b/crates/nu-plugin-engine/src/interface/tests.rs index 8759a658af..b40593b983 100644 --- a/crates/nu-plugin-engine/src/interface/tests.rs +++ b/crates/nu-plugin-engine/src/interface/tests.rs @@ -20,6 +20,7 @@ use nu_protocol::{ Spanned, Value, ast::{Math, Operator}, engine::Closure, + shell_error, }; use serde::{Deserialize, Serialize}; use std::{ @@ -87,7 +88,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul fn test_io_error() -> ShellError { ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::Other, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other), Span::test_data(), None, "test io error", diff --git a/crates/nu-plugin-engine/src/persistent.rs b/crates/nu-plugin-engine/src/persistent.rs index fbdcef52a8..a6efb2c285 100644 --- a/crates/nu-plugin-engine/src/persistent.rs +++ b/crates/nu-plugin-engine/src/persistent.rs @@ -190,11 +190,7 @@ impl PersistentPlugin { // Start the plugin garbage collector let gc = PluginGc::new(mutable.gc_config.clone(), &self).map_err(|err| { - IoError::new_internal( - err.kind(), - "Could not start plugin gc", - nu_protocol::location!(), - ) + IoError::new_internal(err, "Could not start plugin gc", nu_protocol::location!()) })?; let pid = child.id(); diff --git a/crates/nu-plugin-test-support/src/spawn_fake_plugin.rs b/crates/nu-plugin-test-support/src/spawn_fake_plugin.rs index 06357a7396..0b639bb3c6 100644 --- a/crates/nu-plugin-test-support/src/spawn_fake_plugin.rs +++ b/crates/nu-plugin-test-support/src/spawn_fake_plugin.rs @@ -66,7 +66,7 @@ pub(crate) fn spawn_fake_plugin( .spawn(move || manager.consume_all(output_read).expect("Plugin read error")) .map_err(|err| { IoError::new_internal( - err.kind(), + err, format!("Could not spawn fake plugin interface reader ({name})"), nu_protocol::location!(), ) @@ -87,7 +87,7 @@ pub(crate) fn spawn_fake_plugin( }) .map_err(|err| { IoError::new_internal( - err.kind(), + err, format!("Could not spawn fake plugin runner ({name})"), nu_protocol::location!(), ) diff --git a/crates/nu-plugin/src/plugin/interface/mod.rs b/crates/nu-plugin/src/plugin/interface/mod.rs index 2969fd50a2..d1c0d502f6 100644 --- a/crates/nu-plugin/src/plugin/interface/mod.rs +++ b/crates/nu-plugin/src/plugin/interface/mod.rs @@ -1048,7 +1048,7 @@ impl ForegroundGuard { // This should always succeed, frankly, but handle the error just in case setpgid(Pid::from_raw(0), Pid::from_raw(0)).map_err(|err| { nu_protocol::shell_error::io::IoError::new_internal( - std::io::Error::from(err).kind(), + std::io::Error::from(err), "Could not set pgid", nu_protocol::location!(), ) diff --git a/crates/nu-plugin/src/plugin/interface/tests.rs b/crates/nu-plugin/src/plugin/interface/tests.rs index a9535a3eee..7165753a70 100644 --- a/crates/nu-plugin/src/plugin/interface/tests.rs +++ b/crates/nu-plugin/src/plugin/interface/tests.rs @@ -12,7 +12,7 @@ use nu_plugin_protocol::{ use nu_protocol::{ BlockId, ByteStreamType, Config, CustomValue, IntoInterruptiblePipelineData, LabeledError, PipelineData, PluginSignature, ShellError, Signals, Span, Spanned, Value, VarId, - engine::Closure, + engine::Closure, shell_error, }; use std::{ collections::HashMap, @@ -91,7 +91,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul fn test_io_error() -> ShellError { ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::Other, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other), Span::test_data(), None, "test io error", diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 7d1efab177..5efb350e6c 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -51,6 +51,7 @@ nix = { workspace = true, default-features = false, features = ["signal"] } [target.'cfg(windows)'.dependencies] dirs-sys = { workspace = true } windows-sys = { workspace = true } +windows = { workspace = true } [features] default = ["os"] diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 9add1813f6..d33a6c2000 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -353,7 +353,7 @@ impl EngineState { let cwd = self.cwd(Some(stack))?; std::env::set_current_dir(cwd).map_err(|err| { - IoError::new_internal(err.kind(), "Could not set current dir", crate::location!()) + IoError::new_internal(err, "Could not set current dir", crate::location!()) })?; if let Some(config) = stack.config.take() { @@ -546,7 +546,7 @@ impl EngineState { Ok(PluginRegistryFile::default()) } else { Err(ShellError::Io(IoError::new_internal_with_path( - err.kind(), + err, "Failed to open plugin file", crate::location!(), PathBuf::from(plugin_path), @@ -563,7 +563,7 @@ impl EngineState { // Write it to the same path let plugin_file = File::create(plugin_path.as_path()).map_err(|err| { IoError::new_internal_with_path( - err.kind(), + err, "Failed to write plugin file", crate::location!(), PathBuf::from(plugin_path), diff --git a/crates/nu-protocol/src/errors/labeled_error.rs b/crates/nu-protocol/src/errors/labeled_error.rs index adde650f40..c8361667c6 100644 --- a/crates/nu-protocol/src/errors/labeled_error.rs +++ b/crates/nu-protocol/src/errors/labeled_error.rs @@ -143,11 +143,11 @@ impl LabeledError { /// [`ShellError`] implements `miette::Diagnostic`: /// /// ```rust - /// # use nu_protocol::{ShellError, LabeledError, shell_error::io::IoError, Span}; + /// # use nu_protocol::{ShellError, LabeledError, shell_error::{self, io::IoError}, Span}; /// # /// let error = LabeledError::from_diagnostic( /// &ShellError::Io(IoError::new_with_additional_context( - /// std::io::ErrorKind::Other, + /// shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other), /// Span::test_data(), /// None, /// "some error" diff --git a/crates/nu-protocol/src/errors/shell_error/io.rs b/crates/nu-protocol/src/errors/shell_error/io.rs index 69427ef5c5..0722cd9abe 100644 --- a/crates/nu-protocol/src/errors/shell_error/io.rs +++ b/crates/nu-protocol/src/errors/shell_error/io.rs @@ -38,7 +38,7 @@ use super::{ShellError, location::Location}; /// /// # let span = Span::test_data(); /// let path = PathBuf::from("/some/missing/file"); -/// let error = IoError::new(std::io::ErrorKind::NotFound, span, path); +/// let error = IoError::new(ErrorKind::FileNotFound, span, path); /// println!("Error: {:?}", error); /// ``` /// @@ -47,7 +47,7 @@ use super::{ShellError, location::Location}; /// # use nu_protocol::shell_error::io::{IoError, ErrorKind}; // # /// let error = IoError::new_internal( -/// std::io::ErrorKind::UnexpectedEof, +/// ErrorKind::from_std(std::io::ErrorKind::UnexpectedEof), /// "Failed to read data from buffer", /// nu_protocol::location!() /// ); @@ -93,7 +93,7 @@ pub struct IoError { /// and is part of [`std::io::Error`]. /// If a kind cannot be represented by it, consider adding a new variant to [`ErrorKind`]. /// - /// Only in very rare cases should [`std::io::ErrorKind::Other`] be used, make sure you provide + /// Only in very rare cases should [`std::io::ErrorKind::other()`] be used, make sure you provide /// `additional_context` to get useful errors in these cases. pub kind: ErrorKind, @@ -125,15 +125,63 @@ pub struct IoError { pub location: Option, } +/// Prevents other crates from constructing certain enum variants directly. +/// +/// This type is only used to block construction while still allowing pattern matching. +/// It's not meant to be used for anything else. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Sealed; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Diagnostic)] pub enum ErrorKind { - Std(std::io::ErrorKind), + /// [`std::io::ErrorKind`] from the standard library. + /// + /// This variant wraps a standard library error kind and extends our own error enum with it. + /// The hidden field prevents other crates, even our own, from constructing this directly. + /// Most of the time, you already have a full [`std::io::Error`], so just pass that directly to + /// [`IoError::new`] or [`IoError::new_with_additional_context`]. + /// This allows us to inspect the raw os error of `std::io::Error`s. + /// + /// Matching is still easy: + /// + /// ```rust + /// # use nu_protocol::shell_error::io::ErrorKind; + /// # + /// # let err_kind = ErrorKind::from_std(std::io::ErrorKind::NotFound); + /// match err_kind { + /// ErrorKind::Std(std::io::ErrorKind::NotFound, ..) => { /* ... */ } + /// _ => {} + /// } + /// ``` + /// + /// If you want to provide an [`std::io::ErrorKind`] manually, use [`ErrorKind::from_std`]. + #[allow(private_interfaces)] + Std(std::io::ErrorKind, Sealed), + NotAFile, + + /// The file or directory is in use by another program. + /// + /// On Windows, this maps to + /// [`ERROR_SHARING_VIOLATION`](windows::Win32::Foundation::ERROR_SHARING_VIOLATION) and + /// prevents access like deletion or modification. + AlreadyInUse, + // use these variants in cases where we know precisely whether a file or directory was expected FileNotFound, DirectoryNotFound, } +impl ErrorKind { + /// Construct an [`ErrorKind`] from a [`std::io::ErrorKind`] without a full [`std::io::Error`]. + /// + /// In most cases, you should use [`IoError::new`] and pass the full [`std::io::Error`] instead. + /// This method is only meant for cases where we provide our own io error kinds. + pub fn from_std(kind: std::io::ErrorKind) -> Self { + Self::Std(kind, Sealed) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Error, Diagnostic)] #[error("{0}")] pub struct AdditionalContext(String); @@ -237,10 +285,10 @@ impl IoError { /// /// # Examples /// ```rust - /// use nu_protocol::shell_error::io::IoError; + /// use nu_protocol::shell_error::{self, io::IoError}; /// /// let error = IoError::new_internal( - /// std::io::ErrorKind::UnexpectedEof, + /// shell_error::io::ErrorKind::from_std(std::io::ErrorKind::UnexpectedEof), /// "Failed to read from buffer", /// nu_protocol::location!(), /// ); @@ -268,11 +316,11 @@ impl IoError { /// /// # Examples /// ```rust - /// use nu_protocol::shell_error::io::IoError; + /// use nu_protocol::shell_error::{self, io::IoError}; /// use std::path::PathBuf; /// /// let error = IoError::new_internal_with_path( - /// std::io::ErrorKind::NotFound, + /// shell_error::io::ErrorKind::FileNotFound, /// "Could not find special file", /// nu_protocol::location!(), /// PathBuf::from("/some/file"), @@ -297,14 +345,37 @@ impl IoError { /// /// This method is particularly useful when you need to handle multiple I/O errors which all /// take the same span and path. - /// Instead of calling `.map_err(|err| IoError::new(err.kind(), span, path))` every time, you + /// Instead of calling `.map_err(|err| IoError::new(err, span, path))` every time, you /// can create the factory closure once and pass that into `.map_err`. pub fn factory<'p, P>(span: Span, path: P) -> impl Fn(std::io::Error) -> Self + use<'p, P> where P: Into>, { let path = path.into(); - move |err: std::io::Error| IoError::new(err.kind(), span, path.map(PathBuf::from)) + move |err: std::io::Error| IoError::new(err, span, path.map(PathBuf::from)) + } +} + +impl From for ErrorKind { + fn from(err: std::io::Error) -> Self { + (&err).into() + } +} + +impl From<&std::io::Error> for ErrorKind { + fn from(err: &std::io::Error) -> Self { + #[cfg(windows)] + if let Some(raw_os_error) = err.raw_os_error() { + use windows::Win32::Foundation; + + #[allow(clippy::single_match, reason = "in the future we can expand here")] + match Foundation::WIN32_ERROR(raw_os_error as u32) { + Foundation::ERROR_SHARING_VIOLATION => return ErrorKind::AlreadyInUse, + _ => {} + } + } + + ErrorKind::Std(err.kind(), Sealed) } } @@ -312,7 +383,7 @@ impl StdError for IoError {} impl Display for IoError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self.kind { - ErrorKind::Std(std::io::ErrorKind::NotFound) => write!(f, "Not found"), + ErrorKind::Std(std::io::ErrorKind::NotFound, _) => write!(f, "Not found"), ErrorKind::FileNotFound => write!(f, "File not found"), ErrorKind::DirectoryNotFound => write!(f, "Directory not found"), _ => write!(f, "I/O error"), @@ -323,13 +394,14 @@ impl Display for IoError { impl Display for ErrorKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - ErrorKind::Std(std::io::ErrorKind::NotFound) => write!(f, "Not found"), - ErrorKind::Std(error_kind) => { + ErrorKind::Std(std::io::ErrorKind::NotFound, _) => write!(f, "Not found"), + ErrorKind::Std(error_kind, _) => { let msg = error_kind.to_string(); let (first, rest) = msg.split_at(1); write!(f, "{}{}", first.to_uppercase(), rest) } ErrorKind::NotAFile => write!(f, "Not a file"), + ErrorKind::AlreadyInUse => write!(f, "Already in use"), ErrorKind::FileNotFound => write!(f, "File not found"), ErrorKind::DirectoryNotFound => write!(f, "Directory not found"), } @@ -342,7 +414,7 @@ impl Diagnostic for IoError { fn code<'a>(&'a self) -> Option> { let mut code = String::from("nu::shell::io::"); match self.kind { - ErrorKind::Std(error_kind) => match error_kind { + ErrorKind::Std(error_kind, _) => match error_kind { std::io::ErrorKind::NotFound => code.push_str("not_found"), std::io::ErrorKind::PermissionDenied => code.push_str("permission_denied"), std::io::ErrorKind::ConnectionRefused => code.push_str("connection_refused"), @@ -366,6 +438,7 @@ impl Diagnostic for IoError { kind => code.push_str(&kind.to_string().to_lowercase().replace(" ", "_")), }, ErrorKind::NotAFile => code.push_str("not_a_file"), + ErrorKind::AlreadyInUse => code.push_str("already_in_use"), ErrorKind::FileNotFound => code.push_str("file_not_found"), ErrorKind::DirectoryNotFound => code.push_str("directory_not_found"), } @@ -378,7 +451,10 @@ impl Diagnostic for IoError { let path = format!("'{}'", path.display()); match self.kind { ErrorKind::NotAFile => format!("{path} is not a file"), - ErrorKind::Std(std::io::ErrorKind::NotFound) + ErrorKind::AlreadyInUse => { + format!("{path} is already being used by another program") + } + ErrorKind::Std(std::io::ErrorKind::NotFound, _) | ErrorKind::FileNotFound | ErrorKind::DirectoryNotFound => format!("{path} does not exist"), _ => format!("The error occurred at {path}"), @@ -430,16 +506,10 @@ impl From for std::io::Error { } } -impl From for ErrorKind { - fn from(value: std::io::ErrorKind) -> Self { - ErrorKind::Std(value) - } -} - impl From for std::io::ErrorKind { fn from(value: ErrorKind) -> Self { match value { - ErrorKind::Std(error_kind) => error_kind, + ErrorKind::Std(error_kind, _) => error_kind, _ => std::io::ErrorKind::Other, } } @@ -455,8 +525,8 @@ pub enum NotFound { Directory, } -/// Extension trait for working with [`std::io::ErrorKind`]. -pub trait ErrorKindExt { +/// Extension trait for working with [`std::io::Error`]. +pub trait IoErrorExt { /// Map [`NotFound`](std::io::ErrorKind) variants into more precise variants. /// /// The OS doesn't know when an entity was not found whether it was meant to be a file or a @@ -469,7 +539,7 @@ pub trait ErrorKindExt { /// If the file isn't found, return [`FileNotFound`](ErrorKind::FileNotFound). /// ```rust /// # use nu_protocol::{ - /// # shell_error::io::{ErrorKind, ErrorKindExt, IoError, NotFound}, + /// # shell_error::io::{ErrorKind, IoErrorExt, IoError, NotFound}, /// # ShellError, Span, /// # }; /// # use std::{fs, path::PathBuf}; @@ -479,7 +549,7 @@ pub trait ErrorKindExt { /// let a_file = PathBuf::from("scripts/ellie.nu"); /// let ellie = fs::read_to_string(&a_file).map_err(|err| { /// ShellError::Io(IoError::new( - /// err.kind().not_found_as(NotFound::File), + /// err.not_found_as(NotFound::File), /// span, /// a_file, /// )) @@ -498,21 +568,46 @@ pub trait ErrorKindExt { fn not_found_as(self, kind: NotFound) -> ErrorKind; } -impl ErrorKindExt for std::io::ErrorKind { +impl IoErrorExt for ErrorKind { fn not_found_as(self, kind: NotFound) -> ErrorKind { match (kind, self) { - (NotFound::File, Self::NotFound) => ErrorKind::FileNotFound, - (NotFound::Directory, Self::NotFound) => ErrorKind::DirectoryNotFound, - _ => ErrorKind::Std(self), - } - } -} - -impl ErrorKindExt for ErrorKind { - fn not_found_as(self, kind: NotFound) -> ErrorKind { - match self { - Self::Std(std_kind) => std_kind.not_found_as(kind), + (NotFound::File, Self::Std(std::io::ErrorKind::NotFound, _)) => ErrorKind::FileNotFound, + (NotFound::Directory, Self::Std(std::io::ErrorKind::NotFound, _)) => { + ErrorKind::DirectoryNotFound + } _ => self, } } } + +impl IoErrorExt for std::io::Error { + fn not_found_as(self, kind: NotFound) -> ErrorKind { + ErrorKind::from(self).not_found_as(kind) + } +} + +impl IoErrorExt for &std::io::Error { + fn not_found_as(self, kind: NotFound) -> ErrorKind { + ErrorKind::from(self).not_found_as(kind) + } +} + +#[cfg(test)] +mod assert_not_impl { + use super::*; + + /// Assertion that `ErrorKind` does not implement `From`. + /// + /// This implementation exists only in tests to make sure that no crate, + /// including ours, accidentally adds a `From` impl for `ErrorKind`. + /// If someone tries, it will fail due to conflicting implementations. + /// + /// We want to force usage of [`IoError::new`] with a full [`std::io::Error`] instead of + /// allowing conversion from just an [`std::io::ErrorKind`]. + /// That way, we can properly inspect and classify uncategorized I/O errors. + impl From for ErrorKind { + fn from(_: std::io::ErrorKind) -> Self { + unimplemented!("ErrorKind should not implement From") + } + } +} diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index ac85ea1645..ca18359f65 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -246,7 +246,7 @@ impl ByteStream { if let Some(mut reader) = self.reader() { // Copy the number of skipped bytes into the sink before proceeding io::copy(&mut (&mut reader).take(n), &mut io::sink()) - .map_err(|err| IoError::new(err.kind(), span, None))?; + .map_err(|err| IoError::new(err, span, None))?; Ok( ByteStream::read(reader, span, Signals::empty(), ByteStreamType::Binary) .with_known_size(known_size), @@ -367,7 +367,7 @@ impl ByteStream { /// binary. #[cfg(feature = "os")] pub fn stdin(span: Span) -> Result { - let stdin = os_pipe::dup_stdin().map_err(|err| IoError::new(err.kind(), span, None))?; + let stdin = os_pipe::dup_stdin().map_err(|err| IoError::new(err, span, None))?; let source = ByteStreamSource::File(convert_file(stdin)); Ok(Self::new( source, @@ -853,7 +853,7 @@ impl Iterator for Lines { trim_end_newline(&mut string); Some(Ok(string)) } - Err(e) => Some(Err(IoError::new(e.kind(), self.span, None).into())), + Err(err) => Some(Err(IoError::new(err, self.span, None).into())), } } } @@ -1052,7 +1052,7 @@ impl Iterator for SplitRead { self.internal.next().map(|r| { r.map_err(|err| { ShellError::Io(IoError::new_internal( - err.kind(), + err, "Could not get next value for SplitRead", crate::location!(), )) @@ -1094,7 +1094,7 @@ impl Chunks { fn next_string(&mut self) -> Result, (Vec, ShellError)> { let from_io_error = |err: std::io::Error| match ShellErrorBridge::try_from(err) { Ok(err) => err.0, - Err(err) => IoError::new(err.kind(), self.span, None).into(), + Err(err) => IoError::new(err, self.span, None).into(), }; // Get some data from the reader @@ -1177,11 +1177,7 @@ impl Iterator for Chunks { Ok(buf) => buf, Err(err) => { self.error = true; - return Some(Err(ShellError::Io(IoError::new( - err.kind(), - self.span, - None, - )))); + return Some(Err(ShellError::Io(IoError::new(err, self.span, None)))); } }; if !buf.is_empty() { diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index a79afecc1a..51918a16b9 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -222,14 +222,14 @@ impl PipelineData { let bytes = value_to_bytes(value)?; dest.write_all(&bytes).map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not write PipelineData to dest", crate::location!(), ) })?; dest.flush().map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not flush PipelineData to dest", crate::location!(), ) @@ -241,14 +241,14 @@ impl PipelineData { let bytes = value_to_bytes(value)?; dest.write_all(&bytes).map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not write PipelineData to dest", crate::location!(), ) })?; dest.write_all(b"\n").map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not write linebreak after PipelineData to dest", crate::location!(), ) @@ -256,7 +256,7 @@ impl PipelineData { } dest.flush().map_err(|err| { IoError::new_internal( - err.kind(), + err, "Could not flush PipelineData to dest", crate::location!(), ) @@ -775,11 +775,9 @@ where let io_error_map = |err: std::io::Error, location: Location| { let context = format!("Writing to {} failed", destination_name); match span { - None => IoError::new_internal(err.kind(), context, location), - Some(span) if span == Span::unknown() => { - IoError::new_internal(err.kind(), context, location) - } - Some(span) => IoError::new_with_additional_context(err.kind(), span, None, context), + None => IoError::new_internal(err, context, location), + Some(span) if span == Span::unknown() => IoError::new_internal(err, context, location), + Some(span) => IoError::new_with_additional_context(err, span, None, context), } }; diff --git a/crates/nu-protocol/src/process/child.rs b/crates/nu-protocol/src/process/child.rs index d419b808d5..6a67693bc0 100644 --- a/crates/nu-protocol/src/process/child.rs +++ b/crates/nu-protocol/src/process/child.rs @@ -81,7 +81,7 @@ impl ExitStatusFuture { } Ok(Ok(status)) => Ok(status), Ok(Err(err)) => Err(ShellError::Io(IoError::new_with_additional_context( - err.kind(), + err, span, None, "failed to get exit code", @@ -276,7 +276,7 @@ impl ChildProcess { }) .map_err(|err| { IoError::new_with_additional_context( - err.kind(), + err, span, None, "Could now spawn exit status waiter", @@ -325,7 +325,7 @@ impl ChildProcess { } let bytes = if let Some(stdout) = self.stdout { - collect_bytes(stdout).map_err(|err| IoError::new(err.kind(), self.span, None))? + collect_bytes(stdout).map_err(|err| IoError::new(err, self.span, None))? } else { Vec::new() }; diff --git a/crates/nu_plugin_polars/src/dataframe/command/core/open.rs b/crates/nu_plugin_polars/src/dataframe/command/core/open.rs index fe55836624..0cec4efd76 100644 --- a/crates/nu_plugin_polars/src/dataframe/command/core/open.rs +++ b/crates/nu_plugin_polars/src/dataframe/command/core/open.rs @@ -10,7 +10,8 @@ use nu_utils::perf; use nu_plugin::{EvaluatedCall, PluginCommand}; use nu_protocol::{ Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned, - SyntaxShape, Type, Value, shell_error::io::IoError, + SyntaxShape, Type, Value, + shell_error::{self, io::IoError}, }; use std::{fs::File, io::BufReader, num::NonZeroUsize, path::PathBuf, sync::Arc}; @@ -193,7 +194,7 @@ fn command( )), }, None => Err(ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other), spanned_file.span, PathBuf::from(spanned_file.item), "File without extension", diff --git a/crates/nu_plugin_polars/src/dataframe/command/core/save/mod.rs b/crates/nu_plugin_polars/src/dataframe/command/core/save/mod.rs index 86c2cc6556..ec36fe3f86 100644 --- a/crates/nu_plugin_polars/src/dataframe/command/core/save/mod.rs +++ b/crates/nu_plugin_polars/src/dataframe/command/core/save/mod.rs @@ -16,7 +16,8 @@ use log::debug; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_protocol::{ Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned, - SyntaxShape, Type, shell_error::io::IoError, + SyntaxShape, Type, + shell_error::{self, io::IoError}, }; use polars::error::PolarsError; @@ -212,7 +213,7 @@ fn command( )), }, None => Err(ShellError::Io(IoError::new_with_additional_context( - std::io::ErrorKind::NotFound, + shell_error::io::ErrorKind::FileNotFound, resource.span, Some(PathBuf::from(resource.path)), "File without extension", diff --git a/src/ide.rs b/src/ide.rs index de3e1f68fe..fe33bd79d7 100644 --- a/src/ide.rs +++ b/src/ide.rs @@ -5,7 +5,7 @@ use nu_protocol::{ DeclId, ShellError, Span, Value, VarId, engine::{EngineState, Stack, StateWorkingSet}, report_shell_error, - shell_error::io::{ErrorKindExt, IoError, NotFound}, + shell_error::io::{IoError, IoErrorExt, NotFound}, }; use reedline::Completer; use serde_json::{Value as JsonValue, json}; @@ -58,7 +58,7 @@ fn read_in_file<'a>( let file = std::fs::read(file_path) .map_err(|err| { ShellError::Io(IoError::new_with_additional_context( - err.kind().not_found_as(NotFound::File), + err.not_found_as(NotFound::File), Span::unknown(), PathBuf::from(file_path), "Could not read file", diff --git a/src/main.rs b/src/main.rs index 9880741e28..dd0664b0bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -417,7 +417,7 @@ fn main() -> Result<()> { let filename = canonicalize_with(&plugin_filename.item, &init_cwd) .map_err(|err| { nu_protocol::shell_error::io::IoError::new( - err.kind(), + err, plugin_filename.span, PathBuf::from(&plugin_filename.item), )