Add ErrSpan extension trait for Result (#12626)

# Description
This adds an extension trait to `Result` that wraps errors in `Spanned`,
saving the effort of calling `.map_err(|err| err.into_spanned(span))`
every time. This will hopefully make it even more likely that someone
will want to use a spanned `io::Error` and make it easier to remove the
impl for `From<io::Error> for ShellError` because that doesn't have span
information.
This commit is contained in:
Devyn Cairns 2024-04-23 01:39:55 -07:00 committed by GitHub
parent b0acc1d890
commit 5c7f7883c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 47 additions and 21 deletions

View File

@ -117,7 +117,7 @@ impl Command for Do {
None, None,
) )
}) })
.map_err(|e| e.into_spanned(call.head)) .err_span(call.head)
}) })
.transpose()?; .transpose()?;

View File

@ -106,9 +106,7 @@ apparent the next time `nu` is next launched with that plugin cache file.
let shell_expanded = shell let shell_expanded = shell
.as_ref() .as_ref()
.map(|s| { .map(|s| nu_path::canonicalize_with(&s.item, &cwd).err_span(s.span))
nu_path::canonicalize_with(&s.item, &cwd).map_err(|err| err.into_spanned(s.span))
})
.transpose()?; .transpose()?;
// Parse the plugin filename so it can be used to spawn the plugin // Parse the plugin filename so it can be used to spawn the plugin

View File

@ -30,7 +30,7 @@ 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_cache_file_path).is_ok_and(|m| m.len() > 0) { let mut contents = if fs::metadata(&plugin_cache_file_path).is_ok_and(|m| m.len() > 0) {
PluginCacheFile::read_from( PluginCacheFile::read_from(
File::open(&plugin_cache_file_path).map_err(|err| err.into_spanned(span))?, File::open(&plugin_cache_file_path).err_span(span)?,
Some(span), Some(span),
)? )?
} else { } else {
@ -42,7 +42,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_cache_file_path).map_err(|err| err.into_spanned(span))?, File::create(&plugin_cache_file_path).err_span(span)?,
Some(span), Some(span),
)?; )?;

View File

@ -133,7 +133,7 @@ impl Command for Save {
.spawn(move || stderr.drain()), .spawn(move || stderr.drain()),
}) })
.transpose() .transpose()
.map_err(|e| e.into_spanned(span))?; .err_span(span)?;
let res = stream_to_file(stdout, file, span, progress); let res = stream_to_file(stdout, file, span, progress);
if let Some(h) = handler { if let Some(h) = handler {

View File

@ -125,8 +125,7 @@ use it in your pipeline."#
if use_stderr { if use_stderr {
let stderr = stderr let stderr = stderr
.map(|stderr| { .map(|stderr| {
let iter = tee(stderr.stream, with_stream) let iter = tee(stderr.stream, with_stream).err_span(call.head)?;
.map_err(|e| e.into_spanned(call.head))?;
Ok::<_, ShellError>(RawStream::new( Ok::<_, ShellError>(RawStream::new(
Box::new(iter.map(flatten_result)), Box::new(iter.map(flatten_result)),
stderr.ctrlc, stderr.ctrlc,
@ -146,8 +145,7 @@ use it in your pipeline."#
} else { } else {
let stdout = stdout let stdout = stdout
.map(|stdout| { .map(|stdout| {
let iter = tee(stdout.stream, with_stream) let iter = tee(stdout.stream, with_stream).err_span(call.head)?;
.map_err(|e| e.into_spanned(call.head))?;
Ok::<_, ShellError>(RawStream::new( Ok::<_, ShellError>(RawStream::new(
Box::new(iter.map(flatten_result)), Box::new(iter.map(flatten_result)),
stdout.ctrlc, stdout.ctrlc,
@ -189,7 +187,7 @@ use it in your pipeline."#
// Make sure to drain any iterator produced to avoid unexpected behavior // Make sure to drain any iterator produced to avoid unexpected behavior
result.and_then(|data| data.drain()) result.and_then(|data| data.drain())
}) })
.map_err(|e| e.into_spanned(call.head))? .err_span(call.head)?
.map(move |result| result.unwrap_or_else(|err| Value::error(err, closure_span))) .map(move |result| result.unwrap_or_else(|err| Value::error(err, closure_span)))
.into_pipeline_data_with_metadata(metadata, engine_state.ctrlc.clone()); .into_pipeline_data_with_metadata(metadata, engine_state.ctrlc.clone());

View File

@ -62,7 +62,7 @@ impl Command for Complete {
} }
}) })
.map(|handle| (handle, stderr_span)) .map(|handle| (handle, stderr_span))
.map_err(|err| err.into_spanned(call.head)) .err_span(call.head)
}) })
.transpose()?; .transpose()?;

View File

@ -495,7 +495,7 @@ impl ExternalCommand {
Ok(()) Ok(())
}) })
.map_err(|e| e.into_spanned(head))?; .err_span(head)?;
} }
} }
@ -580,7 +580,7 @@ impl ExternalCommand {
Ok(()) Ok(())
} }
}) })
.map_err(|e| e.into_spanned(head))?; .err_span(head)?;
let exit_code_receiver = ValueReceiver::new(exit_code_rx); let exit_code_receiver = ValueReceiver::new(exit_code_rx);

View File

@ -2,6 +2,7 @@ pub use crate::CallExt;
pub use nu_protocol::{ pub use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned, record, Category, ErrSpan, Example, IntoInterruptiblePipelineData, IntoPipelineData,
PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
Value,
}; };

View File

@ -3550,8 +3550,7 @@ pub fn parse_where(working_set: &mut StateWorkingSet, lite_command: &LiteCommand
pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline { pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
use nu_plugin::{get_signature, PersistentPlugin, PluginDeclaration}; use nu_plugin::{get_signature, PersistentPlugin, PluginDeclaration};
use nu_protocol::{ use nu_protocol::{
engine::Stack, IntoSpanned, PluginCacheItem, PluginIdentity, PluginSignature, engine::Stack, ErrSpan, PluginCacheItem, PluginIdentity, PluginSignature, RegisteredPlugin,
RegisteredPlugin,
}; };
let spans = &lite_command.parts; let spans = &lite_command.parts;
@ -3694,8 +3693,7 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
let path = path.path_buf(); let path = path.path_buf();
// Create the plugin identity. This validates that the plugin name starts with `nu_plugin_` // Create the plugin identity. This validates that the plugin name starts with `nu_plugin_`
let identity = let identity = PluginIdentity::new(path, shell).err_span(path_span)?;
PluginIdentity::new(path, shell).map_err(|err| err.into_spanned(path_span))?;
// Find garbage collection config // Find garbage collection config
let gc_config = working_set let gc_config = working_set

View File

@ -147,3 +147,34 @@ pub fn span(spans: &[Span]) -> Span {
Span::new(spans[0].start, end) Span::new(spans[0].start, end)
} }
} }
/// An extension trait for `Result`, which adds a span to the error type.
pub trait ErrSpan {
type Result;
/// Add the given span to the error type `E`, turning it into a `Spanned<E>`.
///
/// Some auto-conversion methods to `ShellError` from other error types are available on spanned
/// errors, to give users better information about where an error came from. For example, it is
/// preferred when working with `std::io::Error`:
///
/// ```no_run
/// use nu_protocol::{ErrSpan, ShellError, Span};
/// use std::io::Read;
///
/// fn read_from(mut reader: impl Read, span: Span) -> Result<Vec<u8>, ShellError> {
/// let mut vec = vec![];
/// reader.read_to_end(&mut vec).err_span(span)?;
/// Ok(vec)
/// }
/// ```
fn err_span(self, span: Span) -> Self::Result;
}
impl<T, E> ErrSpan for Result<T, E> {
type Result = Result<T, Spanned<E>>;
fn err_span(self, span: Span) -> Self::Result {
self.map_err(|err| err.into_spanned(span))
}
}