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

View File

@ -16,9 +16,9 @@
//! invoked by Nushell. //! invoked by Nushell.
//! //!
//! ```rust,no_run //! ```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_plugin::{Plugin, PluginCommand, SimplePluginCommand, EngineInterface};
//! use nu_protocol::{PluginSignature, Value}; //! use nu_protocol::{PluginSignature, LabeledError, Value};
//! //!
//! struct MyPlugin; //! struct MyPlugin;
//! struct MyCommand; //! struct MyCommand;
@ -64,7 +64,7 @@ mod util;
pub use plugin::{ pub use plugin::{
serve_plugin, EngineInterface, Plugin, PluginCommand, PluginEncoder, SimplePluginCommand, serve_plugin, EngineInterface, Plugin, PluginCommand, PluginEncoder, SimplePluginCommand,
}; };
pub use protocol::{EvaluatedCall, LabeledError}; pub use protocol::EvaluatedCall;
pub use serializers::{json::JsonSerializer, msgpack::MsgPackSerializer}; pub use serializers::{json::JsonSerializer, msgpack::MsgPackSerializer};
// Used by other nu crates. // 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 /// The API for a Nushell plugin command
/// ///
@ -18,7 +18,7 @@ use crate::{EngineInterface, EvaluatedCall, LabeledError, Plugin};
/// Basic usage: /// Basic usage:
/// ``` /// ```
/// # use nu_plugin::*; /// # use nu_plugin::*;
/// # use nu_protocol::{PluginSignature, PipelineData, Type, Value}; /// # use nu_protocol::{PluginSignature, PipelineData, Type, Value, LabeledError};
/// struct LowercasePlugin; /// struct LowercasePlugin;
/// struct Lowercase; /// struct Lowercase;
/// ///
@ -108,7 +108,7 @@ pub trait PluginCommand: Sync {
/// Basic usage: /// Basic usage:
/// ``` /// ```
/// # use nu_plugin::*; /// # use nu_plugin::*;
/// # use nu_protocol::{PluginSignature, Type, Value}; /// # use nu_protocol::{PluginSignature, Type, Value, LabeledError};
/// struct HelloPlugin; /// struct HelloPlugin;
/// struct Hello; /// struct Hello;
/// ///

View File

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

View File

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

View File

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

View File

@ -12,8 +12,8 @@ use std::collections::HashMap;
pub use evaluated_call::EvaluatedCall; pub use evaluated_call::EvaluatedCall;
use nu_protocol::{ use nu_protocol::{
ast::Operator, engine::Closure, Config, PipelineData, PluginSignature, RawStream, ShellError, ast::Operator, engine::Closure, Config, LabeledError, PipelineData, PluginSignature, RawStream,
Span, Spanned, Value, ShellError, Span, Spanned, Value,
}; };
pub use plugin_custom_value::PluginCustomValue; pub use plugin_custom_value::PluginCustomValue;
#[cfg(test)] #[cfg(test)]
@ -289,73 +289,6 @@ pub enum StreamMessage {
Ack(StreamId), 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. /// Response to a [`PluginCall`]. The type parameter determines the output type for pipeline data.
/// ///
/// Note: exported for internal use, not public. /// Note: exported for internal use, not public.

View File

@ -1,11 +1,11 @@
macro_rules! generate_tests { macro_rules! generate_tests {
($encoder:expr) => { ($encoder:expr) => {
use crate::protocol::{ use crate::protocol::{
CallInfo, CustomValueOp, EvaluatedCall, LabeledError, PipelineDataHeader, PluginCall, CallInfo, CustomValueOp, EvaluatedCall, PipelineDataHeader, PluginCall,
PluginCallResponse, PluginCustomValue, PluginInput, PluginOption, PluginOutput, PluginCallResponse, PluginCustomValue, PluginInput, PluginOption, PluginOutput,
StreamData, StreamMessage, StreamData, StreamMessage,
}; };
use nu_protocol::{PluginSignature, Span, Spanned, SyntaxShape, Value}; use nu_protocol::{LabeledError, PluginSignature, Span, Spanned, SyntaxShape, Value};
#[test] #[test]
fn decode_eof() { fn decode_eof() {
@ -364,11 +364,15 @@ macro_rules! generate_tests {
#[test] #[test]
fn response_round_trip_error() { fn response_round_trip_error() {
let error = LabeledError { let error = LabeledError::new("label")
label: "label".into(), .with_code("test::error")
msg: "msg".into(), .with_url("https://example.org/test/error")
span: Some(Span::new(2, 30)), .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 response = PluginCallResponse::Error(error.clone());
let output = PluginOutput::CallResponse(6, response); let output = PluginOutput::CallResponse(6, response);
@ -392,11 +396,7 @@ macro_rules! generate_tests {
#[test] #[test]
fn response_round_trip_error_none() { fn response_round_trip_error_none() {
let error = LabeledError { let error = LabeledError::new("error");
label: "label".into(),
msg: "msg".into(),
span: None,
};
let response = PluginCallResponse::Error(error.clone()); let response = PluginCallResponse::Error(error.clone());
let output = PluginOutput::CallResponse(7, response); 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; pub mod cli_error;
mod labeled_error;
mod parse_error; mod parse_error;
mod parse_warning; mod parse_warning;
mod shell_error; mod shell_error;
pub use cli_error::{format_error, report_error, report_error_new}; 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_error::{DidYouMean, ParseError};
pub use parse_warning::ParseWarning; pub use parse_warning::ParseWarning;
pub use shell_error::*; pub use shell_error::*;

View File

@ -1097,6 +1097,11 @@ pub enum ShellError {
span: Span, 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. /// Attempted to use a command that has been removed from Nushell.
/// ///
/// ## Resolution /// ## 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> { pub fn into_code(err: &ShellError) -> Option<String> {
err.code().map(|code| code.to_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::{ use nu_protocol::{
record, Category, CustomValue, PluginSignature, ShellError, Span, SyntaxShape, Value, record, Category, CustomValue, LabeledError, PluginSignature, ShellError, Span, SyntaxShape,
Value,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, PluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{Category, PipelineData, PluginExample, PluginSignature, SyntaxShape, Type}; use nu_protocol::{
Category, LabeledError, PipelineData, PluginExample, PluginSignature, SyntaxShape, Type,
};
use crate::Example; 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::{ use nu_protocol::{
Category, IntoInterruptiblePipelineData, PipelineData, PluginExample, PluginSignature, Category, IntoInterruptiblePipelineData, LabeledError, PipelineData, PluginExample,
SyntaxShape, Type, Value, PluginSignature, SyntaxShape, Type, Value,
}; };
use crate::Example; use crate::Example;

View File

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

View File

@ -1,5 +1,5 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, PluginExample, PluginSignature, SyntaxShape, Value}; use nu_protocol::{Category, LabeledError, PluginExample, PluginSignature, SyntaxShape, Value};
use crate::Example; 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::{ use nu_protocol::{
Category, ListStream, PipelineData, PluginExample, PluginSignature, SyntaxShape, Type, Value, Category, LabeledError, ListStream, PipelineData, PluginExample, PluginSignature, SyntaxShape,
Type, Value,
}; };
use crate::Example; use crate::Example;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
use ical::parser::ical::component::*; use ical::parser::ical::component::*;
use ical::property::Property; use ical::property::Property;
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{
record, Category, PluginExample, PluginSignature, ShellError, Span, Type, Value, record, Category, LabeledError, PluginExample, PluginSignature, ShellError, Span, Type, Value,
}; };
use std::io::BufReader; 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::{ use nu_protocol::{
record, Category, PluginExample, PluginSignature, Record, ShellError, Type, Value, record, Category, LabeledError, PluginExample, PluginSignature, Record, ShellError, Type, Value,
}; };
use crate::FromCmds; use crate::FromCmds;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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