Refactor: Construct IoError from std::io::Error instead of std::io::ErrorKind (#15777)

This commit is contained in:
Piepmatz 2025-05-18 14:52:40 +02:00 committed by GitHub
parent c4dcfdb77b
commit 833471241a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
80 changed files with 408 additions and 299 deletions

1
Cargo.lock generated
View File

@ -3996,6 +3996,7 @@ dependencies = [
"thiserror 2.0.12", "thiserror 2.0.12",
"typetag", "typetag",
"web-time", "web-time",
"windows 0.56.0",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]

View File

@ -1,5 +1,8 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::{HistoryFileFormat, shell_error::io::IoError}; use nu_protocol::{
HistoryFileFormat,
shell_error::{self, io::IoError},
};
use reedline::{ use reedline::{
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery, FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
SqliteBackedHistory, SqliteBackedHistory,
@ -94,7 +97,7 @@ impl Command for History {
}) })
}) })
.ok_or(IoError::new( .ok_or(IoError::new(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::FileNotFound,
head, head,
history_path, history_path,
))? ))?
@ -110,7 +113,7 @@ impl Command for History {
}) })
}) })
.ok_or(IoError::new( .ok_or(IoError::new(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::FileNotFound,
head, head,
history_path, history_path,
))? ))?

View File

@ -287,7 +287,7 @@ fn backup(path: &Path, span: Span) -> Result<Option<PathBuf>, ShellError> {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None), Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => { Err(e) => {
return Err(IoError::new_internal( return Err(IoError::new_internal(
e.kind(), e,
"Could not get metadata", "Could not get metadata",
nu_protocol::location!(), nu_protocol::location!(),
) )
@ -297,7 +297,7 @@ fn backup(path: &Path, span: Span) -> Result<Option<PathBuf>, ShellError> {
let bak_path = find_backup_path(path, span)?; let bak_path = find_backup_path(path, span)?;
std::fs::copy(path, &bak_path).map_err(|err| { std::fs::copy(path, &bak_path).map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err.not_found_as(NotFound::File),
"Could not copy backup", "Could not copy backup",
nu_protocol::location!(), nu_protocol::location!(),
) )

View File

@ -42,7 +42,7 @@ impl Command for KeybindingsListen {
Err(e) => { Err(e) => {
terminal::disable_raw_mode().map_err(|err| { terminal::disable_raw_mode().map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not disable raw mode", "Could not disable raw mode",
nu_protocol::location!(), nu_protocol::location!(),
) )
@ -71,18 +71,10 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
let config = engine_state.get_config(); let config = engine_state.get_config();
stdout().flush().map_err(|err| { stdout().flush().map_err(|err| {
IoError::new_internal( IoError::new_internal(err, "Could not flush stdout", nu_protocol::location!())
err.kind(),
"Could not flush stdout",
nu_protocol::location!(),
)
})?; })?;
terminal::enable_raw_mode().map_err(|err| { terminal::enable_raw_mode().map_err(|err| {
IoError::new_internal( IoError::new_internal(err, "Could not enable raw mode", nu_protocol::location!())
err.kind(),
"Could not enable raw mode",
nu_protocol::location!(),
)
})?; })?;
if config.use_kitty_protocol { if config.use_kitty_protocol {
@ -114,7 +106,7 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
loop { loop {
let event = crossterm::event::read().map_err(|err| { 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()) { if event == Event::Key(KeyCode::Esc.into()) {
break; break;
@ -136,7 +128,7 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
}; };
stdout.queue(crossterm::style::Print(o)).map_err(|err| { stdout.queue(crossterm::style::Print(o)).map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not print output record", "Could not print output record",
nu_protocol::location!(), nu_protocol::location!(),
) )
@ -144,14 +136,10 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
stdout stdout
.queue(crossterm::style::Print("\r\n")) .queue(crossterm::style::Print("\r\n"))
.map_err(|err| { .map_err(|err| {
IoError::new_internal( IoError::new_internal(err, "Could not print linebreak", nu_protocol::location!())
err.kind(),
"Could not print linebreak",
nu_protocol::location!(),
)
})?; })?;
stdout.flush().map_err(|err| { 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<Value, ShellError> {
} }
terminal::disable_raw_mode().map_err(|err| { terminal::disable_raw_mode().map_err(|err| {
IoError::new_internal( IoError::new_internal(err, "Could not disable raw mode", nu_protocol::location!())
err.kind(),
"Could not disable raw mode",
nu_protocol::location!(),
)
})?; })?;
Ok(Value::nothing(Span::unknown())) Ok(Value::nothing(Span::unknown()))

View File

@ -80,7 +80,7 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
report_shell_error( report_shell_error(
engine_state, engine_state,
&ShellError::Io(IoError::new_internal_with_path( &ShellError::Io(IoError::new_internal_with_path(
err.kind(), err,
"Could not open plugin registry file", "Could not open plugin registry file",
nu_protocol::location!(), nu_protocol::location!(),
plugin_path, plugin_path,
@ -323,7 +323,7 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
if let Err(err) = std::fs::File::create(&new_plugin_file_path) if let Err(err) = std::fs::File::create(&new_plugin_file_path)
.map_err(|err| { .map_err(|err| {
IoError::new_internal_with_path( IoError::new_internal_with_path(
err.kind(), err,
"Could not create new plugin file", "Could not create new plugin file",
nu_protocol::location!(), nu_protocol::location!(),
new_plugin_file_path.clone(), new_plugin_file_path.clone(),

View File

@ -28,7 +28,7 @@ pub fn evaluate_file(
let file_path = canonicalize_with(&path, cwd).map_err(|err| { let file_path = canonicalize_with(&path, cwd).map_err(|err| {
IoError::new_internal_with_path( IoError::new_internal_with_path(
err.kind().not_found_as(NotFound::File), err.not_found_as(NotFound::File),
"Could not access file", "Could not access file",
nu_protocol::location!(), nu_protocol::location!(),
PathBuf::from(&path), PathBuf::from(&path),
@ -47,7 +47,7 @@ pub fn evaluate_file(
let file = std::fs::read(&file_path).map_err(|err| { let file = std::fs::read(&file_path).map_err(|err| {
IoError::new_internal_with_path( IoError::new_internal_with_path(
err.kind().not_found_as(NotFound::File), err.not_found_as(NotFound::File),
"Could not read file", "Could not read file",
nu_protocol::location!(), nu_protocol::location!(),
file_path.clone(), file_path.clone(),

View File

@ -22,6 +22,7 @@ use nu_color_config::StyleComputer;
use nu_engine::env_to_strings; use nu_engine::env_to_strings;
use nu_engine::exit::cleanup_exit; use nu_engine::exit::cleanup_exit;
use nu_parser::{lex, parse, trim_quotes_str}; use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::shell_error;
use nu_protocol::shell_error::io::IoError; use nu_protocol::shell_error::io::IoError;
use nu_protocol::{ use nu_protocol::{
HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value,
@ -854,7 +855,7 @@ fn do_auto_cd(
report_shell_error( report_shell_error(
engine_state, engine_state,
&ShellError::Io(IoError::new_with_additional_context( &ShellError::Io(IoError::new_with_additional_context(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::DirectoryNotFound,
span, span,
PathBuf::from(&path), PathBuf::from(&path),
"Cannot change directory", "Cannot change directory",
@ -868,7 +869,7 @@ fn do_auto_cd(
report_shell_error( report_shell_error(
engine_state, engine_state,
&ShellError::Io(IoError::new_with_additional_context( &ShellError::Io(IoError::new_with_additional_context(
std::io::ErrorKind::PermissionDenied, shell_error::io::ErrorKind::from_std(std::io::ErrorKind::PermissionDenied),
span, span,
PathBuf::from(path), PathBuf::from(path),
"Cannot change directory", "Cannot change directory",

View File

@ -134,7 +134,7 @@ fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream {
let mut byte = [0]; let mut byte = [0];
if reader if reader
.read(&mut byte[..]) .read(&mut byte[..])
.map_err(|err| IoError::new(err.kind(), head, None))? .map_err(|err| IoError::new(err, head, None))?
> 0 > 0
{ {
// Format the byte as bits // Format the byte as bits

View File

@ -107,14 +107,14 @@ impl Command for Do {
let mut buf = Vec::new(); let mut buf = Vec::new();
stdout.read_to_end(&mut buf).map_err(|err| { stdout.read_to_end(&mut buf).map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not read stdout to end", "Could not read stdout to end",
nu_protocol::location!(), nu_protocol::location!(),
) )
})?; })?;
Ok::<_, ShellError>(buf) Ok::<_, ShellError>(buf)
}) })
.map_err(|err| IoError::new(err.kind(), head, None)) .map_err(|err| IoError::new(err, head, None))
}) })
.transpose()?; .transpose()?;
@ -126,7 +126,7 @@ impl Command for Do {
let mut buf = String::new(); let mut buf = String::new();
stderr stderr
.read_to_string(&mut buf) .read_to_string(&mut buf)
.map_err(|err| IoError::new(err.kind(), span, None))?; .map_err(|err| IoError::new(err, span, None))?;
buf buf
} }
}; };

View File

@ -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, || { let filename_expanded = nu_path::locate_in_dirs(&filename.item, &cwd, || {
get_plugin_dirs(engine_state, stack) 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 let shell_expanded = shell
.as_ref() .as_ref()
.map(|s| { .map(|s| {
nu_path::canonicalize_with(&s.item, &cwd) 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()?; .transpose()?;

View File

@ -1,6 +1,10 @@
#[allow(deprecated)] #[allow(deprecated)]
use nu_engine::{command_prelude::*, current_dir}; 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::{ use std::{
fs::{self, File}, fs::{self, File},
path::PathBuf, 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) { if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
PluginRegistryFile::read_from( PluginRegistryFile::read_from(
File::open(&plugin_registry_file_path) 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), Some(file_span),
) )
} else if let Some(path) = custom_path { } else if let Some(path) = custom_path {
Err(ShellError::Io(IoError::new( Err(ShellError::Io(IoError::new(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::FileNotFound,
path.span, path.span,
PathBuf::from(&path.item), PathBuf::from(&path.item),
))) )))
@ -75,9 +79,8 @@ pub(crate) fn modify_plugin_file(
// Try to read the plugin file if it exists // 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) { let mut contents = if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
PluginRegistryFile::read_from( PluginRegistryFile::read_from(
File::open(&plugin_registry_file_path).map_err(|err| { File::open(&plugin_registry_file_path)
IoError::new(err.kind(), file_span, plugin_registry_file_path.clone()) .map_err(|err| IoError::new(err, file_span, plugin_registry_file_path.clone()))?,
})?,
Some(file_span), Some(file_span),
)? )?
} else { } else {
@ -90,7 +93,7 @@ pub(crate) fn modify_plugin_file(
// Save the modified file on success // Save the modified file on success
contents.write_to( contents.write_to(
File::create(&plugin_registry_file_path) 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), Some(span),
)?; )?;

View File

@ -77,7 +77,7 @@ impl Command for BytesEndsWith {
Ok(&[]) => break, Ok(&[]) => break,
Ok(buf) => buf, Ok(buf) => buf,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, 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(); let len = buf.len();
if len >= cap { if len >= cap {

View File

@ -72,7 +72,7 @@ impl Command for BytesStartsWith {
reader reader
.take(pattern.len() as u64) .take(pattern.len() as u64)
.read_to_end(&mut start) .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()) Ok(Value::bool(start == pattern, head).into_pipeline_data())
} else { } else {

View File

@ -41,12 +41,11 @@ impl SQLiteDatabase {
} }
pub fn try_from_path(path: &Path, span: Span, signals: Signals) -> Result<Self, ShellError> { pub fn try_from_path(path: &Path, span: Span, signals: Signals) -> Result<Self, ShellError> {
let mut file = let mut file = File::open(path).map_err(|e| IoError::new(e, span, PathBuf::from(path)))?;
File::open(path).map_err(|e| IoError::new(e.kind(), span, PathBuf::from(path)))?;
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
file.read_exact(&mut buf) 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(|_| { .and_then(|_| {
if buf == SQLITE_MAGIC_BYTES { if buf == SQLITE_MAGIC_BYTES {
Ok(SQLiteDatabase::new(path, signals)) Ok(SQLiteDatabase::new(path, signals))

View File

@ -115,7 +115,7 @@ pub(super) fn start_editor(
let child = child.map_err(|err| { let child = child.map_err(|err| {
IoError::new_with_additional_context( IoError::new_with_additional_context(
err.kind(), err,
call.head, call.head,
None, None,
"Could not spawn foreground child", "Could not spawn foreground child",

View File

@ -59,7 +59,7 @@ impl Command for ConfigReset {
)); ));
if let Err(err) = std::fs::rename(nu_config.clone(), &backup_path) { if let Err(err) = std::fs::rename(nu_config.clone(), &backup_path) {
return Err(ShellError::Io(IoError::new_with_additional_context( return Err(ShellError::Io(IoError::new_with_additional_context(
err.kind().not_found_as(NotFound::Directory), err.not_found_as(NotFound::Directory),
span, span,
PathBuf::from(backup_path), PathBuf::from(backup_path),
"config.nu could not be backed up", "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 Ok(mut file) = std::fs::File::create(&nu_config) {
if let Err(err) = writeln!(&mut file, "{config_file}") { if let Err(err) = writeln!(&mut file, "{config_file}") {
return Err(ShellError::Io(IoError::new_with_additional_context( return Err(ShellError::Io(IoError::new_with_additional_context(
err.kind().not_found_as(NotFound::File), err.not_found_as(NotFound::File),
span, span,
PathBuf::from(nu_config), PathBuf::from(nu_config),
"config.nu could not be written to", "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"),)); 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) { if let Err(err) = std::fs::rename(env_config.clone(), &backup_path) {
return Err(ShellError::Io(IoError::new_with_additional_context( return Err(ShellError::Io(IoError::new_with_additional_context(
err.kind().not_found_as(NotFound::Directory), err.not_found_as(NotFound::Directory),
span, span,
PathBuf::from(backup_path), PathBuf::from(backup_path),
"env.nu could not be backed up", "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 Ok(mut file) = std::fs::File::create(&env_config) {
if let Err(err) = writeln!(&mut file, "{config_file}") { if let Err(err) = writeln!(&mut file, "{config_file}") {
return Err(ShellError::Io(IoError::new_with_additional_context( return Err(ShellError::Io(IoError::new_with_additional_context(
err.kind().not_found_as(NotFound::File), err.not_found_as(NotFound::File),
span, span,
PathBuf::from(env_config), PathBuf::from(env_config),
"env.nu could not be written to", "env.nu could not be written to",

View File

@ -2,7 +2,11 @@ use nu_engine::{
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return, command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return,
redirect_env, 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; use std::path::PathBuf;
/// Source a file for environment variables. /// Source a file for environment variables.
@ -66,7 +70,7 @@ impl Command for SourceEnv {
PathBuf::from(&path) PathBuf::from(&path)
} else { } else {
return Err(ShellError::Io(IoError::new( return Err(ShellError::Io(IoError::new(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::FileNotFound,
source_filename.span, source_filename.span,
PathBuf::from(source_filename.item), PathBuf::from(source_filename.item),
))); )));

View File

@ -53,7 +53,7 @@ impl Command for JobKill {
jobs.kill_and_remove(id).map_err(|err| { jobs.kill_and_remove(id).map_err(|err| {
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
err.kind(), err,
"Failed to kill the requested job", "Failed to kill the requested job",
nu_protocol::location!(), nu_protocol::location!(),
)) ))

View File

@ -121,7 +121,7 @@ impl Command for JobSpawn {
Err(err) => { Err(err) => {
jobs.remove_job(id); jobs.remove_job(id);
Err(ShellError::Io(IoError::new_with_additional_context( Err(ShellError::Io(IoError::new_with_additional_context(
err.kind(), err,
call.head, call.head,
None, None,
"Failed to spawn thread for job", "Failed to spawn thread for job",

View File

@ -3,7 +3,6 @@ use nu_protocol::{
JobId, JobId,
engine::{FrozenJob, Job, ThreadJob}, engine::{FrozenJob, Job, ThreadJob},
process::check_ok, process::check_ok,
shell_error,
}; };
use nu_system::{ForegroundWaitStatus, kill_by_pid}; use nu_system::{ForegroundWaitStatus, kill_by_pid};
@ -123,7 +122,7 @@ fn unfreeze_job(
if !thread_job.try_add_pid(pid) { if !thread_job.try_add_pid(pid) {
kill_by_pid(pid.into()).map_err(|err| { kill_by_pid(pid.into()).map_err(|err| {
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
err.kind(), err,
"job was interrupted; could not kill foreground process", "job was interrupted; could not kill foreground process",
nu_protocol::location!(), nu_protocol::location!(),
)) ))
@ -163,7 +162,7 @@ fn unfreeze_job(
Ok(ForegroundWaitStatus::Finished(status)) => check_ok(status, false, span), Ok(ForegroundWaitStatus::Finished(status)) => check_ok(status, false, span),
Err(err) => Err(ShellError::Io(IoError::new_internal( Err(err) => Err(ShellError::Io(IoError::new_internal(
shell_error::io::ErrorKind::Std(err.kind()), err,
"Failed to unfreeze foreground process", "Failed to unfreeze foreground process",
nu_protocol::location!(), nu_protocol::location!(),
))), ))),

View File

@ -77,7 +77,7 @@ impl Command for Cd {
if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) { if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) {
if !path.is_dir() { if !path.is_dir() {
return Err(shell_error::io::IoError::new( return Err(shell_error::io::IoError::new(
shell_error::io::ErrorKind::Std( shell_error::io::ErrorKind::from_std(
std::io::ErrorKind::NotADirectory, std::io::ErrorKind::NotADirectory,
), ),
v.span, v.span,
@ -106,7 +106,9 @@ impl Command for Cd {
}; };
if !path.is_dir() { if !path.is_dir() {
return Err(shell_error::io::IoError::new( 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, v.span,
path, path,
) )
@ -132,9 +134,12 @@ impl Command for Cd {
stack.set_cwd(path)?; stack.set_cwd(path)?;
Ok(PipelineData::empty()) Ok(PipelineData::empty())
} }
PermissionResult::PermissionDenied => { PermissionResult::PermissionDenied => Err(IoError::new(
Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into()) shell_error::io::ErrorKind::from_std(std::io::ErrorKind::PermissionDenied),
} call.head,
path,
)
.into()),
} }
} }

View File

@ -5,7 +5,10 @@ use nu_engine::glob_from;
use nu_engine::{command_prelude::*, env::current_dir}; use nu_engine::{command_prelude::*, env::current_dir};
use nu_glob::MatchOptions; use nu_glob::MatchOptions;
use nu_path::{expand_path_with, expand_to_real_path}; 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 pathdiff::diff_paths;
use rayon::prelude::*; use rayon::prelude::*;
#[cfg(unix)] #[cfg(unix)]
@ -252,7 +255,7 @@ fn ls_for_one_pattern(
// it makes no sense to list an empty string. // it makes no sense to list an empty string.
if path.item.as_ref().is_empty() { if path.item.as_ref().is_empty() {
return Err(ShellError::Io(IoError::new_with_additional_context( 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, path.span,
PathBuf::from(path.item.to_string()), PathBuf::from(path.item.to_string()),
"empty string('') directory or file does not exist", "empty string('') directory or file does not exist",
@ -357,7 +360,7 @@ fn ls_for_one_pattern(
let count = std::thread::available_parallelism() let count = std::thread::available_parallelism()
.map_err(|err| { .map_err(|err| {
IoError::new_with_additional_context( IoError::new_with_additional_context(
err.kind(), err,
call_span, call_span,
None, None,
"Could not get available parallelism", "Could not get available parallelism",
@ -793,6 +796,7 @@ fn unix_time_to_local_date_time(secs: i64) -> Option<DateTime<Local>> {
mod windows_helper { mod windows_helper {
use super::*; use super::*;
use nu_protocol::shell_error;
use std::os::windows::prelude::OsStrExt; use std::os::windows::prelude::OsStrExt;
use windows::Win32::Foundation::FILETIME; use windows::Win32::Foundation::FILETIME;
use windows::Win32::Storage::FileSystem::{ use windows::Win32::Storage::FileSystem::{
@ -928,7 +932,7 @@ mod windows_helper {
Ok(find_data) Ok(find_data)
} }
Err(e) => Err(ShellError::Io(IoError::new_with_additional_context( 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, span,
PathBuf::from(filename), PathBuf::from(filename),
format!("Could not read metadata: {e}"), format!("Could not read metadata: {e}"),
@ -973,11 +977,11 @@ fn read_dir(
let signals_clone = signals.clone(); let signals_clone = signals.clone();
let items = f let items = f
.read_dir() .read_dir()
.map_err(|err| IoError::new(err.kind(), span, f.clone()))? .map_err(|err| IoError::new(err, span, f.clone()))?
.map(move |d| { .map(move |d| {
signals_clone.check(span)?; signals_clone.check(span)?;
d.map(|r| r.path()) 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) .map_err(ShellError::from)
}); });
if !use_threads { if !use_threads {

View File

@ -121,7 +121,7 @@ impl Command for Open {
if permission_denied(path) { if permission_denied(path) {
let err = IoError::new( let err = IoError::new(
std::io::ErrorKind::PermissionDenied, shell_error::io::ErrorKind::from_std(std::io::ErrorKind::PermissionDenied),
arg_span, arg_span,
PathBuf::from(path), PathBuf::from(path),
); );
@ -162,14 +162,18 @@ impl Command for Open {
// At least under windows this check ensures that we don't get a // At least under windows this check ensures that we don't get a
// permission denied error on directories // permission denied error on directories
return Err(ShellError::Io(IoError::new( 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, arg_span,
PathBuf::from(path), PathBuf::from(path),
))); )));
} }
let file = std::fs::File::open(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 // No content_type by default - Is added later if no converter is found
let stream = PipelineData::ByteStream( let stream = PipelineData::ByteStream(

View File

@ -304,7 +304,7 @@ fn rm(
&& matches!( && matches!(
e, e,
ShellError::Io(IoError { 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 { 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 { } else if verbose {
let msg = if interactive && !confirmed { let msg = if interactive && !confirmed {
"not deleted" "not deleted"

View File

@ -130,7 +130,7 @@ impl Command for Save {
io::copy(&mut tee, &mut io::stderr()) 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(()) Ok(())
} }
@ -428,20 +428,24 @@ fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError>
(true, true) => std::fs::OpenOptions::new() (true, true) => std::fs::OpenOptions::new()
.append(true) .append(true)
.open(path) .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) // 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 // A TOCTOU problem exists here, which may cause wrong error message to be shown
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
if path.is_dir() { 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, std::io::ErrorKind::IsADirectory,
)) ))
} else { } 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"))] #[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())
} }
}; };

View File

@ -1,6 +1,9 @@
#[allow(deprecated)] #[allow(deprecated)]
use nu_engine::{command_prelude::*, current_dir}; 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 std::path::PathBuf;
use uu_cp::{BackupMode, CopyMode, UpdateMode}; use uu_cp::{BackupMode, CopyMode, UpdateMode};
@ -198,7 +201,7 @@ impl Command for UCp {
.collect(); .collect();
if exp_files.is_empty() { if exp_files.is_empty() {
return Err(ShellError::Io(IoError::new( return Err(ShellError::Io(IoError::new(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::FileNotFound,
p.span, p.span,
PathBuf::from(p.item.to_string()), PathBuf::from(p.item.to_string()),
))); )));

View File

@ -1,7 +1,10 @@
#[allow(deprecated)] #[allow(deprecated)]
use nu_engine::{command_prelude::*, current_dir}; use nu_engine::{command_prelude::*, current_dir};
use nu_path::expand_path_with; 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 std::{ffi::OsString, path::PathBuf};
use uu_mv::{BackupMode, UpdateMode}; use uu_mv::{BackupMode, UpdateMode};
@ -139,7 +142,7 @@ impl Command for UMv {
.collect(); .collect();
if exp_files.is_empty() { if exp_files.is_empty() {
return Err(ShellError::Io(IoError::new( return Err(ShellError::Io(IoError::new(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::FileNotFound,
p.span, p.span,
PathBuf::from(p.item.to_string()), PathBuf::from(p.item.to_string()),
))); )));

View File

@ -227,7 +227,7 @@ impl Command for UTouch {
TouchError::ReferenceFileInaccessible(reference_path, io_err) => { TouchError::ReferenceFileInaccessible(reference_path, io_err) => {
let span = reference_span.expect("touch should've been given a reference file"); let span = reference_span.expect("touch should've been given a reference file");
ShellError::Io(IoError::new_with_additional_context( ShellError::Io(IoError::new_with_additional_context(
io_err.kind(), io_err,
span, span,
reference_path, reference_path,
"failed to read metadata", "failed to read metadata",

View File

@ -86,7 +86,7 @@ impl Command for Watch {
Ok(p) => p, Ok(p) => p,
Err(err) => { Err(err) => {
return Err(ShellError::Io(IoError::new( return Err(ShellError::Io(IoError::new(
err.kind(), err,
path_arg.span, path_arg.span,
PathBuf::from(path_no_whitespace), PathBuf::from(path_no_whitespace),
))); )));

View File

@ -37,7 +37,7 @@ pub fn empty(
.bytes() .bytes()
.next() .next()
.transpose() .transpose()
.map_err(|err| IoError::new(err.kind(), span, None))? .map_err(|err| IoError::new(err, span, None))?
.is_none(); .is_none();
if negate { if negate {
Ok(Value::bool(!is_empty, head).into_pipeline_data()) Ok(Value::bool(!is_empty, head).into_pipeline_data())

View File

@ -182,7 +182,7 @@ fn first_helper(
let mut byte = [0u8]; let mut byte = [0u8];
if reader if reader
.read(&mut byte) .read(&mut byte)
.map_err(|err| IoError::new(err.kind(), span, None))? .map_err(|err| IoError::new(err, span, None))?
> 0 > 0
{ {
Ok(Value::int(byte[0] as i64, head).into_pipeline_data()) Ok(Value::int(byte[0] as i64, head).into_pipeline_data())

View File

@ -137,7 +137,7 @@ interleave
} }
}) })
.map(|_| ()) .map(|_| ())
.map_err(|err| IoError::new(err.kind(), head, None).into()) .map_err(|err| IoError::new(err, head, None).into())
}) })
})?; })?;

View File

@ -166,7 +166,7 @@ impl Command for Last {
let mut buf = VecDeque::with_capacity(rows + TAKE as usize); let mut buf = VecDeque::with_capacity(rows + TAKE as usize);
loop { loop {
let taken = std::io::copy(&mut (&mut reader).take(TAKE), &mut buf) 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 { if buf.len() > rows {
buf.drain(..(buf.len() - rows)); buf.drain(..(buf.len() - rows));
} }

View File

@ -1,6 +1,8 @@
use nu_engine::{command_prelude::*, get_eval_block_with_early_return}; use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
#[cfg(feature = "os")] #[cfg(feature = "os")]
use nu_protocol::process::ChildPipe; use nu_protocol::process::ChildPipe;
#[cfg(test)]
use nu_protocol::shell_error;
use nu_protocol::{ use nu_protocol::{
ByteStream, ByteStreamSource, OutDest, PipelineMetadata, Signals, ByteStream, ByteStreamSource, OutDest, PipelineMetadata, Signals,
byte_stream::copy_with_signals, engine::Closure, report_shell_error, shell_error::io::IoError, 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)) eval_block(PipelineData::ByteStream(stream, info.metadata))
}) })
.map_err(|err| { .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 }) Ok(TeeThread { sender, thread })
@ -481,12 +483,7 @@ fn copy_on_thread(
Ok(()) Ok(())
}) })
.map_err(|err| { .map_err(|err| {
IoError::new_with_additional_context( IoError::new_with_additional_context(err, span, None, "Could not spawn stderr copier")
err.kind(),
span,
None,
"Could not spawn stderr copier",
)
.into() .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 slow_input = (0..100).inspect(|_| std::thread::sleep(Duration::from_millis(1)));
let iter = tee(slow_input, |_| { let iter = tee(slow_input, |_| {
Err(ShellError::Io(IoError::new_with_additional_context( 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(), Span::test_data(),
None, None,
"test", "test",
@ -564,7 +561,7 @@ fn tee_waits_for_the_other_thread() {
std::thread::sleep(Duration::from_millis(10)); std::thread::sleep(Duration::from_millis(10));
waited_clone.store(true, Ordering::Relaxed); waited_clone.store(true, Ordering::Relaxed);
Err(ShellError::Io(IoError::new_with_additional_context( 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(), Span::test_data(),
None, None,
"test", "test",

View File

@ -134,7 +134,7 @@ fn read_json_lines(
.lines() .lines()
.filter(|line| line.as_ref().is_ok_and(|line| !line.trim().is_empty()) || line.is_err()) .filter(|line| line.as_ref().is_ok_and(|line| !line.trim().is_empty()) || line.is_err())
.map(move |line| { .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 { if strict {
convert_string_to_value_strict(&line, span) convert_string_to_value_strict(&line, span)
} else { } else {

View File

@ -8,7 +8,7 @@ use std::{iter, sync::Arc};
fn make_csv_error(error: csv::Error, format_name: &str, head: Span) -> ShellError { fn make_csv_error(error: csv::Error, format_name: &str, head: Span) -> ShellError {
if let csv::ErrorKind::Io(error) = error.kind() { if let csv::ErrorKind::Io(error) = error.kind() {
IoError::new(error.kind(), head, None).into() IoError::new(error, head, None).into()
} else { } else {
ShellError::GenericError { ShellError::GenericError {
error: format!("Failed to generate {format_name} data"), error: format!("Failed to generate {format_name} data"),

View File

@ -152,7 +152,7 @@ impl From<WriteError> for ShellError {
help: None, help: None,
inner: vec![], 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, WriteError::Shell(err) => *err,
} }
} }

View File

@ -95,7 +95,7 @@ impl Command for ToMsgpackz {
serialize_types, serialize_types,
)?; )?;
out.flush() out.flush()
.map_err(|err| IoError::new(err.kind(), call.head, None))?; .map_err(|err| IoError::new(err, call.head, None))?;
drop(out); drop(out);
Ok(Value::binary(out_buf, call.head).into_pipeline_data()) Ok(Value::binary(out_buf, call.head).into_pipeline_data())

View File

@ -55,13 +55,8 @@ impl Command for Source {
let cwd = engine_state.cwd_as_string(Some(stack))?; let cwd = engine_state.cwd_as_string(Some(stack))?;
let pb = std::path::PathBuf::from(block_id_name); let pb = std::path::PathBuf::from(block_id_name);
let parent = pb.parent().unwrap_or(std::path::Path::new("")); let parent = pb.parent().unwrap_or(std::path::Path::new(""));
let file_path = canonicalize_with(pb.as_path(), cwd).map_err(|err| { let file_path = canonicalize_with(pb.as_path(), cwd)
IoError::new( .map_err(|err| IoError::new(err.not_found_as(NotFound::File), call.head, pb.clone()))?;
err.kind().not_found_as(NotFound::File),
call.head,
pb.clone(),
)
})?;
// Note: We intentionally left out PROCESS_PATH since it's supposed to // 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. // to work like argv[0] in C, which is the name of the program being executed.

View File

@ -374,9 +374,7 @@ fn send_multipart_request(
let mut builder = MultipartWriter::new(); let mut builder = MultipartWriter::new();
let err = |e: std::io::Error| { let err = |e: std::io::Error| {
ShellErrorOrRequestError::ShellError( ShellErrorOrRequestError::ShellError(IoError::new(e, span, None).into())
IoError::new_with_additional_context(e.kind(), span, None, e).into(),
)
}; };
for (col, val) in val.into_owned() { 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 let _ = tx.send(ret); // may fail if the user has cancelled the operation
}) })
.map_err(|err| { .map_err(|err| {
IoError::new_with_additional_context( IoError::new_with_additional_context(err, span, None, "Could not spawn HTTP requester")
err.kind(),
span,
None,
"Could not spawn HTTP requester",
)
}) })
.map_err(ShellError::from)?; .map_err(ShellError::from)?;
@ -529,12 +522,7 @@ fn send_cancellable_request_bytes(
let _ = tx.send(ret); let _ = tx.send(ret);
}) })
.map_err(|err| { .map_err(|err| {
IoError::new_with_additional_context( IoError::new_with_additional_context(err, span, None, "Could not spawn HTTP requester")
err.kind(),
span,
None,
"Could not spawn HTTP requester",
)
}) })
.map_err(ShellError::from)?; .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(); 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(), _ => generic_network_failure(),
} }

View File

@ -132,7 +132,7 @@ fn get_free_port(
} }
Err(IoError::new_with_additional_context( 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, range_span,
None, None,
"Every port has been tried, but no valid one was found", "Every port has been tried, but no valid one was found",

View File

@ -153,7 +153,7 @@ fn exists(path: &Path, span: Span, args: &Arguments) -> Value {
Value::bool( Value::bool(
match exists { match exists {
Ok(exists) => 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, span,
) )

View File

@ -1,6 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_path::expand_path_with; 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)] #[derive(Clone)]
pub struct PathSelf; pub struct PathSelf;
@ -56,7 +59,7 @@ impl Command for PathSelf {
let cwd = working_set.permanent_state.cwd(None)?; let cwd = working_set.permanent_state.cwd(None)?;
let current_file = working_set.files.top().ok_or_else(|| { let current_file = working_set.files.top().ok_or_else(|| {
IoError::new_with_additional_context( IoError::new_with_additional_context(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::FileNotFound,
call.head, call.head,
None, None,
"Couldn't find current file", "Couldn't find current file",
@ -67,7 +70,7 @@ impl Command for PathSelf {
let dir = expand_path_with( let dir = expand_path_with(
current_file.parent().ok_or_else(|| { current_file.parent().ok_or_else(|| {
IoError::new_with_additional_context( IoError::new_with_additional_context(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::FileNotFound,
call.head, call.head,
current_file.to_owned(), current_file.to_owned(),
"Couldn't find current file's parent.", "Couldn't find current file's parent.",

View File

@ -108,7 +108,7 @@ fn path_type(path: &Path, span: Span, args: &Arguments) -> Value {
match path.symlink_metadata() { match path.symlink_metadata() {
Ok(metadata) => Value::string(get_file_type(&metadata), span), Ok(metadata) => Value::string(get_file_type(&metadata), span),
Err(err) if err.kind() == io::ErrorKind::NotFound => Value::nothing(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),
} }
} }

View File

@ -77,7 +77,7 @@ impl FileInfo {
long, long,
}) })
} }
Err(e) => Err(IoError::new(e.kind(), tag, path).into()), Err(e) => Err(IoError::new(e, tag, path).into()),
} }
} }
} }

View File

@ -7,7 +7,7 @@ use crossterm::{
}; };
use itertools::Itertools; use itertools::Itertools;
use nu_engine::command_prelude::*; 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}; use std::{io::Write, time::Duration};
@ -116,7 +116,9 @@ impl Command for Input {
crossterm::terminal::disable_raw_mode() crossterm::terminal::disable_raw_mode()
.map_err(&from_io_error)?; .map_err(&from_io_error)?;
return Err(IoError::new( return Err(IoError::new(
shell_error::io::ErrorKind::from_std(
std::io::ErrorKind::Interrupted, std::io::ErrorKind::Interrupted,
),
call.head, call.head,
None, None,
) )
@ -156,7 +158,7 @@ impl Command for Input {
terminal::Clear(ClearType::CurrentLine), terminal::Clear(ClearType::CurrentLine),
cursor::MoveToColumn(0), 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 { if let Some(prompt) = &prompt {
execute!(std::io::stdout(), Print(prompt.to_string())) execute!(std::io::stdout(), Print(prompt.to_string()))
.map_err(&from_io_error)?; .map_err(&from_io_error)?;

View File

@ -84,7 +84,7 @@ There are 4 `key_type` variants:
let add_raw = call.has_flag(engine_state, stack, "raw")?; let add_raw = call.has_flag(engine_state, stack, "raw")?;
let config = engine_state.get_config(); 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 config.use_kitty_protocol {
if let Ok(false) = crossterm::terminal::supports_keyboard_enhancement() { 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); let event = parse_event(head, &event, &event_type_filter, add_raw);
if let Some(event) = event { 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 { if config.use_kitty_protocol {
let _ = execute!( let _ = execute!(
std::io::stdout(), std::io::stdout(),
@ -230,17 +230,17 @@ impl EventTypeFilter {
fn enable_events(&self, span: Span) -> Result<DeferredConsoleRestore, ShellError> { fn enable_events(&self, span: Span) -> Result<DeferredConsoleRestore, ShellError> {
if self.listen_mouse { if self.listen_mouse {
crossterm::execute!(stdout(), EnableMouseCapture) 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 { if self.listen_paste {
crossterm::execute!(stdout(), EnableBracketedPaste) 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 { if self.listen_focus {
crossterm::execute!(stdout(), crossterm::event::EnableFocusChange) 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 { Ok(DeferredConsoleRestore {

View File

@ -142,12 +142,7 @@ impl Command for InputList {
.report(false) .report(false)
.interact_on_opt(&Term::stderr()) .interact_on_opt(&Term::stderr())
.map_err(|dialoguer::Error::IO(err)| { .map_err(|dialoguer::Error::IO(err)| {
IoError::new_with_additional_context( IoError::new_with_additional_context(err, call.head, None, INTERACT_ERROR)
err.kind(),
call.head,
None,
INTERACT_ERROR,
)
})?, })?,
) )
} else if fuzzy { } else if fuzzy {
@ -164,12 +159,7 @@ impl Command for InputList {
.report(false) .report(false)
.interact_on_opt(&Term::stderr()) .interact_on_opt(&Term::stderr())
.map_err(|dialoguer::Error::IO(err)| { .map_err(|dialoguer::Error::IO(err)| {
IoError::new_with_additional_context( IoError::new_with_additional_context(err, call.head, None, INTERACT_ERROR)
err.kind(),
call.head,
None,
INTERACT_ERROR,
)
})?, })?,
) )
} else { } else {
@ -185,12 +175,7 @@ impl Command for InputList {
.report(false) .report(false)
.interact_on_opt(&Term::stderr()) .interact_on_opt(&Term::stderr())
.map_err(|dialoguer::Error::IO(err)| { .map_err(|dialoguer::Error::IO(err)| {
IoError::new_with_additional_context( IoError::new_with_additional_context(err, call.head, None, INTERACT_ERROR)
err.kind(),
call.head,
None,
INTERACT_ERROR,
)
})?, })?,
) )
}; };

View File

@ -99,19 +99,17 @@ The `prefix` is not included in the output."
let prefix = prefix.unwrap_or_default(); let prefix = prefix.unwrap_or_default();
let terminator: Option<Vec<u8>> = call.get_flag(engine_state, stack, "terminator")?; let terminator: Option<Vec<u8>> = call.get_flag(engine_state, stack, "terminator")?;
crossterm::terminal::enable_raw_mode() crossterm::terminal::enable_raw_mode().map_err(|err| IoError::new(err, call.head, None))?;
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
scopeguard::defer! { scopeguard::defer! {
let _ = crossterm::terminal::disable_raw_mode(); let _ = crossterm::terminal::disable_raw_mode();
} }
// clear terminal events // clear terminal events
while crossterm::event::poll(Duration::from_secs(0)) 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 // If there's an event, read it to remove it from the queue
let _ = crossterm::event::read() let _ = crossterm::event::read().map_err(|err| IoError::new(err, call.head, None))?;
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
} }
let mut b = [0u8; 1]; let mut b = [0u8; 1];
@ -122,17 +120,17 @@ The `prefix` is not included in the output."
let mut stdout = std::io::stdout().lock(); let mut stdout = std::io::stdout().lock();
stdout stdout
.write_all(&query) .write_all(&query)
.map_err(|err| IoError::new(err.kind(), call.head, None))?; .map_err(|err| IoError::new(err, call.head, None))?;
stdout stdout
.flush() .flush()
.map_err(|err| IoError::new(err.kind(), call.head, None))?; .map_err(|err| IoError::new(err, call.head, None))?;
} }
// Validate and skip prefix // Validate and skip prefix
for bc in prefix { for bc in prefix {
stdin stdin
.read_exact(&mut b) .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 { if b[0] != bc {
return Err(ShellError::GenericError { return Err(ShellError::GenericError {
error: "Input did not begin with expected sequence".into(), error: "Input did not begin with expected sequence".into(),
@ -151,7 +149,7 @@ The `prefix` is not included in the output."
loop { loop {
stdin stdin
.read_exact(&mut b) .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 { if b[0] == CTRL_C {
return Err(ShellError::InterruptedByUser { return Err(ShellError::InterruptedByUser {
@ -173,7 +171,7 @@ The `prefix` is not included in the output."
loop { loop {
stdin stdin
.read_exact(&mut b) .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 { if b[0] == CTRL_C {
break; break;

View File

@ -255,7 +255,7 @@ fn parse_file_script(
match std::fs::read(path) { match std::fs::read(path) {
Ok(contents) => parse_script(working_set, Some(&filename), &contents, is_debug, call_head), Ok(contents) => parse_script(working_set, Some(&filename), &contents, is_debug, call_head),
Err(err) => Err(ShellError::Io(IoError::new( Err(err) => Err(ShellError::Io(IoError::new(
err.kind().not_found_as(NotFound::File), err.not_found_as(NotFound::File),
path_span, path_span,
PathBuf::from(path), PathBuf::from(path),
))), ))),

View File

@ -93,7 +93,7 @@ fn registry_query(
let reg_hive = get_reg_hive(engine_state, stack, call)?; let reg_hive = get_reg_hive(engine_state, stack, call)?;
let reg_key = reg_hive let reg_key = reg_hive
.open_subkey(registry_key.item) .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() { if registry_value.is_none() {
let mut reg_values = vec![]; let mut reg_values = vec![];

View File

@ -205,11 +205,11 @@ impl Command for External {
let stderr = stack.stderr(); let stderr = stack.stderr();
let merged_stream = if matches!(stdout, OutDest::Pipe) && matches!(stderr, OutDest::Pipe) { let merged_stream = if matches!(stdout, OutDest::Pipe) && matches!(stderr, OutDest::Pipe) {
let (reader, writer) = 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( command.stdout(
writer writer
.try_clone() .try_clone()
.map_err(|err| IoError::new(err.kind(), call.head, None))?, .map_err(|err| IoError::new(err, call.head, None))?,
); );
command.stderr(writer); command.stderr(writer);
Some(reader) Some(reader)
@ -220,8 +220,7 @@ impl Command for External {
command.stdout(Stdio::null()); command.stdout(Stdio::null());
} else { } else {
command.stdout( command.stdout(
Stdio::try_from(stdout) Stdio::try_from(stdout).map_err(|err| IoError::new(err, call.head, None))?,
.map_err(|err| IoError::new(err.kind(), call.head, None))?,
); );
} }
@ -231,8 +230,7 @@ impl Command for External {
command.stderr(Stdio::null()); command.stderr(Stdio::null());
} else { } else {
command.stderr( command.stderr(
Stdio::try_from(stderr) Stdio::try_from(stderr).map_err(|err| IoError::new(err, call.head, None))?,
.map_err(|err| IoError::new(err.kind(), call.head, None))?,
); );
} }
@ -280,7 +278,7 @@ impl Command for External {
let mut child = child.map_err(|err| { let mut child = child.map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not spawn foreground child", "Could not spawn foreground child",
nu_protocol::location!(), nu_protocol::location!(),
) )
@ -290,7 +288,7 @@ impl Command for External {
if !thread_job.try_add_pid(child.pid()) { if !thread_job.try_add_pid(child.pid()) {
kill_by_pid(child.pid().into()).map_err(|err| { kill_by_pid(child.pid().into()).map_err(|err| {
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
err.kind(), err,
"Could not spawn external stdin worker", "Could not spawn external stdin worker",
nu_protocol::location!(), nu_protocol::location!(),
)) ))
@ -310,7 +308,7 @@ impl Command for External {
}) })
.map_err(|err| { .map_err(|err| {
IoError::new_with_additional_context( IoError::new_with_additional_context(
err.kind(), err,
call.head, call.head,
None, None,
"Could not spawn external stdin worker", "Could not spawn external stdin worker",
@ -498,7 +496,7 @@ fn write_pipeline_data(
} else if let PipelineData::Value(Value::Binary { val, .. }, ..) = data { } else if let PipelineData::Value(Value::Binary { val, .. }, ..) = data {
writer.write_all(&val).map_err(|err| { writer.write_all(&val).map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not write pipeline data", "Could not write pipeline data",
nu_protocol::location!(), nu_protocol::location!(),
) )
@ -518,7 +516,7 @@ fn write_pipeline_data(
let bytes = value.coerce_into_binary()?; let bytes = value.coerce_into_binary()?;
writer.write_all(&bytes).map_err(|err| { writer.write_all(&bytes).map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not write pipeline data", "Could not write pipeline data",
nu_protocol::location!(), nu_protocol::location!(),
) )

View File

@ -518,7 +518,7 @@ fn pretty_hex_stream(stream: ByteStream, span: Span) -> ByteStream {
(&mut reader) (&mut reader)
.take(cfg.width as u64) .take(cfg.width as u64)
.read_to_end(&mut read_buf) .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() { if !read_buf.is_empty() {
nu_pretty_hex::hex_write(&mut write_buf, &read_buf, cfg, Some(true)) nu_pretty_hex::hex_write(&mut write_buf, &read_buf, cfg, Some(true))

View File

@ -60,5 +60,5 @@ fn self_path_runtime() {
fn self_path_repl() { fn self_path_repl() {
let actual = nu!("const foo = path self; $foo"); let actual = nu!("const foo = path self; $foo");
assert!(!actual.status.success()); assert!(!actual.status.success());
assert!(actual.err.contains("nu::shell::io::not_found")); assert!(actual.err.contains("nu::shell::io::file_not_found"));
} }

View File

@ -6,6 +6,8 @@ use nu_test_support::playground::Playground;
use rstest::rstest; use rstest::rstest;
#[cfg(not(windows))] #[cfg(not(windows))]
use std::fs; use std::fs;
#[cfg(windows)]
use std::{fs::OpenOptions, os::windows::fs::OpenOptionsExt};
#[test] #[test]
fn removes_a_file() { fn removes_a_file() {
@ -562,3 +564,26 @@ fn rm_with_tilde() {
assert!(!files_exist_at(&["~tilde"], dirs.test())); 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"));
})
}

View File

@ -4,7 +4,7 @@ use nu_protocol::{
ShellError, Span, Type, Value, VarId, ShellError, Span, Type, Value, VarId,
ast::Expr, ast::Expr,
engine::{Call, EngineState, Stack, StateWorkingSet}, engine::{Call, EngineState, Stack, StateWorkingSet},
shell_error::io::{ErrorKindExt, IoError, NotFound}, shell_error::io::{IoError, IoErrorExt, NotFound},
}; };
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -221,7 +221,7 @@ pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf,
// be an absolute path already. // be an absolute path already.
canonicalize_with(&cwd, ".").map_err(|err| { canonicalize_with(&cwd, ".").map_err(|err| {
ShellError::Io(IoError::new_internal_with_path( ShellError::Io(IoError::new_internal_with_path(
err.kind().not_found_as(NotFound::Directory), err.not_found_as(NotFound::Directory),
"Could not canonicalize current dir", "Could not canonicalize current dir",
nu_protocol::location!(), nu_protocol::location!(),
PathBuf::from(cwd), PathBuf::from(cwd),
@ -241,7 +241,7 @@ pub fn current_dir_const(working_set: &StateWorkingSet) -> Result<PathBuf, Shell
// be an absolute path already. // be an absolute path already.
canonicalize_with(&cwd, ".").map_err(|err| { canonicalize_with(&cwd, ".").map_err(|err| {
ShellError::Io(IoError::new_internal_with_path( ShellError::Io(IoError::new_internal_with_path(
err.kind().not_found_as(NotFound::Directory), err.not_found_as(NotFound::Directory),
"Could not canonicalize current dir", "Could not canonicalize current dir",
nu_protocol::location!(), nu_protocol::location!(),
PathBuf::from(cwd), PathBuf::from(cwd),

View File

@ -1529,7 +1529,7 @@ fn open_file(ctx: &EvalContext<'_>, path: &Value, append: bool) -> Result<Arc<Fi
let file = options let file = options
.create(true) .create(true)
.open(&path_expanded) .open(&path_expanded)
.map_err(|err| IoError::new(err.kind(), path.span(), path_expanded))?; .map_err(|err| IoError::new(err, path.span(), path_expanded))?;
Ok(Arc::new(file)) Ok(Arc::new(file))
} }

View File

@ -81,7 +81,7 @@ pub fn glob_from(
} }
Ok(p) => p, Ok(p) => p,
Err(err) => { 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) (path.parent().map(|parent| parent.to_path_buf()), path)

View File

@ -87,7 +87,7 @@ impl CommunicationMode {
.and_then(|name| ListenerOptions::new().name(name).create_sync()) .and_then(|name| ListenerOptions::new().name(name).create_sync())
.map_err(|err| { .map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
format!( format!(
"Could not interpret local socket name {:?}", "Could not interpret local socket name {:?}",
name.to_string_lossy() name.to_string_lossy()
@ -117,7 +117,7 @@ impl CommunicationMode {
.and_then(|name| ls::Stream::connect(name)) .and_then(|name| ls::Stream::connect(name))
.map_err(|err| { .map_err(|err| {
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
err.kind(), err,
format!( format!(
"Could not interpret local socket name {:?}", "Could not interpret local socket name {:?}",
name.to_string_lossy() name.to_string_lossy()
@ -190,7 +190,7 @@ impl PreparedServerCommunication {
.set_nonblocking(ListenerNonblockingMode::Accept) .set_nonblocking(ListenerNonblockingMode::Accept)
.map_err(|err| { .map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not set non-blocking mode accept for listener", "Could not set non-blocking mode accept for listener",
nu_protocol::location!(), nu_protocol::location!(),
) )
@ -204,7 +204,7 @@ impl PreparedServerCommunication {
// good measure. Had an issue without this on macOS. // good measure. Had an issue without this on macOS.
stream.set_nonblocking(false).map_err(|err| { stream.set_nonblocking(false).map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not disable non-blocking mode for listener", "Could not disable non-blocking mode for listener",
nu_protocol::location!(), nu_protocol::location!(),
) )
@ -217,7 +217,7 @@ impl PreparedServerCommunication {
// `WouldBlock` is ok, just means it's not ready yet, but some other // `WouldBlock` is ok, just means it's not ready yet, but some other
// kind of error should be reported // kind of error should be reported
return Err(ShellError::Io(IoError::new_internal( return Err(ShellError::Io(IoError::new_internal(
err.kind(), err,
"Accepting new data from listener failed", "Accepting new data from listener failed",
nu_protocol::location!(), nu_protocol::location!(),
))); )));

View File

@ -82,7 +82,7 @@ where
fn flush(&self) -> Result<(), ShellError> { fn flush(&self) -> Result<(), ShellError> {
self.0.lock().flush().map_err(|err| { self.0.lock().flush().map_err(|err| {
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
err.kind(), err,
"PluginWrite could not flush", "PluginWrite could not flush",
nu_protocol::location!(), nu_protocol::location!(),
)) ))
@ -112,7 +112,7 @@ where
})?; })?;
lock.flush().map_err(|err| { lock.flush().map_err(|err| {
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
err.kind(), err,
"PluginWrite could not flush", "PluginWrite could not flush",
nu_protocol::location!(), nu_protocol::location!(),
)) ))
@ -340,7 +340,7 @@ where
writer.write_all(std::iter::from_fn(move || match reader.read(buf) { writer.write_all(std::iter::from_fn(move || match reader.read(buf) {
Ok(0) => None, Ok(0) => None,
Ok(len) => Some(Ok(buf[..len].to_vec())), 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(()) Ok(())
} }
@ -368,7 +368,7 @@ where
}) })
.map_err(|err| { .map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not spawn plugin stream background writer", "Could not spawn plugin stream background writer",
nu_protocol::location!(), nu_protocol::location!(),
) )

View File

@ -246,7 +246,7 @@ fn read_pipeline_data_byte_stream() -> Result<(), ShellError> {
ByteStreamSource::Read(mut read) => { ByteStreamSource::Read(mut read) => {
let mut buf = Vec::new(); let mut buf = Vec::new();
read.read_to_end(&mut buf) 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()); let iter = buf.chunks_exact(out_pattern.len());
assert_eq!(iter.len(), iterations); assert_eq!(iter.len(), iterations);
for chunk in iter { for chunk in iter {

View File

@ -1,5 +1,8 @@
use nu_plugin_protocol::{PluginInput, PluginOutput}; 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 serde::Deserialize;
use crate::{Encoder, PluginEncoder}; use crate::{Encoder, PluginEncoder};
@ -28,7 +31,7 @@ impl Encoder<PluginInput> for JsonSerializer {
serde_json::to_writer(&mut *writer, plugin_input).map_err(json_encode_err)?; serde_json::to_writer(&mut *writer, plugin_input).map_err(json_encode_err)?;
writer.write_all(b"\n").map_err(|err| { writer.write_all(b"\n").map_err(|err| {
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
err.kind(), err,
"Failed to write final line break", "Failed to write final line break",
location!(), location!(),
)) ))
@ -55,7 +58,7 @@ impl Encoder<PluginOutput> for JsonSerializer {
serde_json::to_writer(&mut *writer, plugin_output).map_err(json_encode_err)?; serde_json::to_writer(&mut *writer, plugin_output).map_err(json_encode_err)?;
writer.write_all(b"\n").map_err(|err| { writer.write_all(b"\n").map_err(|err| {
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
err.kind(), err,
"JsonSerializer could not encode linebreak", "JsonSerializer could not encode linebreak",
nu_protocol::location!(), nu_protocol::location!(),
)) ))
@ -77,7 +80,7 @@ impl Encoder<PluginOutput> for JsonSerializer {
fn json_encode_err(err: serde_json::Error) -> ShellError { fn json_encode_err(err: serde_json::Error) -> ShellError {
if err.is_io() { if err.is_io() {
ShellError::Io(IoError::new_internal( 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", "Could not encode with json",
nu_protocol::location!(), nu_protocol::location!(),
)) ))
@ -94,7 +97,7 @@ fn json_decode_err<T>(err: serde_json::Error) -> Result<Option<T>, ShellError> {
Ok(None) Ok(None)
} else if err.is_io() { } else if err.is_io() {
Err(ShellError::Io(IoError::new_internal( 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", "Could not decode with json",
nu_protocol::location!(), nu_protocol::location!(),
))) )))

View File

@ -1,7 +1,10 @@
use std::io::ErrorKind; use std::io::ErrorKind;
use nu_plugin_protocol::{PluginInput, PluginOutput}; 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 serde::Deserialize;
use crate::{Encoder, PluginEncoder}; use crate::{Encoder, PluginEncoder};
@ -66,7 +69,7 @@ fn rmp_encode_err(err: rmp_serde::encode::Error) -> ShellError {
// I/O error // I/O error
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
// TODO: get a better kind here // 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", "Could not encode with rmp",
nu_protocol::location!(), nu_protocol::location!(),
)) ))
@ -92,7 +95,7 @@ fn rmp_decode_err<T>(err: rmp_serde::decode::Error) -> Result<Option<T>, ShellEr
// I/O error // I/O error
Err(ShellError::Io(IoError::new_internal( Err(ShellError::Io(IoError::new_internal(
// TODO: get a better kind here // 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", "Could not decode with rmp",
nu_protocol::location!(), nu_protocol::location!(),
))) )))

View File

@ -379,7 +379,7 @@ macro_rules! generate_tests {
.with_help("some help") .with_help("some help")
.with_label("msg", Span::new(2, 30)) .with_label("msg", Span::new(2, 30))
.with_inner(ShellError::Io(IoError::new( .with_inner(ShellError::Io(IoError::new(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::from_std(std::io::ErrorKind::NotFound),
Span::test_data(), Span::test_data(),
None, None,
))); )));

View File

@ -20,6 +20,7 @@ use nu_protocol::{
Spanned, Value, Spanned, Value,
ast::{Math, Operator}, ast::{Math, Operator},
engine::Closure, engine::Closure,
shell_error,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
@ -87,7 +88,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul
fn test_io_error() -> ShellError { fn test_io_error() -> ShellError {
ShellError::Io(IoError::new_with_additional_context( ShellError::Io(IoError::new_with_additional_context(
std::io::ErrorKind::Other, shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other),
Span::test_data(), Span::test_data(),
None, None,
"test io error", "test io error",

View File

@ -190,11 +190,7 @@ impl PersistentPlugin {
// Start the plugin garbage collector // Start the plugin garbage collector
let gc = PluginGc::new(mutable.gc_config.clone(), &self).map_err(|err| { let gc = PluginGc::new(mutable.gc_config.clone(), &self).map_err(|err| {
IoError::new_internal( IoError::new_internal(err, "Could not start plugin gc", nu_protocol::location!())
err.kind(),
"Could not start plugin gc",
nu_protocol::location!(),
)
})?; })?;
let pid = child.id(); let pid = child.id();

View File

@ -66,7 +66,7 @@ pub(crate) fn spawn_fake_plugin(
.spawn(move || manager.consume_all(output_read).expect("Plugin read error")) .spawn(move || manager.consume_all(output_read).expect("Plugin read error"))
.map_err(|err| { .map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
format!("Could not spawn fake plugin interface reader ({name})"), format!("Could not spawn fake plugin interface reader ({name})"),
nu_protocol::location!(), nu_protocol::location!(),
) )
@ -87,7 +87,7 @@ pub(crate) fn spawn_fake_plugin(
}) })
.map_err(|err| { .map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
format!("Could not spawn fake plugin runner ({name})"), format!("Could not spawn fake plugin runner ({name})"),
nu_protocol::location!(), nu_protocol::location!(),
) )

View File

@ -1048,7 +1048,7 @@ impl ForegroundGuard {
// This should always succeed, frankly, but handle the error just in case // This should always succeed, frankly, but handle the error just in case
setpgid(Pid::from_raw(0), Pid::from_raw(0)).map_err(|err| { setpgid(Pid::from_raw(0), Pid::from_raw(0)).map_err(|err| {
nu_protocol::shell_error::io::IoError::new_internal( nu_protocol::shell_error::io::IoError::new_internal(
std::io::Error::from(err).kind(), std::io::Error::from(err),
"Could not set pgid", "Could not set pgid",
nu_protocol::location!(), nu_protocol::location!(),
) )

View File

@ -12,7 +12,7 @@ use nu_plugin_protocol::{
use nu_protocol::{ use nu_protocol::{
BlockId, ByteStreamType, Config, CustomValue, IntoInterruptiblePipelineData, LabeledError, BlockId, ByteStreamType, Config, CustomValue, IntoInterruptiblePipelineData, LabeledError,
PipelineData, PluginSignature, ShellError, Signals, Span, Spanned, Value, VarId, PipelineData, PluginSignature, ShellError, Signals, Span, Spanned, Value, VarId,
engine::Closure, engine::Closure, shell_error,
}; };
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -91,7 +91,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul
fn test_io_error() -> ShellError { fn test_io_error() -> ShellError {
ShellError::Io(IoError::new_with_additional_context( ShellError::Io(IoError::new_with_additional_context(
std::io::ErrorKind::Other, shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other),
Span::test_data(), Span::test_data(),
None, None,
"test io error", "test io error",

View File

@ -51,6 +51,7 @@ nix = { workspace = true, default-features = false, features = ["signal"] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
dirs-sys = { workspace = true } dirs-sys = { workspace = true }
windows-sys = { workspace = true } windows-sys = { workspace = true }
windows = { workspace = true }
[features] [features]
default = ["os"] default = ["os"]

View File

@ -353,7 +353,7 @@ impl EngineState {
let cwd = self.cwd(Some(stack))?; let cwd = self.cwd(Some(stack))?;
std::env::set_current_dir(cwd).map_err(|err| { 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() { if let Some(config) = stack.config.take() {
@ -546,7 +546,7 @@ impl EngineState {
Ok(PluginRegistryFile::default()) Ok(PluginRegistryFile::default())
} else { } else {
Err(ShellError::Io(IoError::new_internal_with_path( Err(ShellError::Io(IoError::new_internal_with_path(
err.kind(), err,
"Failed to open plugin file", "Failed to open plugin file",
crate::location!(), crate::location!(),
PathBuf::from(plugin_path), PathBuf::from(plugin_path),
@ -563,7 +563,7 @@ impl EngineState {
// Write it to the same path // Write it to the same path
let plugin_file = File::create(plugin_path.as_path()).map_err(|err| { let plugin_file = File::create(plugin_path.as_path()).map_err(|err| {
IoError::new_internal_with_path( IoError::new_internal_with_path(
err.kind(), err,
"Failed to write plugin file", "Failed to write plugin file",
crate::location!(), crate::location!(),
PathBuf::from(plugin_path), PathBuf::from(plugin_path),

View File

@ -143,11 +143,11 @@ impl LabeledError {
/// [`ShellError`] implements `miette::Diagnostic`: /// [`ShellError`] implements `miette::Diagnostic`:
/// ///
/// ```rust /// ```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( /// let error = LabeledError::from_diagnostic(
/// &ShellError::Io(IoError::new_with_additional_context( /// &ShellError::Io(IoError::new_with_additional_context(
/// std::io::ErrorKind::Other, /// shell_error::io::ErrorKind::from_std(std::io::ErrorKind::Other),
/// Span::test_data(), /// Span::test_data(),
/// None, /// None,
/// "some error" /// "some error"

View File

@ -38,7 +38,7 @@ use super::{ShellError, location::Location};
/// ///
/// # let span = Span::test_data(); /// # let span = Span::test_data();
/// let path = PathBuf::from("/some/missing/file"); /// 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); /// println!("Error: {:?}", error);
/// ``` /// ```
/// ///
@ -47,7 +47,7 @@ use super::{ShellError, location::Location};
/// # use nu_protocol::shell_error::io::{IoError, ErrorKind}; /// # use nu_protocol::shell_error::io::{IoError, ErrorKind};
// # // #
/// let error = IoError::new_internal( /// let error = IoError::new_internal(
/// std::io::ErrorKind::UnexpectedEof, /// ErrorKind::from_std(std::io::ErrorKind::UnexpectedEof),
/// "Failed to read data from buffer", /// "Failed to read data from buffer",
/// nu_protocol::location!() /// nu_protocol::location!()
/// ); /// );
@ -93,7 +93,7 @@ pub struct IoError {
/// and is part of [`std::io::Error`]. /// and is part of [`std::io::Error`].
/// If a kind cannot be represented by it, consider adding a new variant to [`ErrorKind`]. /// 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. /// `additional_context` to get useful errors in these cases.
pub kind: ErrorKind, pub kind: ErrorKind,
@ -125,15 +125,63 @@ pub struct IoError {
pub location: Option<String>, pub location: Option<String>,
} }
/// 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Diagnostic)]
pub enum ErrorKind { 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, 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 // use these variants in cases where we know precisely whether a file or directory was expected
FileNotFound, FileNotFound,
DirectoryNotFound, 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)] #[derive(Debug, Clone, PartialEq, Eq, Error, Diagnostic)]
#[error("{0}")] #[error("{0}")]
pub struct AdditionalContext(String); pub struct AdditionalContext(String);
@ -237,10 +285,10 @@ impl IoError {
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
/// use nu_protocol::shell_error::io::IoError; /// use nu_protocol::shell_error::{self, io::IoError};
/// ///
/// let error = IoError::new_internal( /// let error = IoError::new_internal(
/// std::io::ErrorKind::UnexpectedEof, /// shell_error::io::ErrorKind::from_std(std::io::ErrorKind::UnexpectedEof),
/// "Failed to read from buffer", /// "Failed to read from buffer",
/// nu_protocol::location!(), /// nu_protocol::location!(),
/// ); /// );
@ -268,11 +316,11 @@ impl IoError {
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
/// use nu_protocol::shell_error::io::IoError; /// use nu_protocol::shell_error::{self, io::IoError};
/// use std::path::PathBuf; /// use std::path::PathBuf;
/// ///
/// let error = IoError::new_internal_with_path( /// let error = IoError::new_internal_with_path(
/// std::io::ErrorKind::NotFound, /// shell_error::io::ErrorKind::FileNotFound,
/// "Could not find special file", /// "Could not find special file",
/// nu_protocol::location!(), /// nu_protocol::location!(),
/// PathBuf::from("/some/file"), /// 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 /// This method is particularly useful when you need to handle multiple I/O errors which all
/// take the same span and path. /// 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`. /// 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> pub fn factory<'p, P>(span: Span, path: P) -> impl Fn(std::io::Error) -> Self + use<'p, P>
where where
P: Into<Option<&'p Path>>, P: Into<Option<&'p Path>>,
{ {
let path = path.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<std::io::Error> 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 { impl Display for IoError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.kind { 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::FileNotFound => write!(f, "File not found"),
ErrorKind::DirectoryNotFound => write!(f, "Directory not found"), ErrorKind::DirectoryNotFound => write!(f, "Directory not found"),
_ => write!(f, "I/O error"), _ => write!(f, "I/O error"),
@ -323,13 +394,14 @@ impl Display for IoError {
impl Display for ErrorKind { impl Display for ErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
ErrorKind::Std(std::io::ErrorKind::NotFound) => write!(f, "Not found"), ErrorKind::Std(std::io::ErrorKind::NotFound, _) => write!(f, "Not found"),
ErrorKind::Std(error_kind) => { ErrorKind::Std(error_kind, _) => {
let msg = error_kind.to_string(); let msg = error_kind.to_string();
let (first, rest) = msg.split_at(1); let (first, rest) = msg.split_at(1);
write!(f, "{}{}", first.to_uppercase(), rest) write!(f, "{}{}", first.to_uppercase(), rest)
} }
ErrorKind::NotAFile => write!(f, "Not a file"), ErrorKind::NotAFile => write!(f, "Not a file"),
ErrorKind::AlreadyInUse => write!(f, "Already in use"),
ErrorKind::FileNotFound => write!(f, "File not found"), ErrorKind::FileNotFound => write!(f, "File not found"),
ErrorKind::DirectoryNotFound => write!(f, "Directory not found"), ErrorKind::DirectoryNotFound => write!(f, "Directory not found"),
} }
@ -342,7 +414,7 @@ impl Diagnostic for IoError {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> { fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
let mut code = String::from("nu::shell::io::"); let mut code = String::from("nu::shell::io::");
match self.kind { 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::NotFound => code.push_str("not_found"),
std::io::ErrorKind::PermissionDenied => code.push_str("permission_denied"), std::io::ErrorKind::PermissionDenied => code.push_str("permission_denied"),
std::io::ErrorKind::ConnectionRefused => code.push_str("connection_refused"), 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(" ", "_")), kind => code.push_str(&kind.to_string().to_lowercase().replace(" ", "_")),
}, },
ErrorKind::NotAFile => code.push_str("not_a_file"), 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::FileNotFound => code.push_str("file_not_found"),
ErrorKind::DirectoryNotFound => code.push_str("directory_not_found"), ErrorKind::DirectoryNotFound => code.push_str("directory_not_found"),
} }
@ -378,7 +451,10 @@ impl Diagnostic for IoError {
let path = format!("'{}'", path.display()); let path = format!("'{}'", path.display());
match self.kind { match self.kind {
ErrorKind::NotAFile => format!("{path} is not a file"), 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::FileNotFound
| ErrorKind::DirectoryNotFound => format!("{path} does not exist"), | ErrorKind::DirectoryNotFound => format!("{path} does not exist"),
_ => format!("The error occurred at {path}"), _ => format!("The error occurred at {path}"),
@ -430,16 +506,10 @@ impl From<IoError> for std::io::Error {
} }
} }
impl From<std::io::ErrorKind> for ErrorKind {
fn from(value: std::io::ErrorKind) -> Self {
ErrorKind::Std(value)
}
}
impl From<ErrorKind> for std::io::ErrorKind { impl From<ErrorKind> for std::io::ErrorKind {
fn from(value: ErrorKind) -> Self { fn from(value: ErrorKind) -> Self {
match value { match value {
ErrorKind::Std(error_kind) => error_kind, ErrorKind::Std(error_kind, _) => error_kind,
_ => std::io::ErrorKind::Other, _ => std::io::ErrorKind::Other,
} }
} }
@ -455,8 +525,8 @@ pub enum NotFound {
Directory, Directory,
} }
/// Extension trait for working with [`std::io::ErrorKind`]. /// Extension trait for working with [`std::io::Error`].
pub trait ErrorKindExt { pub trait IoErrorExt {
/// Map [`NotFound`](std::io::ErrorKind) variants into more precise variants. /// 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 /// 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). /// If the file isn't found, return [`FileNotFound`](ErrorKind::FileNotFound).
/// ```rust /// ```rust
/// # use nu_protocol::{ /// # use nu_protocol::{
/// # shell_error::io::{ErrorKind, ErrorKindExt, IoError, NotFound}, /// # shell_error::io::{ErrorKind, IoErrorExt, IoError, NotFound},
/// # ShellError, Span, /// # ShellError, Span,
/// # }; /// # };
/// # use std::{fs, path::PathBuf}; /// # use std::{fs, path::PathBuf};
@ -479,7 +549,7 @@ pub trait ErrorKindExt {
/// let a_file = PathBuf::from("scripts/ellie.nu"); /// let a_file = PathBuf::from("scripts/ellie.nu");
/// let ellie = fs::read_to_string(&a_file).map_err(|err| { /// let ellie = fs::read_to_string(&a_file).map_err(|err| {
/// ShellError::Io(IoError::new( /// ShellError::Io(IoError::new(
/// err.kind().not_found_as(NotFound::File), /// err.not_found_as(NotFound::File),
/// span, /// span,
/// a_file, /// a_file,
/// )) /// ))
@ -498,21 +568,46 @@ pub trait ErrorKindExt {
fn not_found_as(self, kind: NotFound) -> ErrorKind; 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 { fn not_found_as(self, kind: NotFound) -> ErrorKind {
match (kind, self) { match (kind, self) {
(NotFound::File, Self::NotFound) => ErrorKind::FileNotFound, (NotFound::File, Self::Std(std::io::ErrorKind::NotFound, _)) => ErrorKind::FileNotFound,
(NotFound::Directory, Self::NotFound) => ErrorKind::DirectoryNotFound, (NotFound::Directory, Self::Std(std::io::ErrorKind::NotFound, _)) => {
_ => ErrorKind::Std(self), ErrorKind::DirectoryNotFound
} }
}
}
impl ErrorKindExt for ErrorKind {
fn not_found_as(self, kind: NotFound) -> ErrorKind {
match self {
Self::Std(std_kind) => std_kind.not_found_as(kind),
_ => self, _ => 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<std::io::ErrorKind>`.
///
/// This implementation exists only in tests to make sure that no crate,
/// including ours, accidentally adds a `From<std::io::ErrorKind>` 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<std::io::ErrorKind> for ErrorKind {
fn from(_: std::io::ErrorKind) -> Self {
unimplemented!("ErrorKind should not implement From<std::io::ErrorKind>")
}
}
}

View File

@ -246,7 +246,7 @@ impl ByteStream {
if let Some(mut reader) = self.reader() { if let Some(mut reader) = self.reader() {
// Copy the number of skipped bytes into the sink before proceeding // Copy the number of skipped bytes into the sink before proceeding
io::copy(&mut (&mut reader).take(n), &mut io::sink()) 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( Ok(
ByteStream::read(reader, span, Signals::empty(), ByteStreamType::Binary) ByteStream::read(reader, span, Signals::empty(), ByteStreamType::Binary)
.with_known_size(known_size), .with_known_size(known_size),
@ -367,7 +367,7 @@ impl ByteStream {
/// binary. /// binary.
#[cfg(feature = "os")] #[cfg(feature = "os")]
pub fn stdin(span: Span) -> Result<Self, ShellError> { pub fn stdin(span: Span) -> Result<Self, ShellError> {
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)); let source = ByteStreamSource::File(convert_file(stdin));
Ok(Self::new( Ok(Self::new(
source, source,
@ -853,7 +853,7 @@ impl Iterator for Lines {
trim_end_newline(&mut string); trim_end_newline(&mut string);
Some(Ok(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| { self.internal.next().map(|r| {
r.map_err(|err| { r.map_err(|err| {
ShellError::Io(IoError::new_internal( ShellError::Io(IoError::new_internal(
err.kind(), err,
"Could not get next value for SplitRead", "Could not get next value for SplitRead",
crate::location!(), crate::location!(),
)) ))
@ -1094,7 +1094,7 @@ impl Chunks {
fn next_string(&mut self) -> Result<Option<String>, (Vec<u8>, ShellError)> { fn next_string(&mut self) -> Result<Option<String>, (Vec<u8>, ShellError)> {
let from_io_error = |err: std::io::Error| match ShellErrorBridge::try_from(err) { let from_io_error = |err: std::io::Error| match ShellErrorBridge::try_from(err) {
Ok(err) => err.0, 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 // Get some data from the reader
@ -1177,11 +1177,7 @@ impl Iterator for Chunks {
Ok(buf) => buf, Ok(buf) => buf,
Err(err) => { Err(err) => {
self.error = true; self.error = true;
return Some(Err(ShellError::Io(IoError::new( return Some(Err(ShellError::Io(IoError::new(err, self.span, None))));
err.kind(),
self.span,
None,
))));
} }
}; };
if !buf.is_empty() { if !buf.is_empty() {

View File

@ -222,14 +222,14 @@ impl PipelineData {
let bytes = value_to_bytes(value)?; let bytes = value_to_bytes(value)?;
dest.write_all(&bytes).map_err(|err| { dest.write_all(&bytes).map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not write PipelineData to dest", "Could not write PipelineData to dest",
crate::location!(), crate::location!(),
) )
})?; })?;
dest.flush().map_err(|err| { dest.flush().map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not flush PipelineData to dest", "Could not flush PipelineData to dest",
crate::location!(), crate::location!(),
) )
@ -241,14 +241,14 @@ impl PipelineData {
let bytes = value_to_bytes(value)?; let bytes = value_to_bytes(value)?;
dest.write_all(&bytes).map_err(|err| { dest.write_all(&bytes).map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not write PipelineData to dest", "Could not write PipelineData to dest",
crate::location!(), crate::location!(),
) )
})?; })?;
dest.write_all(b"\n").map_err(|err| { dest.write_all(b"\n").map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not write linebreak after PipelineData to dest", "Could not write linebreak after PipelineData to dest",
crate::location!(), crate::location!(),
) )
@ -256,7 +256,7 @@ impl PipelineData {
} }
dest.flush().map_err(|err| { dest.flush().map_err(|err| {
IoError::new_internal( IoError::new_internal(
err.kind(), err,
"Could not flush PipelineData to dest", "Could not flush PipelineData to dest",
crate::location!(), crate::location!(),
) )
@ -775,11 +775,9 @@ where
let io_error_map = |err: std::io::Error, location: Location| { let io_error_map = |err: std::io::Error, location: Location| {
let context = format!("Writing to {} failed", destination_name); let context = format!("Writing to {} failed", destination_name);
match span { match span {
None => IoError::new_internal(err.kind(), context, location), None => IoError::new_internal(err, context, location),
Some(span) if span == Span::unknown() => { Some(span) if span == Span::unknown() => IoError::new_internal(err, context, location),
IoError::new_internal(err.kind(), context, location) Some(span) => IoError::new_with_additional_context(err, span, None, context),
}
Some(span) => IoError::new_with_additional_context(err.kind(), span, None, context),
} }
}; };

View File

@ -81,7 +81,7 @@ impl ExitStatusFuture {
} }
Ok(Ok(status)) => Ok(status), Ok(Ok(status)) => Ok(status),
Ok(Err(err)) => Err(ShellError::Io(IoError::new_with_additional_context( Ok(Err(err)) => Err(ShellError::Io(IoError::new_with_additional_context(
err.kind(), err,
span, span,
None, None,
"failed to get exit code", "failed to get exit code",
@ -276,7 +276,7 @@ impl ChildProcess {
}) })
.map_err(|err| { .map_err(|err| {
IoError::new_with_additional_context( IoError::new_with_additional_context(
err.kind(), err,
span, span,
None, None,
"Could now spawn exit status waiter", "Could now spawn exit status waiter",
@ -325,7 +325,7 @@ impl ChildProcess {
} }
let bytes = if let Some(stdout) = self.stdout { 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 { } else {
Vec::new() Vec::new()
}; };

View File

@ -10,7 +10,8 @@ use nu_utils::perf;
use nu_plugin::{EvaluatedCall, PluginCommand}; use nu_plugin::{EvaluatedCall, PluginCommand};
use nu_protocol::{ use nu_protocol::{
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned, 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}; 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( 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, spanned_file.span,
PathBuf::from(spanned_file.item), PathBuf::from(spanned_file.item),
"File without extension", "File without extension",

View File

@ -16,7 +16,8 @@ use log::debug;
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{ use nu_protocol::{
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned, 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; use polars::error::PolarsError;
@ -212,7 +213,7 @@ fn command(
)), )),
}, },
None => Err(ShellError::Io(IoError::new_with_additional_context( None => Err(ShellError::Io(IoError::new_with_additional_context(
std::io::ErrorKind::NotFound, shell_error::io::ErrorKind::FileNotFound,
resource.span, resource.span,
Some(PathBuf::from(resource.path)), Some(PathBuf::from(resource.path)),
"File without extension", "File without extension",

View File

@ -5,7 +5,7 @@ use nu_protocol::{
DeclId, ShellError, Span, Value, VarId, DeclId, ShellError, Span, Value, VarId,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
report_shell_error, report_shell_error,
shell_error::io::{ErrorKindExt, IoError, NotFound}, shell_error::io::{IoError, IoErrorExt, NotFound},
}; };
use reedline::Completer; use reedline::Completer;
use serde_json::{Value as JsonValue, json}; use serde_json::{Value as JsonValue, json};
@ -58,7 +58,7 @@ fn read_in_file<'a>(
let file = std::fs::read(file_path) let file = std::fs::read(file_path)
.map_err(|err| { .map_err(|err| {
ShellError::Io(IoError::new_with_additional_context( ShellError::Io(IoError::new_with_additional_context(
err.kind().not_found_as(NotFound::File), err.not_found_as(NotFound::File),
Span::unknown(), Span::unknown(),
PathBuf::from(file_path), PathBuf::from(file_path),
"Could not read file", "Could not read file",

View File

@ -417,7 +417,7 @@ fn main() -> Result<()> {
let filename = canonicalize_with(&plugin_filename.item, &init_cwd) let filename = canonicalize_with(&plugin_filename.item, &init_cwd)
.map_err(|err| { .map_err(|err| {
nu_protocol::shell_error::io::IoError::new( nu_protocol::shell_error::io::IoError::new(
err.kind(), err,
plugin_filename.span, plugin_filename.span,
PathBuf::from(&plugin_filename.item), PathBuf::from(&plugin_filename.item),
) )