Better generic errors for plugins (and perhaps scripts) (#12236)

# Description
This makes `LabeledError` much more capable of representing close to
everything a `miette::Diagnostic` can, including `ShellError`, and
allows plugins to generate multiple error spans, codes, help, etc.

`LabeledError` is now embeddable within `ShellError` as a transparent
variant.

This could also be used to improve `error make` and `try/catch` to
reflect `LabeledError` exactly in the future.

Also cleaned up some errors in existing plugins.

# User-Facing Changes
Breaking change for plugins. Nicer errors for users.
This commit is contained in:
Devyn Cairns 2024-03-21 04:27:21 -07:00 committed by GitHub
parent 8237d15683
commit efe25e3f58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 453 additions and 307 deletions

View File

@ -2,7 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, LabeledError, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -258,13 +259,10 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
}
// correct return: everything present
ShellError::GenericError {
error: msg,
msg: text,
span: Some(Span::new(span_start as usize, span_end as usize)),
help,
inner: vec![],
}
let mut error =
LabeledError::new(msg).with_label(text, Span::new(span_start as usize, span_end as usize));
error.help = help;
error.into()
}
fn get_span_sides(span: &Record, span_span: Span, side: &str) -> Result<i64, ShellError> {

View File

@ -16,9 +16,9 @@
//! invoked by Nushell.
//!
//! ```rust,no_run
//! use nu_plugin::{EvaluatedCall, LabeledError, MsgPackSerializer, serve_plugin};
//! use nu_plugin::{EvaluatedCall, MsgPackSerializer, serve_plugin};
//! use nu_plugin::{Plugin, PluginCommand, SimplePluginCommand, EngineInterface};
//! use nu_protocol::{PluginSignature, Value};
//! use nu_protocol::{PluginSignature, LabeledError, Value};
//!
//! struct MyPlugin;
//! struct MyCommand;
@ -64,7 +64,7 @@ mod util;
pub use plugin::{
serve_plugin, EngineInterface, Plugin, PluginCommand, PluginEncoder, SimplePluginCommand,
};
pub use protocol::{EvaluatedCall, LabeledError};
pub use protocol::EvaluatedCall;
pub use serializers::{json::JsonSerializer, msgpack::MsgPackSerializer};
// Used by other nu crates.

View File

@ -1,6 +1,6 @@
use nu_protocol::{PipelineData, PluginSignature, Value};
use nu_protocol::{LabeledError, PipelineData, PluginSignature, Value};
use crate::{EngineInterface, EvaluatedCall, LabeledError, Plugin};
use crate::{EngineInterface, EvaluatedCall, Plugin};
/// The API for a Nushell plugin command
///
@ -18,7 +18,7 @@ use crate::{EngineInterface, EvaluatedCall, LabeledError, Plugin};
/// Basic usage:
/// ```
/// # use nu_plugin::*;
/// # use nu_protocol::{PluginSignature, PipelineData, Type, Value};
/// # use nu_protocol::{PluginSignature, PipelineData, Type, Value, LabeledError};
/// struct LowercasePlugin;
/// struct Lowercase;
///
@ -108,7 +108,7 @@ pub trait PluginCommand: Sync {
/// Basic usage:
/// ```
/// # use nu_plugin::*;
/// # use nu_protocol::{PluginSignature, Type, Value};
/// # use nu_protocol::{PluginSignature, Type, Value, LabeledError};
/// struct HelloPlugin;
/// struct Hello;
///

View File

@ -6,7 +6,7 @@ use std::{
};
use nu_protocol::{
engine::Closure, Config, IntoInterruptiblePipelineData, ListStream, PipelineData,
engine::Closure, Config, IntoInterruptiblePipelineData, LabeledError, ListStream, PipelineData,
PluginSignature, ShellError, Spanned, Value,
};
@ -16,7 +16,7 @@ use crate::{
PluginCall, PluginCallId, PluginCallResponse, PluginCustomValue, PluginInput, PluginOption,
ProtocolInfo,
},
LabeledError, PluginOutput,
PluginOutput,
};
use super::{

View File

@ -4,8 +4,8 @@ use std::{
};
use nu_protocol::{
engine::Closure, Config, CustomValue, IntoInterruptiblePipelineData, PipelineData,
PluginExample, PluginSignature, ShellError, Span, Spanned, Value,
engine::Closure, Config, CustomValue, IntoInterruptiblePipelineData, LabeledError,
PipelineData, PluginExample, PluginSignature, ShellError, Span, Spanned, Value,
};
use crate::{
@ -16,7 +16,7 @@ use crate::{
ListStreamInfo, PipelineDataHeader, PluginCall, PluginCustomValue, PluginInput, Protocol,
ProtocolInfo, RawStreamInfo, StreamData, StreamMessage,
},
EvaluatedCall, LabeledError, PluginCallResponse, PluginOutput,
EvaluatedCall, PluginCallResponse, PluginOutput,
};
use super::{EngineInterfaceManager, ReceivedPluginCall};
@ -738,11 +738,7 @@ fn interface_write_response_with_stream() -> Result<(), ShellError> {
fn interface_write_response_with_error() -> Result<(), ShellError> {
let test = TestCase::new();
let interface = test.engine().interface_for_context(35);
let labeled_error = LabeledError {
label: "this is an error".into(),
msg: "a test error".into(),
span: None,
};
let labeled_error = LabeledError::new("this is an error").with_help("a test error");
interface
.write_response(Err(labeled_error.clone()))?
.write()?;

View File

@ -1,4 +1,5 @@
use nu_engine::documentation::get_flags_section;
use nu_protocol::LabeledError;
use std::cmp::Ordering;
use std::collections::HashMap;
@ -13,9 +14,7 @@ use std::sync::mpsc::TrySendError;
use std::sync::{mpsc, Arc, Mutex};
use crate::plugin::interface::{EngineInterfaceManager, ReceivedPluginCall};
use crate::protocol::{
CallInfo, CustomValueOp, LabeledError, PluginCustomValue, PluginInput, PluginOutput,
};
use crate::protocol::{CallInfo, CustomValueOp, PluginCustomValue, PluginInput, PluginOutput};
use crate::EncodingType;
#[cfg(unix)]
@ -230,7 +229,7 @@ where
/// Basic usage:
/// ```
/// # use nu_plugin::*;
/// # use nu_protocol::{PluginSignature, Type, Value};
/// # use nu_protocol::{PluginSignature, LabeledError, Type, Value};
/// struct HelloPlugin;
/// struct Hello;
///
@ -537,11 +536,12 @@ pub fn serve_plugin(plugin: &impl Plugin, encoder: impl PluginEncoder + 'static)
let result = if let Some(command) = commands.get(&name) {
command.run(plugin, &engine, &call, input)
} else {
Err(LabeledError {
label: format!("Plugin command not found: `{name}`"),
msg: format!("plugin `{plugin_name}` doesn't have this command"),
span: Some(call.head),
})
Err(
LabeledError::new(format!("Plugin command not found: `{name}`")).with_label(
format!("plugin `{plugin_name}` doesn't have this command"),
call.head,
),
)
};
let write_result = engine
.write_response(result)

View File

@ -12,8 +12,8 @@ use std::collections::HashMap;
pub use evaluated_call::EvaluatedCall;
use nu_protocol::{
ast::Operator, engine::Closure, Config, PipelineData, PluginSignature, RawStream, ShellError,
Span, Spanned, Value,
ast::Operator, engine::Closure, Config, LabeledError, PipelineData, PluginSignature, RawStream,
ShellError, Span, Spanned, Value,
};
pub use plugin_custom_value::PluginCustomValue;
#[cfg(test)]
@ -289,73 +289,6 @@ pub enum StreamMessage {
Ack(StreamId),
}
/// An error message with debugging information that can be passed to Nushell from the plugin
///
/// The `LabeledError` struct is a structured error message that can be returned from
/// a [Plugin](crate::Plugin)'s [`run`](crate::Plugin::run()) method. It contains
/// the error message along with optional [Span] data to support highlighting in the
/// shell.
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
pub struct LabeledError {
/// The name of the error
pub label: String,
/// A detailed error description
pub msg: String,
/// The [Span] in which the error occurred
pub span: Option<Span>,
}
impl From<LabeledError> for ShellError {
fn from(error: LabeledError) -> Self {
if error.span.is_some() {
ShellError::GenericError {
error: error.label,
msg: error.msg,
span: error.span,
help: None,
inner: vec![],
}
} else {
ShellError::GenericError {
error: error.label,
msg: "".into(),
span: None,
help: (!error.msg.is_empty()).then_some(error.msg),
inner: vec![],
}
}
}
}
impl From<ShellError> for LabeledError {
fn from(error: ShellError) -> Self {
use miette::Diagnostic;
// This is not perfect - we can only take the first labeled span as that's all we have
// space for.
if let Some(labeled_span) = error.labels().and_then(|mut iter| iter.nth(0)) {
let offset = labeled_span.offset();
let span = Span::new(offset, offset + labeled_span.len());
LabeledError {
label: error.to_string(),
msg: labeled_span
.label()
.map(|label| label.to_owned())
.unwrap_or_else(|| "".into()),
span: Some(span),
}
} else {
LabeledError {
label: error.to_string(),
msg: error
.help()
.map(|help| help.to_string())
.unwrap_or_else(|| "".into()),
span: None,
}
}
}
}
/// Response to a [`PluginCall`]. The type parameter determines the output type for pipeline data.
///
/// Note: exported for internal use, not public.

View File

@ -1,11 +1,11 @@
macro_rules! generate_tests {
($encoder:expr) => {
use crate::protocol::{
CallInfo, CustomValueOp, EvaluatedCall, LabeledError, PipelineDataHeader, PluginCall,
CallInfo, CustomValueOp, EvaluatedCall, PipelineDataHeader, PluginCall,
PluginCallResponse, PluginCustomValue, PluginInput, PluginOption, PluginOutput,
StreamData, StreamMessage,
};
use nu_protocol::{PluginSignature, Span, Spanned, SyntaxShape, Value};
use nu_protocol::{LabeledError, PluginSignature, Span, Spanned, SyntaxShape, Value};
#[test]
fn decode_eof() {
@ -364,11 +364,15 @@ macro_rules! generate_tests {
#[test]
fn response_round_trip_error() {
let error = LabeledError {
label: "label".into(),
msg: "msg".into(),
span: Some(Span::new(2, 30)),
};
let error = LabeledError::new("label")
.with_code("test::error")
.with_url("https://example.org/test/error")
.with_help("some help")
.with_label("msg", Span::new(2, 30))
.with_inner(ShellError::IOError {
msg: "io error".into(),
});
let response = PluginCallResponse::Error(error.clone());
let output = PluginOutput::CallResponse(6, response);
@ -392,11 +396,7 @@ macro_rules! generate_tests {
#[test]
fn response_round_trip_error_none() {
let error = LabeledError {
label: "label".into(),
msg: "msg".into(),
span: None,
};
let error = LabeledError::new("error");
let response = PluginCallResponse::Error(error.clone());
let output = PluginOutput::CallResponse(7, response);

View File

@ -0,0 +1,241 @@
use std::fmt;
use miette::Diagnostic;
use serde::{Deserialize, Serialize};
use crate::Span;
use super::ShellError;
/// A very generic type of error used for interfacing with external code, such as scripts and
/// plugins.
///
/// This generally covers most of the interface of [`miette::Diagnostic`], but with types that are
/// well-defined for our protocol.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LabeledError {
/// The main message for the error.
pub msg: String,
/// Labeled spans attached to the error, demonstrating to the user where the problem is.
#[serde(default)]
pub labels: Vec<ErrorLabel>,
/// A unique machine- and search-friendly error code to associate to the error. (e.g.
/// `nu::shell::missing_config_value`)
#[serde(default)]
pub code: Option<String>,
/// A link to documentation about the error, used in conjunction with `code`
#[serde(default)]
pub url: Option<String>,
/// Additional help for the error, usually a hint about what the user might try
#[serde(default)]
pub help: Option<String>,
/// Errors that are related to or caused this error
#[serde(default)]
pub inner: Vec<LabeledError>,
}
impl LabeledError {
/// Create a new plain [`LabeledError`] with the given message.
///
/// This is usually used builder-style with methods like [`.with_label()`] to build an error.
///
/// # Example
///
/// ```rust
/// # use nu_protocol::LabeledError;
/// let error = LabeledError::new("Something bad happened");
/// assert_eq!("Something bad happened", error.to_string());
/// ```
pub fn new(msg: impl Into<String>) -> LabeledError {
LabeledError {
msg: msg.into(),
labels: vec![],
code: None,
url: None,
help: None,
inner: vec![],
}
}
/// Add a labeled span to the error to demonstrate to the user where the problem is.
///
/// # Example
///
/// ```rust
/// # use nu_protocol::{LabeledError, Span};
/// # let span = Span::test_data();
/// let error = LabeledError::new("An error")
/// .with_label("happened here", span);
/// assert_eq!("happened here", &error.labels[0].text);
/// assert_eq!(span, error.labels[0].span);
/// ```
pub fn with_label(mut self, text: impl Into<String>, span: Span) -> Self {
self.labels.push(ErrorLabel {
text: text.into(),
span,
});
self
}
/// Add a unique machine- and search-friendly error code to associate to the error. (e.g.
/// `nu::shell::missing_config_value`)
///
/// # Example
///
/// ```rust
/// # use nu_protocol::LabeledError;
/// let error = LabeledError::new("An error")
/// .with_code("my_product::error");
/// assert_eq!(Some("my_product::error"), error.code.as_deref());
/// ```
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
/// Add a link to documentation about the error, used in conjunction with `code`.
///
/// # Example
///
/// ```rust
/// # use nu_protocol::LabeledError;
/// let error = LabeledError::new("An error")
/// .with_url("https://example.org/");
/// assert_eq!(Some("https://example.org/"), error.url.as_deref());
/// ```
pub fn with_url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
/// Add additional help for the error, usually a hint about what the user might try.
///
/// # Example
///
/// ```rust
/// # use nu_protocol::LabeledError;
/// let error = LabeledError::new("An error")
/// .with_help("did you try turning it off and back on again?");
/// assert_eq!(Some("did you try turning it off and back on again?"), error.help.as_deref());
/// ```
pub fn with_help(mut self, help: impl Into<String>) -> Self {
self.help = Some(help.into());
self
}
/// Add an error that is related to or caused this error.
///
/// # Example
///
/// ```rust
/// # use nu_protocol::LabeledError;
/// let error = LabeledError::new("An error")
/// .with_inner(LabeledError::new("out of coolant"));
/// assert_eq!(LabeledError::new("out of coolant"), error.inner[0]);
/// ```
pub fn with_inner(mut self, inner: impl Into<LabeledError>) -> Self {
self.inner.push(inner.into());
self
}
/// Create a [`LabeledError`] from a type that implements [`miette::Diagnostic`].
///
/// # Example
///
/// [`ShellError`] implements `miette::Diagnostic`:
///
/// ```rust
/// # use nu_protocol::{ShellError, LabeledError};
/// let error = LabeledError::from_diagnostic(&ShellError::IOError { msg: "error".into() });
/// assert!(error.to_string().contains("I/O error"));
/// ```
pub fn from_diagnostic(diag: &(impl miette::Diagnostic + ?Sized)) -> LabeledError {
LabeledError {
msg: diag.to_string(),
labels: diag
.labels()
.into_iter()
.flatten()
.map(|label| ErrorLabel {
text: label.label().unwrap_or("").into(),
span: Span::new(label.offset(), label.offset() + label.len()),
})
.collect(),
code: diag.code().map(|s| s.to_string()),
url: diag.url().map(|s| s.to_string()),
help: diag.help().map(|s| s.to_string()),
inner: diag
.related()
.into_iter()
.flatten()
.map(Self::from_diagnostic)
.collect(),
}
}
}
/// A labeled span within a [`LabeledError`].
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ErrorLabel {
/// Text to show together with the span
pub text: String,
/// Span pointing at where the text references in the source
pub span: Span,
}
impl fmt::Display for LabeledError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.msg)
}
}
impl std::error::Error for LabeledError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.inner.get(0).map(|r| r as _)
}
}
impl Diagnostic for LabeledError {
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
self.code.as_ref().map(Box::new).map(|b| b as _)
}
fn severity(&self) -> Option<miette::Severity> {
None
}
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
self.help.as_ref().map(Box::new).map(|b| b as _)
}
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
self.url.as_ref().map(Box::new).map(|b| b as _)
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
None
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
Some(Box::new(self.labels.iter().map(|label| {
miette::LabeledSpan::new_with_span(
Some(label.text.clone()).filter(|s| !s.is_empty()),
label.span,
)
})))
}
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
Some(Box::new(self.inner.iter().map(|r| r as _)))
}
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
None
}
}
impl From<ShellError> for LabeledError {
fn from(err: ShellError) -> Self {
LabeledError::from_diagnostic(&err)
}
}

View File

@ -1,9 +1,11 @@
pub mod cli_error;
mod labeled_error;
mod parse_error;
mod parse_warning;
mod shell_error;
pub use cli_error::{format_error, report_error, report_error_new};
pub use labeled_error::{ErrorLabel, LabeledError};
pub use parse_error::{DidYouMean, ParseError};
pub use parse_warning::ParseWarning;
pub use shell_error::*;

View File

@ -1097,6 +1097,11 @@ pub enum ShellError {
span: Span,
},
/// This is a generic error type used for user and plugin-generated errors.
#[error(transparent)]
#[diagnostic(transparent)]
LabeledError(#[from] Box<super::LabeledError>),
/// Attempted to use a command that has been removed from Nushell.
///
/// ## Resolution
@ -1396,6 +1401,12 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
}
}
impl From<super::LabeledError> for ShellError {
fn from(value: super::LabeledError) -> Self {
ShellError::LabeledError(Box::new(value))
}
}
pub fn into_code(err: &ShellError) -> Option<String> {
err.code().map(|code| code.to_string())
}

View File

@ -1,6 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
record, Category, CustomValue, PluginSignature, ShellError, Span, SyntaxShape, Value,
record, Category, CustomValue, LabeledError, PluginSignature, ShellError, Span, SyntaxShape,
Value,
};
use serde::{Deserialize, Serialize};

View File

@ -1,7 +1,7 @@
use crate::{cool_custom_value::CoolCustomValue, CustomValuePlugin};
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginExample, PluginSignature, Span, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginExample, PluginSignature, Span, Value};
pub struct Generate;

View File

@ -1,7 +1,9 @@
use crate::{second_custom_value::SecondCustomValue, CustomValuePlugin};
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginExample, PluginSignature, Span, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
Category, LabeledError, PluginExample, PluginSignature, Span, SyntaxShape, Value,
};
pub struct Generate2;

View File

@ -1,6 +1,4 @@
use nu_plugin::{
serve_plugin, EngineInterface, LabeledError, MsgPackSerializer, Plugin, PluginCommand,
};
use nu_plugin::{serve_plugin, EngineInterface, MsgPackSerializer, Plugin, PluginCommand};
mod cool_custom_value;
mod second_custom_value;
@ -14,7 +12,7 @@ mod update_arg;
use drop_check::{DropCheck, DropCheckValue};
use generate::Generate;
use generate2::Generate2;
use nu_protocol::CustomValue;
use nu_protocol::{CustomValue, LabeledError};
use update::Update;
use update_arg::UpdateArg;

View File

@ -2,8 +2,10 @@ use crate::{
cool_custom_value::CoolCustomValue, second_custom_value::SecondCustomValue, CustomValuePlugin,
};
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginExample, PluginSignature, ShellError, Span, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
Category, LabeledError, PluginExample, PluginSignature, ShellError, Span, Value,
};
pub struct Update;

View File

@ -1,7 +1,7 @@
use crate::{update::Update, CustomValuePlugin};
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, SyntaxShape, Value};
pub struct UpdateArg;

View File

@ -1,5 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, PluginCommand};
use nu_protocol::{Category, PipelineData, PluginExample, PluginSignature, RawStream, Type, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, LabeledError, PipelineData, PluginExample, PluginSignature, RawStream, Type, Value,
};
use crate::Example;

View File

@ -1,5 +1,5 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, Type, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Type, Value};
use crate::Example;
@ -27,12 +27,10 @@ impl SimplePluginCommand for Config {
let config = engine.get_plugin_config()?;
match config {
Some(config) => Ok(config.clone()),
None => Err(LabeledError {
label: "No config sent".into(),
msg: "Configuration for this plugin was not found in `$env.config.plugins.example`"
.into(),
span: Some(call.head),
}),
None => Err(LabeledError::new("No config sent").with_label(
"configuration for this plugin was not found in `$env.config.plugins.example`",
call.head,
)),
}
}
}

View File

@ -1,5 +1,5 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Value};
use crate::Example;

View File

@ -1,5 +1,5 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, SyntaxShape, Type, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, SyntaxShape, Type, Value};
use crate::Example;
@ -42,11 +42,8 @@ impl SimplePluginCommand for Env {
// Get working directory
Ok(Value::string(engine.get_current_dir()?, call.head))
}
Some(value) => Err(LabeledError {
label: "Invalid arguments".into(),
msg: "--cwd can't be used with --set".into(),
span: Some(value.span()),
}),
Some(value) => Err(LabeledError::new("Invalid arguments")
.with_label("--cwd can't be used with --set", value.span())),
}
} else if let Some(value) = call.get_flag_value("set") {
// Set single env var

View File

@ -1,5 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, PluginCommand};
use nu_protocol::{Category, PipelineData, PluginExample, PluginSignature, SyntaxShape, Type};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, LabeledError, PipelineData, PluginExample, PluginSignature, SyntaxShape, Type,
};
use crate::Example;

View File

@ -1,7 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, PluginCommand};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, IntoInterruptiblePipelineData, PipelineData, PluginExample, PluginSignature,
SyntaxShape, Type, Value,
Category, IntoInterruptiblePipelineData, LabeledError, PipelineData, PluginExample,
PluginSignature, SyntaxShape, Type, Value,
};
use crate::Example;

View File

@ -1,5 +1,5 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Value};
use crate::Example;
@ -32,10 +32,7 @@ particularly useful.
call: &EvaluatedCall,
_input: &Value,
) -> Result<Value, LabeledError> {
Err(LabeledError {
label: "No subcommand provided".into(),
msg: "add --help to see a list of subcommands".into(),
span: Some(call.head),
})
Err(LabeledError::new("No subcommand provided")
.with_label("add --help to see a list of subcommands", call.head))
}
}

View File

@ -1,5 +1,5 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginExample, PluginSignature, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginExample, PluginSignature, SyntaxShape, Value};
use crate::Example;

View File

@ -1,6 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, PluginCommand};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, ListStream, PipelineData, PluginExample, PluginSignature, SyntaxShape, Type, Value,
Category, LabeledError, ListStream, PipelineData, PluginExample, PluginSignature, SyntaxShape,
Type, Value,
};
use crate::Example;

View File

@ -1,5 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, PluginCommand};
use nu_protocol::{Category, PipelineData, PluginExample, PluginSignature, Span, Type, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, LabeledError, PipelineData, PluginExample, PluginSignature, Span, Type, Value,
};
use crate::Example;
@ -33,18 +35,15 @@ impl PluginCommand for Sum {
input: PipelineData,
) -> Result<PipelineData, LabeledError> {
let mut acc = IntOrFloat::Int(0);
let span = input.span();
for value in input {
if let Ok(n) = value.as_i64() {
acc.add_i64(n);
} else if let Ok(n) = value.as_f64() {
acc.add_f64(n);
} else {
return Err(LabeledError {
label: "Stream only accepts ints and floats".into(),
msg: format!("found {}", value.get_type()),
span,
});
return Err(LabeledError::new("Sum only accepts ints and floats")
.with_label(format!("found {} in input", value.get_type()), value.span())
.with_label("can't be used here", call.head));
}
}
Ok(PipelineData::Value(acc.to_value(call.head), None))

View File

@ -1,5 +1,5 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, SyntaxShape, Value};
use crate::Example;
@ -31,10 +31,7 @@ impl SimplePluginCommand for Three {
) -> Result<Value, LabeledError> {
plugin.print_values(3, call, input)?;
Err(LabeledError {
label: "ERROR from plugin".into(),
msg: "error message pointing to call head span".into(),
span: Some(call.head),
})
Err(LabeledError::new("ERROR from plugin")
.with_label("error message pointing to call head span", call.head))
}
}

View File

@ -1,5 +1,5 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{record, Category, PluginSignature, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{record, Category, LabeledError, PluginSignature, SyntaxShape, Value};
use crate::Example;

View File

@ -1,5 +1,5 @@
use nu_plugin::{EvaluatedCall, LabeledError};
use nu_protocol::Value;
use nu_plugin::EvaluatedCall;
use nu_protocol::{LabeledError, Value};
pub struct Example;

View File

@ -1,7 +1,8 @@
use eml_parser::eml::*;
use eml_parser::EmlParser;
use indexmap::map::IndexMap;
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::LabeledError;
use nu_protocol::{
record, Category, PluginExample, PluginSignature, ShellError, Span, SyntaxShape, Type, Value,
};

View File

@ -1,9 +1,9 @@
use ical::parser::ical::component::*;
use ical::property::Property;
use indexmap::map::IndexMap;
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
record, Category, PluginExample, PluginSignature, ShellError, Span, Type, Value,
record, Category, LabeledError, PluginExample, PluginSignature, ShellError, Span, Type, Value,
};
use std::io::BufReader;

View File

@ -1,6 +1,6 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
record, Category, PluginExample, PluginSignature, Record, ShellError, Type, Value,
record, Category, LabeledError, PluginExample, PluginSignature, Record, ShellError, Type, Value,
};
use crate::FromCmds;

View File

@ -1,9 +1,9 @@
use ical::parser::vcard::component::*;
use ical::property::Property;
use indexmap::map::IndexMap;
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
record, Category, PluginExample, PluginSignature, ShellError, Span, Type, Value,
record, Category, LabeledError, PluginExample, PluginSignature, ShellError, Span, Type, Value,
};
use crate::FromCmds;

View File

@ -1,6 +1,5 @@
use git2::{Branch, BranchType, DescribeOptions, Repository};
use nu_plugin::LabeledError;
use nu_protocol::{record, IntoSpanned, Span, Spanned, Value};
use nu_protocol::{record, IntoSpanned, LabeledError, Span, Spanned, Value};
use std::fmt::Write;
use std::ops::BitAnd;
use std::path::Path;
@ -51,39 +50,38 @@ impl GStat {
// This path has to exist
if !absolute_path.exists() {
return Err(LabeledError {
label: "error with path".to_string(),
msg: format!("path does not exist [{}]", absolute_path.display()),
span: Some(path.span),
});
return Err(LabeledError::new("error with path").with_label(
format!("path does not exist [{}]", absolute_path.display()),
path.span,
));
}
let metadata = std::fs::metadata(&absolute_path).map_err(|e| LabeledError {
label: "error with metadata".to_string(),
msg: format!(
"unable to get metadata for [{}], error: {}",
absolute_path.display(),
e
),
span: Some(path.span),
let metadata = std::fs::metadata(&absolute_path).map_err(|e| {
LabeledError::new("error with metadata").with_label(
format!(
"unable to get metadata for [{}], error: {}",
absolute_path.display(),
e
),
path.span,
)
})?;
// This path has to be a directory
if !metadata.is_dir() {
return Err(LabeledError {
label: "error with directory".to_string(),
msg: format!("path is not a directory [{}]", absolute_path.display()),
span: Some(path.span),
});
return Err(LabeledError::new("error with directory").with_label(
format!("path is not a directory [{}]", absolute_path.display()),
path.span,
));
}
let repo_path = match absolute_path.canonicalize() {
Ok(p) => p,
Err(e) => {
return Err(LabeledError {
label: format!("error canonicalizing [{}]", absolute_path.display()),
msg: e.to_string(),
span: Some(path.span),
});
return Err(LabeledError::new(format!(
"error canonicalizing [{}]",
absolute_path.display()
))
.with_label(e.to_string(), path.span));
}
};

View File

@ -1,8 +1,6 @@
use crate::GStat;
use nu_plugin::{
EngineInterface, EvaluatedCall, LabeledError, Plugin, PluginCommand, SimplePluginCommand,
};
use nu_protocol::{Category, PluginSignature, Spanned, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Spanned, SyntaxShape, Value};
pub struct GStatPlugin;

View File

@ -1,5 +1,4 @@
use nu_plugin::LabeledError;
use nu_protocol::{ast::CellPath, Span, Value};
use nu_protocol::{ast::CellPath, LabeledError, Span, Value};
use semver::{BuildMetadata, Prerelease, Version};
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
@ -102,12 +101,7 @@ impl Inc {
let cell_value = self.inc_value(head, &cell_value)?;
let mut value = value.clone();
value
.update_data_at_cell_path(&cell_path.members, cell_value)
.map_err(|x| {
let error: LabeledError = x.into();
error
})?;
value.update_data_at_cell_path(&cell_path.members, cell_value)?;
Ok(value)
} else {
self.inc_value(head, value)
@ -119,17 +113,14 @@ impl Inc {
Value::Int { val, .. } => Ok(Value::int(val + 1, head)),
Value::String { val, .. } => Ok(self.apply(val, head)),
x => {
let msg = x.coerce_string().map_err(|e| LabeledError {
label: "Unable to extract string".into(),
msg: format!("value cannot be converted to string {x:?} - {e}"),
span: Some(head),
let msg = x.coerce_string().map_err(|e| {
LabeledError::new("Unable to extract string").with_label(
format!("value cannot be converted to string {x:?} - {e}"),
head,
)
})?;
Err(LabeledError {
label: "Incorrect value".into(),
msg,
span: Some(head),
})
Err(LabeledError::new("Incorrect value").with_label(msg, head))
}
}
}

View File

@ -1,9 +1,7 @@
use crate::inc::SemVerAction;
use crate::Inc;
use nu_plugin::{
EngineInterface, EvaluatedCall, LabeledError, Plugin, PluginCommand, SimplePluginCommand,
};
use nu_protocol::{ast::CellPath, PluginSignature, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{ast::CellPath, LabeledError, PluginSignature, SyntaxShape, Value};
pub struct IncPlugin;

View File

@ -3,8 +3,8 @@ use crate::query_web::QueryWeb;
use crate::query_xml::QueryXml;
use nu_engine::documentation::get_flags_section;
use nu_plugin::{EvaluatedCall, LabeledError, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, Value};
use nu_plugin::{EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Value};
use std::fmt::Write;
#[derive(Default)]

View File

@ -1,6 +1,8 @@
use gjson::Value as gjValue;
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, Record, Span, Spanned, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
Category, LabeledError, PluginSignature, Record, Span, Spanned, SyntaxShape, Value,
};
use crate::Query;
@ -39,22 +41,15 @@ pub fn execute_json_query(
let input_string = match input.coerce_str() {
Ok(s) => s,
Err(e) => {
return Err(LabeledError {
span: Some(call.head),
msg: e.to_string(),
label: "problem with input data".to_string(),
})
return Err(LabeledError::new("Problem with input data").with_inner(e));
}
};
let query_string = match &query {
Some(v) => &v.item,
None => {
return Err(LabeledError {
msg: "problem with input data".to_string(),
label: "problem with input data".to_string(),
span: Some(call.head),
})
return Err(LabeledError::new("Problem with input data")
.with_label("query string missing", call.head));
}
};
@ -62,11 +57,9 @@ pub fn execute_json_query(
let is_valid_json = gjson::valid(&input_string);
if !is_valid_json {
return Err(LabeledError {
msg: "invalid json".to_string(),
label: "invalid json".to_string(),
span: Some(call.head),
});
return Err(
LabeledError::new("Invalid JSON").with_label("this is not valid JSON", call.head)
);
}
let val: gjValue = gjson::get(&input_string, query_string);

View File

@ -1,6 +1,9 @@
use crate::{web_tables::WebTable, Query};
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginExample, PluginSignature, Record, Span, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
Category, LabeledError, PluginExample, PluginSignature, Record, Span, Spanned, SyntaxShape,
Value,
};
use scraper::{Html, Selector as ScraperSelector};
pub struct QueryWeb;
@ -99,10 +102,7 @@ impl Default for Selector {
pub fn parse_selector_params(call: &EvaluatedCall, input: &Value) -> Result<Value, LabeledError> {
let head = call.head;
let query: String = match call.get_flag("query")? {
Some(q2) => q2,
None => "".to_string(),
};
let query: Option<Spanned<String>> = call.get_flag("query")?;
let as_html = call.has_flag("as-html")?;
let attribute = call.get_flag("attribute")?.unwrap_or_default();
let as_table: Value = call
@ -111,16 +111,20 @@ pub fn parse_selector_params(call: &EvaluatedCall, input: &Value) -> Result<Valu
let inspect = call.has_flag("inspect")?;
if !&query.is_empty() && ScraperSelector::parse(&query).is_err() {
return Err(LabeledError {
msg: "Cannot parse this query as a valid css selector".to_string(),
label: "Parse error".to_string(),
span: Some(head),
});
if let Some(query) = &query {
if let Err(err) = ScraperSelector::parse(&query.item) {
return Err(LabeledError::new("CSS query parse error")
.with_label(err.to_string(), query.span)
.with_help("cannot parse this query as a valid CSS selector"));
}
} else {
return Err(
LabeledError::new("Missing query argument").with_label("add --query here", call.head)
);
}
let selector = Selector {
query,
query: query.map(|q| q.item).unwrap_or_default(),
as_html,
attribute,
as_table,
@ -130,11 +134,8 @@ pub fn parse_selector_params(call: &EvaluatedCall, input: &Value) -> Result<Valu
let span = input.span();
match input {
Value::String { val, .. } => Ok(begin_selector_query(val.to_string(), selector, span)),
_ => Err(LabeledError {
label: "requires text input".to_string(),
msg: "Expected text from pipeline".to_string(),
span: Some(span),
}),
_ => Err(LabeledError::new("Requires text input")
.with_label("expected text from pipeline", span)),
}
}

View File

@ -1,5 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{record, Category, PluginSignature, Record, Span, Spanned, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
record, Category, LabeledError, PluginSignature, Record, Span, Spanned, SyntaxShape, Value,
};
use sxd_document::parser;
use sxd_xpath::{Context, Factory};
@ -38,11 +40,9 @@ pub fn execute_xpath_query(
let (query_string, span) = match &query {
Some(v) => (&v.item, v.span),
None => {
return Err(LabeledError {
msg: "problem with input data".to_string(),
label: "problem with input data".to_string(),
span: Some(call.head),
})
return Err(
LabeledError::new("problem with input data").with_label("query missing", call.head)
)
}
};
@ -50,12 +50,10 @@ pub fn execute_xpath_query(
let input_string = input.coerce_str()?;
let package = parser::parse(&input_string);
if package.is_err() {
return Err(LabeledError {
label: "invalid xml document".to_string(),
msg: "invalid xml document".to_string(),
span: Some(call.head),
});
if let Err(err) = package {
return Err(
LabeledError::new("Invalid XML document").with_label(err.to_string(), input.span())
);
}
let package = package.expect("invalid xml document");
@ -107,29 +105,20 @@ pub fn execute_xpath_query(
Ok(Value::list(records, call.head))
}
Err(_) => Err(LabeledError {
label: "xpath query error".to_string(),
msg: "xpath query error".to_string(),
span: Some(call.head),
}),
Err(err) => {
Err(LabeledError::new("xpath query error").with_label(err.to_string(), call.head))
}
}
}
fn build_xpath(xpath_str: &str, span: Span) -> Result<sxd_xpath::XPath, LabeledError> {
let factory = Factory::new();
if let Ok(xpath) = factory.build(xpath_str) {
xpath.ok_or_else(|| LabeledError {
label: "invalid xpath query".to_string(),
msg: "invalid xpath query".to_string(),
span: Some(span),
})
} else {
Err(LabeledError {
label: "expected valid xpath query".to_string(),
msg: "expected valid xpath query".to_string(),
span: Some(span),
})
match factory.build(xpath_str) {
Ok(xpath) => xpath.ok_or_else(|| {
LabeledError::new("invalid xpath query").with_label("the query must not be empty", span)
}),
Err(err) => Err(LabeledError::new("invalid xpath query").with_label(err.to_string(), span)),
}
}