Improved labeled error from plugins (#437)

* improved labeled error from plugins

* corrected span
This commit is contained in:
Fernando Herrera 2021-12-05 03:11:19 +00:00 committed by GitHub
parent 03e22b071a
commit 22469a9cb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 611 additions and 208 deletions

View file

@ -38,7 +38,7 @@ impl Command for Hide {
{ {
pat pat
} else { } else {
return Err(ShellError::LabeledError( return Err(ShellError::SpannedLabeledError(
"Unexpected import".into(), "Unexpected import".into(),
"import pattern not supported".into(), "import pattern not supported".into(),
call.head, call.head,

View file

@ -35,7 +35,7 @@ impl Command for Use {
{ {
pat pat
} else { } else {
return Err(ShellError::LabeledError( return Err(ShellError::SpannedLabeledError(
"Unexpected import".into(), "Unexpected import".into(),
"import pattern not supported".into(), "import pattern not supported".into(),
call.head, call.head,

View file

@ -83,32 +83,32 @@ impl Command for Cp {
let prompt = format!( let prompt = format!(
"Are you shure that you want to copy {} to {}?", "Are you shure that you want to copy {} to {}?",
file.as_ref() file.as_ref()
.map_err(|err| ShellError::LabeledError( .map_err(|err| ShellError::SpannedLabeledError(
"Reference error".into(), "Reference error".into(),
err.to_string(), err.to_string(),
call.head call.head
))? ))?
.file_name() .file_name()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"File name error".into(), "File name error".into(),
"Unable to get file name".into(), "Unable to get file name".into(),
call.head call.head
))? ))?
.to_str() .to_str()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"Unable to get str error".into(), "Unable to get str error".into(),
"Unable to convert to str file name".into(), "Unable to convert to str file name".into(),
call.head call.head
))?, ))?,
destination destination
.file_name() .file_name()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"File name error".into(), "File name error".into(),
"Unable to get file name".into(), "Unable to get file name".into(),
call.head call.head
))? ))?
.to_str() .to_str()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"Unable to get str error".into(), "Unable to get str error".into(),
"Unable to convert to str file name".into(), "Unable to convert to str file name".into(),
call.head call.head

View file

@ -56,7 +56,7 @@ impl Command for Ls {
let call_span = call.head; let call_span = call.head;
let glob = glob::glob(&pattern).map_err(|err| { let glob = glob::glob(&pattern).map_err(|err| {
nu_protocol::ShellError::LabeledError( nu_protocol::ShellError::SpannedLabeledError(
"Error extracting glob pattern".into(), "Error extracting glob pattern".into(),
err.to_string(), err.to_string(),
call.head, call.head,

View file

@ -67,32 +67,32 @@ impl Command for Mv {
let prompt = format!( let prompt = format!(
"Are you shure that you want to move {} to {}?", "Are you shure that you want to move {} to {}?",
file.as_ref() file.as_ref()
.map_err(|err| ShellError::LabeledError( .map_err(|err| ShellError::SpannedLabeledError(
"Reference error".into(), "Reference error".into(),
err.to_string(), err.to_string(),
call.head call.head
))? ))?
.file_name() .file_name()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"File name error".into(), "File name error".into(),
"Unable to get file name".into(), "Unable to get file name".into(),
call.head call.head
))? ))?
.to_str() .to_str()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"Unable to get str error".into(), "Unable to get str error".into(),
"Unable to convert to str file name".into(), "Unable to convert to str file name".into(),
call.head call.head
))?, ))?,
destination destination
.file_name() .file_name()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"File name error".into(), "File name error".into(),
"Unable to get file name".into(), "Unable to get file name".into(),
call.head call.head
))? ))?
.to_str() .to_str()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"Unable to get str error".into(), "Unable to get str error".into(),
"Unable to convert to str file name".into(), "Unable to convert to str file name".into(),
call.head call.head

View file

@ -126,13 +126,13 @@ fn rm(
"Are you sure that you what to delete {}?", "Are you sure that you what to delete {}?",
file.1 file.1
.file_name() .file_name()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"File name error".into(), "File name error".into(),
"Unable to get file name".into(), "Unable to get file name".into(),
call.head call.head
))? ))?
.to_str() .to_str()
.ok_or_else(|| ShellError::LabeledError( .ok_or_else(|| ShellError::SpannedLabeledError(
"Unable to get str error".into(), "Unable to get str error".into(),
"Unable to convert to str file name".into(), "Unable to convert to str file name".into(),
call.head call.head
@ -188,7 +188,7 @@ fn rm_helper(call: &Call, args: RmArgs) -> Vec<Value> {
{ {
if trash { if trash {
let error = match call.get_flag_expr("trash").ok_or_else(|| { let error = match call.get_flag_expr("trash").ok_or_else(|| {
ShellError::LabeledError( ShellError::SpannedLabeledError(
"Flag not found".into(), "Flag not found".into(),
"trash flag not found".into(), "trash flag not found".into(),
call.head, call.head,

View file

@ -75,7 +75,7 @@ fn first_helper(
match input_peek match input_peek
.peek() .peek()
.ok_or_else(|| { .ok_or_else(|| {
ShellError::LabeledError( ShellError::SpannedLabeledError(
"Error in first".into(), "Error in first".into(),
"unable to pick on next value".into(), "unable to pick on next value".into(),
call.head, call.head,

View file

@ -87,7 +87,7 @@ impl Command for Kill {
left_span: call left_span: call
.get_named_arg("force") .get_named_arg("force")
.ok_or_else(|| { .ok_or_else(|| {
ShellError::LabeledError( ShellError::SpannedLabeledError(
"Flag error".into(), "Flag error".into(),
"flag force not found".into(), "flag force not found".into(),
call.head, call.head,
@ -98,7 +98,7 @@ impl Command for Kill {
right_span: span(&[ right_span: span(&[
call.get_named_arg("signal") call.get_named_arg("signal")
.ok_or_else(|| { .ok_or_else(|| {
ShellError::LabeledError( ShellError::SpannedLabeledError(
"Flag error".into(), "Flag error".into(),
"flag signal not found".into(), "flag signal not found".into(),
call.head, call.head,

View file

@ -140,7 +140,7 @@ pub fn icon_for_file(file_path: &Path) -> Result<char, ShellError> {
let str = file_path let str = file_path
.file_name() .file_name()
.ok_or_else(|| { .ok_or_else(|| {
ShellError::LabeledError( ShellError::SpannedLabeledError(
"File name error".into(), "File name error".into(),
"Unable to get file name".into(), "Unable to get file name".into(),
Span::unknown(), Span::unknown(),
@ -148,7 +148,7 @@ pub fn icon_for_file(file_path: &Path) -> Result<char, ShellError> {
})? })?
.to_str() .to_str()
.ok_or_else(|| { .ok_or_else(|| {
ShellError::LabeledError( ShellError::SpannedLabeledError(
"Unable to get str error".into(), "Unable to get str error".into(),
"Unable to convert to str file name".into(), "Unable to convert to str file name".into(),
Span::unknown(), Span::unknown(),
@ -164,7 +164,7 @@ pub fn icon_for_file(file_path: &Path) -> Result<char, ShellError> {
Ok(icon) Ok(icon)
} else if let Some(ext) = file_path.extension().as_ref() { } else if let Some(ext) = file_path.extension().as_ref() {
let str = ext.to_str().ok_or_else(|| { let str = ext.to_str().ok_or_else(|| {
ShellError::LabeledError( ShellError::SpannedLabeledError(
"Unable to get str error".into(), "Unable to get str error".into(),
"Unable to convert to str file name".into(), "Unable to convert to str file name".into(),
Span::unknown(), Span::unknown(),

View file

@ -57,7 +57,8 @@ struct Signature {
extraUsage @2 :Text; extraUsage @2 :Text;
requiredPositional @3 :List(Argument); requiredPositional @3 :List(Argument);
optionalPositional @4 :List(Argument); optionalPositional @4 :List(Argument);
rest @5 :Argument; # Optional value. Check for existence when deserializing # Optional value. Check for existence when deserializing
rest @5 :Argument;
named @6 :List(Flag); named @6 :List(Flag);
isFilter @7 :Bool; isFilter @7 :Bool;
category @8 :Category; category @8 :Category;
@ -81,7 +82,8 @@ enum Category {
struct Flag { struct Flag {
long @0 :Text; long @0 :Text;
short @1 :Text; # Optional value. Check for existence when deserializing # Optional value. Check for existence when deserializing (has_short)
short @1 :Text;
arg @2 :Shape; arg @2 :Shape;
required @3 :Bool; required @3 :Bool;
desc @4 :Text; desc @4 :Text;
@ -113,9 +115,9 @@ struct EvaluatedCall {
} }
struct CallInfo { struct CallInfo {
name @0: Text; name @0 :Text;
call @1: EvaluatedCall; call @1 :EvaluatedCall;
input @2: Value; input @2 :Value;
} }
# Main communication structs with the plugin # Main communication structs with the plugin
@ -128,8 +130,15 @@ struct PluginCall {
struct PluginResponse { struct PluginResponse {
union { union {
error @0 :Text; error @0 :LabeledError;
signature @1 :List(Signature); signature @1 :List(Signature);
value @2 :Value; value @2 :Value;
} }
} }
struct LabeledError {
label @0 :Text;
msg @1 :Text;
# Optional Value. When decoding check if it exists (has_span)
span @2 :Span;
}

View file

@ -5,6 +5,10 @@ use nu_protocol::{
FromValue, ShellError, Span, Spanned, Value, FromValue, ShellError, Span, Spanned, Value,
}; };
// The evaluated call is used with the Plugins because the plugin doesn't have
// access to the Stack and the EngineState. For that reason, before encoding the
// message to the plugin all the arguments to the original call (which are expressions)
// are evaluated and passed to Values
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EvaluatedCall { pub struct EvaluatedCall {
pub head: Span, pub head: Span,

View file

@ -1,8 +1,7 @@
pub mod evaluated_call; pub mod evaluated_call;
pub mod plugin; pub mod plugin;
pub mod plugin_call;
pub mod plugin_capnp; pub mod plugin_capnp;
pub mod serializers; pub mod serializers;
pub use evaluated_call::EvaluatedCall; pub use evaluated_call::EvaluatedCall;
pub use plugin::{serve_plugin, Plugin}; pub use plugin::{serve_plugin, LabeledError, Plugin};

View file

@ -1,11 +1,11 @@
use crate::plugin_call::{self, decode_call, encode_response}; use crate::serializers::{decode_call, decode_response, encode_call, encode_response};
use std::io::BufReader; use std::io::BufReader;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command as CommandSys, Stdio}; use std::process::{Command as CommandSys, Stdio};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ast::Call, Signature, Value}; use nu_protocol::{ast::Call, Signature, Value};
use nu_protocol::{PipelineData, ShellError}; use nu_protocol::{PipelineData, ShellError, Span};
use super::evaluated_call::EvaluatedCall; use super::evaluated_call::EvaluatedCall;
@ -25,10 +25,73 @@ pub enum PluginCall {
CallInfo(Box<CallInfo>), CallInfo(Box<CallInfo>),
} }
#[derive(Clone, Debug, PartialEq)]
pub struct LabeledError {
pub label: String,
pub msg: String,
pub span: Option<Span>,
}
impl From<LabeledError> for ShellError {
fn from(error: LabeledError) -> Self {
match error.span {
Some(span) => ShellError::SpannedLabeledError(error.label, error.msg, span),
None => ShellError::LabeledError(error.label, error.msg),
}
}
}
impl From<ShellError> for LabeledError {
fn from(error: ShellError) -> Self {
match error {
ShellError::SpannedLabeledError(label, msg, span) => LabeledError {
label,
msg,
span: Some(span),
},
ShellError::LabeledError(label, msg) => LabeledError {
label,
msg,
span: None,
},
ShellError::CantConvert(expected, input, span) => LabeledError {
label: format!("Can't convert to {}", expected),
msg: format!("can't convert {} to {}", expected, input),
span: Some(span),
},
ShellError::DidYouMean(suggestion, span) => LabeledError {
label: "Name not found".into(),
msg: format!("did you mean '{}'", suggestion),
span: Some(span),
},
ShellError::PluginFailedToLoad(msg) => LabeledError {
label: "Plugin failed to load".into(),
msg,
span: None,
},
ShellError::PluginFailedToEncode(msg) => LabeledError {
label: "Plugin failed to encode".into(),
msg,
span: None,
},
ShellError::PluginFailedToDecode(msg) => LabeledError {
label: "Plugin failed to decode".into(),
msg,
span: None,
},
err => LabeledError {
label: "Error - Add to LabeledError From<ShellError>".into(),
msg: err.to_string(),
span: None,
},
}
}
}
// Information received from the plugin // Information received from the plugin
#[derive(Debug)] #[derive(Debug)]
pub enum PluginResponse { pub enum PluginResponse {
Error(String), Error(LabeledError),
Signature(Vec<Signature>), Signature(Vec<Signature>),
Value(Box<Value>), Value(Box<Value>),
} }
@ -44,21 +107,18 @@ pub fn get_signature(path: &Path) -> Result<Vec<Signature>, ShellError> {
// send call to plugin asking for signature // send call to plugin asking for signature
if let Some(stdin_writer) = &mut child.stdin { if let Some(stdin_writer) = &mut child.stdin {
let mut writer = stdin_writer; let mut writer = stdin_writer;
plugin_call::encode_call(&PluginCall::Signature, &mut writer)? encode_call(&PluginCall::Signature, &mut writer)?
} }
// deserialize response from plugin to extract the signature // deserialize response from plugin to extract the signature
let signature = if let Some(stdout_reader) = &mut child.stdout { let signature = if let Some(stdout_reader) = &mut child.stdout {
let reader = stdout_reader; let reader = stdout_reader;
let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader); let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader);
let response = plugin_call::decode_response(&mut buf_read)?; let response = decode_response(&mut buf_read)?;
match response { match response {
PluginResponse::Signature(sign) => Ok(sign), PluginResponse::Signature(sign) => Ok(sign),
PluginResponse::Error(msg) => Err(ShellError::PluginFailedToLoad(format!( PluginResponse::Error(err) => Err(err.into()),
"Plugin response error {}",
msg,
))),
_ => Err(ShellError::PluginFailedToLoad( _ => Err(ShellError::PluginFailedToLoad(
"Plugin missing signature".into(), "Plugin missing signature".into(),
)), )),
@ -139,7 +199,7 @@ impl Command for PluginDeclaration {
let mut child = plugin_cmd.spawn().map_err(|err| { let mut child = plugin_cmd.spawn().map_err(|err| {
let decl = engine_state.get_decl(call.decl_id); let decl = engine_state.get_decl(call.decl_id);
ShellError::LabeledError( ShellError::SpannedLabeledError(
format!("Unable to spawn plugin for {}", decl.name()), format!("Unable to spawn plugin for {}", decl.name()),
format!("{}", err), format!("{}", err),
call.head, call.head,
@ -170,9 +230,9 @@ impl Command for PluginDeclaration {
let mut writer = stdin_writer; let mut writer = stdin_writer;
plugin_call::encode_call(&plugin_call, &mut writer).map_err(|err| { encode_call(&plugin_call, &mut writer).map_err(|err| {
let decl = engine_state.get_decl(call.decl_id); let decl = engine_state.get_decl(call.decl_id);
ShellError::LabeledError( ShellError::SpannedLabeledError(
format!("Unable to encode call for {}", decl.name()), format!("Unable to encode call for {}", decl.name()),
err.to_string(), err.to_string(),
call.head, call.head,
@ -184,9 +244,9 @@ impl Command for PluginDeclaration {
let pipeline_data = if let Some(stdout_reader) = &mut child.stdout { let pipeline_data = if let Some(stdout_reader) = &mut child.stdout {
let reader = stdout_reader; let reader = stdout_reader;
let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader); let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader);
let response = plugin_call::decode_response(&mut buf_read).map_err(|err| { let response = decode_response(&mut buf_read).map_err(|err| {
let decl = engine_state.get_decl(call.decl_id); let decl = engine_state.get_decl(call.decl_id);
ShellError::LabeledError( ShellError::SpannedLabeledError(
format!("Unable to decode call for {}", decl.name()), format!("Unable to decode call for {}", decl.name()),
err.to_string(), err.to_string(),
call.head, call.head,
@ -197,19 +257,15 @@ impl Command for PluginDeclaration {
PluginResponse::Value(value) => { PluginResponse::Value(value) => {
Ok(PipelineData::Value(value.as_ref().clone(), None)) Ok(PipelineData::Value(value.as_ref().clone(), None))
} }
PluginResponse::Error(msg) => Err(ShellError::LabeledError( PluginResponse::Error(err) => Err(err.into()),
"Error received from plugin".into(), PluginResponse::Signature(..) => Err(ShellError::SpannedLabeledError(
msg,
call.head,
)),
_ => Err(ShellError::LabeledError(
"Plugin missing value".into(), "Plugin missing value".into(),
"No value received from plugin".into(), "Received a signature from plugin instead of value".into(),
call.head, call.head,
)), )),
} }
} else { } else {
Err(ShellError::LabeledError( Err(ShellError::SpannedLabeledError(
"Error with stdout reader".into(), "Error with stdout reader".into(),
"no stdout reader".into(), "no stdout reader".into(),
call.head, call.head,
@ -226,24 +282,43 @@ impl Command for PluginDeclaration {
} }
} }
/// The `Plugin` trait defines the API which plugins use to "hook" into nushell. // The next trait and functions are part of the plugin that is being created
// The `Plugin` trait defines the API which plugins use to "hook" into nushell.
pub trait Plugin { pub trait Plugin {
fn signature(&self) -> Vec<Signature>; fn signature(&self) -> Vec<Signature>;
fn run(&mut self, name: &str, call: &EvaluatedCall, input: &Value) fn run(
-> Result<Value, ShellError>; &mut self,
name: &str,
call: &EvaluatedCall,
input: &Value,
) -> Result<Value, LabeledError>;
} }
// Function used in the plugin definition for the communication protocol between // Function used in the plugin definition for the communication protocol between
// nushell and the external plugin. // nushell and the external plugin.
// If you want to create a new plugin you have to use this function as the main // When creating a new plugin you have to use this function as the main
// entry point for the plugin // entry point for the plugin, e.g.
//
// fn main() {
// serve_plugin(plugin)
// }
//
// where plugin is your struct that implements the Plugin trait
//
// Note. When defining a plugin in other language but Rust, you will have to compile
// the plugin.capnp schema to create the object definitions that will be returned from
// the plugin.
// The object that is expected to be received by nushell is the PluginResponse struct.
// That should be encoded correctly and sent to StdOut for nushell to decode and
// and present its result
//
pub fn serve_plugin(plugin: &mut impl Plugin) { pub fn serve_plugin(plugin: &mut impl Plugin) {
let mut stdin_buf = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, std::io::stdin()); let mut stdin_buf = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, std::io::stdin());
let plugin_call = decode_call(&mut stdin_buf); let plugin_call = decode_call(&mut stdin_buf);
match plugin_call { match plugin_call {
Err(err) => { Err(err) => {
let response = PluginResponse::Error(err.to_string()); let response = PluginResponse::Error(err.into());
encode_response(&response, &mut std::io::stdout()).expect("Error encoding response"); encode_response(&response, &mut std::io::stdout()).expect("Error encoding response");
} }
Ok(plugin_call) => { Ok(plugin_call) => {
@ -259,7 +334,7 @@ pub fn serve_plugin(plugin: &mut impl Plugin) {
let response = match value { let response = match value {
Ok(value) => PluginResponse::Value(Box::new(value)), Ok(value) => PluginResponse::Value(Box::new(value)),
Err(err) => PluginResponse::Error(err.to_string()), Err(err) => PluginResponse::Error(err),
}; };
encode_response(&response, &mut std::io::stdout()) encode_response(&response, &mut std::io::stdout())
.expect("Error encoding response"); .expect("Error encoding response");

View file

@ -3812,14 +3812,21 @@ pub mod plugin_response {
self.builder.into_reader().total_size() self.builder.into_reader().total_size()
} }
#[inline] #[inline]
pub fn set_error(&mut self, value: ::capnp::text::Reader<'_>) { pub fn set_error(
&mut self,
value: crate::plugin_capnp::labeled_error::Reader<'_>,
) -> ::capnp::Result<()> {
self.builder.set_data_field::<u16>(0, 0); self.builder.set_data_field::<u16>(0, 0);
self.builder.get_pointer_field(0).set_text(value); ::capnp::traits::SetPointerBuilder::set_pointer_builder(
self.builder.get_pointer_field(0),
value,
false,
)
} }
#[inline] #[inline]
pub fn init_error(self, size: u32) -> ::capnp::text::Builder<'a> { pub fn init_error(self) -> crate::plugin_capnp::labeled_error::Builder<'a> {
self.builder.set_data_field::<u16>(0, 0); self.builder.set_data_field::<u16>(0, 0);
self.builder.get_pointer_field(0).init_text(size) ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0)
} }
pub fn has_error(&self) -> bool { pub fn has_error(&self) -> bool {
if self.builder.get_data_field::<u16>(0) != 0 { if self.builder.get_data_field::<u16>(0) != 0 {
@ -3930,13 +3937,266 @@ pub mod plugin_response {
Value(A2), Value(A2),
} }
pub type WhichReader<'a> = Which< pub type WhichReader<'a> = Which<
::capnp::Result<::capnp::text::Reader<'a>>, ::capnp::Result<crate::plugin_capnp::labeled_error::Reader<'a>>,
::capnp::Result<::capnp::struct_list::Reader<'a, crate::plugin_capnp::signature::Owned>>, ::capnp::Result<::capnp::struct_list::Reader<'a, crate::plugin_capnp::signature::Owned>>,
::capnp::Result<crate::plugin_capnp::value::Reader<'a>>, ::capnp::Result<crate::plugin_capnp::value::Reader<'a>>,
>; >;
pub type WhichBuilder<'a> = Which< pub type WhichBuilder<'a> = Which<
::capnp::Result<::capnp::text::Builder<'a>>, ::capnp::Result<crate::plugin_capnp::labeled_error::Builder<'a>>,
::capnp::Result<::capnp::struct_list::Builder<'a, crate::plugin_capnp::signature::Owned>>, ::capnp::Result<::capnp::struct_list::Builder<'a, crate::plugin_capnp::signature::Owned>>,
::capnp::Result<crate::plugin_capnp::value::Builder<'a>>, ::capnp::Result<crate::plugin_capnp::value::Builder<'a>>,
>; >;
} }
pub mod labeled_error {
#[derive(Copy, Clone)]
pub struct Owned(());
impl<'a> ::capnp::traits::Owned<'a> for Owned {
type Reader = Reader<'a>;
type Builder = Builder<'a>;
}
impl<'a> ::capnp::traits::OwnedStruct<'a> for Owned {
type Reader = Reader<'a>;
type Builder = Builder<'a>;
}
impl ::capnp::traits::Pipelined for Owned {
type Pipeline = Pipeline;
}
#[derive(Clone, Copy)]
pub struct Reader<'a> {
reader: ::capnp::private::layout::StructReader<'a>,
}
impl<'a> ::capnp::traits::HasTypeId for Reader<'a> {
#[inline]
fn type_id() -> u64 {
_private::TYPE_ID
}
}
impl<'a> ::capnp::traits::FromStructReader<'a> for Reader<'a> {
fn new(reader: ::capnp::private::layout::StructReader<'a>) -> Reader<'a> {
Reader { reader }
}
}
impl<'a> ::capnp::traits::FromPointerReader<'a> for Reader<'a> {
fn get_from_pointer(
reader: &::capnp::private::layout::PointerReader<'a>,
default: ::core::option::Option<&'a [capnp::Word]>,
) -> ::capnp::Result<Reader<'a>> {
::core::result::Result::Ok(::capnp::traits::FromStructReader::new(
reader.get_struct(default)?,
))
}
}
impl<'a> ::capnp::traits::IntoInternalStructReader<'a> for Reader<'a> {
fn into_internal_struct_reader(self) -> ::capnp::private::layout::StructReader<'a> {
self.reader
}
}
impl<'a> ::capnp::traits::Imbue<'a> for Reader<'a> {
fn imbue(&mut self, cap_table: &'a ::capnp::private::layout::CapTable) {
self.reader
.imbue(::capnp::private::layout::CapTableReader::Plain(cap_table))
}
}
impl<'a> Reader<'a> {
pub fn reborrow(&self) -> Reader<'_> {
Reader { ..*self }
}
pub fn total_size(&self) -> ::capnp::Result<::capnp::MessageSize> {
self.reader.total_size()
}
#[inline]
pub fn get_label(self) -> ::capnp::Result<::capnp::text::Reader<'a>> {
::capnp::traits::FromPointerReader::get_from_pointer(
&self.reader.get_pointer_field(0),
::core::option::Option::None,
)
}
pub fn has_label(&self) -> bool {
!self.reader.get_pointer_field(0).is_null()
}
#[inline]
pub fn get_msg(self) -> ::capnp::Result<::capnp::text::Reader<'a>> {
::capnp::traits::FromPointerReader::get_from_pointer(
&self.reader.get_pointer_field(1),
::core::option::Option::None,
)
}
pub fn has_msg(&self) -> bool {
!self.reader.get_pointer_field(1).is_null()
}
#[inline]
pub fn get_span(self) -> ::capnp::Result<crate::plugin_capnp::span::Reader<'a>> {
::capnp::traits::FromPointerReader::get_from_pointer(
&self.reader.get_pointer_field(2),
::core::option::Option::None,
)
}
pub fn has_span(&self) -> bool {
!self.reader.get_pointer_field(2).is_null()
}
}
pub struct Builder<'a> {
builder: ::capnp::private::layout::StructBuilder<'a>,
}
impl<'a> ::capnp::traits::HasStructSize for Builder<'a> {
#[inline]
fn struct_size() -> ::capnp::private::layout::StructSize {
_private::STRUCT_SIZE
}
}
impl<'a> ::capnp::traits::HasTypeId for Builder<'a> {
#[inline]
fn type_id() -> u64 {
_private::TYPE_ID
}
}
impl<'a> ::capnp::traits::FromStructBuilder<'a> for Builder<'a> {
fn new(builder: ::capnp::private::layout::StructBuilder<'a>) -> Builder<'a> {
Builder { builder }
}
}
impl<'a> ::capnp::traits::ImbueMut<'a> for Builder<'a> {
fn imbue_mut(&mut self, cap_table: &'a mut ::capnp::private::layout::CapTable) {
self.builder
.imbue(::capnp::private::layout::CapTableBuilder::Plain(cap_table))
}
}
impl<'a> ::capnp::traits::FromPointerBuilder<'a> for Builder<'a> {
fn init_pointer(
builder: ::capnp::private::layout::PointerBuilder<'a>,
_size: u32,
) -> Builder<'a> {
::capnp::traits::FromStructBuilder::new(builder.init_struct(_private::STRUCT_SIZE))
}
fn get_from_pointer(
builder: ::capnp::private::layout::PointerBuilder<'a>,
default: ::core::option::Option<&'a [capnp::Word]>,
) -> ::capnp::Result<Builder<'a>> {
::core::result::Result::Ok(::capnp::traits::FromStructBuilder::new(
builder.get_struct(_private::STRUCT_SIZE, default)?,
))
}
}
impl<'a> ::capnp::traits::SetPointerBuilder for Reader<'a> {
fn set_pointer_builder<'b>(
pointer: ::capnp::private::layout::PointerBuilder<'b>,
value: Reader<'a>,
canonicalize: bool,
) -> ::capnp::Result<()> {
pointer.set_struct(&value.reader, canonicalize)
}
}
impl<'a> Builder<'a> {
pub fn into_reader(self) -> Reader<'a> {
::capnp::traits::FromStructReader::new(self.builder.into_reader())
}
pub fn reborrow(&mut self) -> Builder<'_> {
Builder { ..*self }
}
pub fn reborrow_as_reader(&self) -> Reader<'_> {
::capnp::traits::FromStructReader::new(self.builder.into_reader())
}
pub fn total_size(&self) -> ::capnp::Result<::capnp::MessageSize> {
self.builder.into_reader().total_size()
}
#[inline]
pub fn get_label(self) -> ::capnp::Result<::capnp::text::Builder<'a>> {
::capnp::traits::FromPointerBuilder::get_from_pointer(
self.builder.get_pointer_field(0),
::core::option::Option::None,
)
}
#[inline]
pub fn set_label(&mut self, value: ::capnp::text::Reader<'_>) {
self.builder.get_pointer_field(0).set_text(value);
}
#[inline]
pub fn init_label(self, size: u32) -> ::capnp::text::Builder<'a> {
self.builder.get_pointer_field(0).init_text(size)
}
pub fn has_label(&self) -> bool {
!self.builder.get_pointer_field(0).is_null()
}
#[inline]
pub fn get_msg(self) -> ::capnp::Result<::capnp::text::Builder<'a>> {
::capnp::traits::FromPointerBuilder::get_from_pointer(
self.builder.get_pointer_field(1),
::core::option::Option::None,
)
}
#[inline]
pub fn set_msg(&mut self, value: ::capnp::text::Reader<'_>) {
self.builder.get_pointer_field(1).set_text(value);
}
#[inline]
pub fn init_msg(self, size: u32) -> ::capnp::text::Builder<'a> {
self.builder.get_pointer_field(1).init_text(size)
}
pub fn has_msg(&self) -> bool {
!self.builder.get_pointer_field(1).is_null()
}
#[inline]
pub fn get_span(self) -> ::capnp::Result<crate::plugin_capnp::span::Builder<'a>> {
::capnp::traits::FromPointerBuilder::get_from_pointer(
self.builder.get_pointer_field(2),
::core::option::Option::None,
)
}
#[inline]
pub fn set_span(
&mut self,
value: crate::plugin_capnp::span::Reader<'_>,
) -> ::capnp::Result<()> {
::capnp::traits::SetPointerBuilder::set_pointer_builder(
self.builder.get_pointer_field(2),
value,
false,
)
}
#[inline]
pub fn init_span(self) -> crate::plugin_capnp::span::Builder<'a> {
::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(2), 0)
}
pub fn has_span(&self) -> bool {
!self.builder.get_pointer_field(2).is_null()
}
}
pub struct Pipeline {
_typeless: ::capnp::any_pointer::Pipeline,
}
impl ::capnp::capability::FromTypelessPipeline for Pipeline {
fn new(typeless: ::capnp::any_pointer::Pipeline) -> Pipeline {
Pipeline {
_typeless: typeless,
}
}
}
impl Pipeline {
pub fn get_span(&self) -> crate::plugin_capnp::span::Pipeline {
::capnp::capability::FromTypelessPipeline::new(self._typeless.get_pointer_field(2))
}
}
mod _private {
use capnp::private::layout;
pub const STRUCT_SIZE: layout::StructSize = layout::StructSize {
data: 0,
pointers: 3,
};
pub const TYPE_ID: u64 = 0x94d1_6904_99e7_04fe;
}
}

View file

@ -38,7 +38,7 @@ fn serialize_named(
entry_builder entry_builder
.reborrow() .reborrow()
.set_key(key.item.as_str()) .set_key(key.item.as_str())
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))?;
if let Some(value) = expression { if let Some(value) = expression {
let value_builder = entry_builder.init_value(); let value_builder = entry_builder.init_value();
@ -54,7 +54,7 @@ pub(crate) fn deserialize_call(
) -> Result<EvaluatedCall, ShellError> { ) -> Result<EvaluatedCall, ShellError> {
let head_reader = reader let head_reader = reader
.get_head() .get_head()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let head = Span { let head = Span {
start: head_reader.get_start() as usize, start: head_reader.get_start() as usize,
@ -77,7 +77,7 @@ fn deserialize_positionals(
) -> Result<Vec<Value>, ShellError> { ) -> Result<Vec<Value>, ShellError> {
let positional_reader = reader let positional_reader = reader
.get_positional() .get_positional()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
positional_reader positional_reader
.iter() .iter()
@ -90,11 +90,11 @@ type NamedList = Vec<(Spanned<String>, Option<Value>)>;
fn deserialize_named(span: Span, reader: evaluated_call::Reader) -> Result<NamedList, ShellError> { fn deserialize_named(span: Span, reader: evaluated_call::Reader) -> Result<NamedList, ShellError> {
let named_reader = reader let named_reader = reader
.get_named() .get_named()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let entries_list = named_reader let entries_list = named_reader
.get_entries() .get_entries()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let mut entries: Vec<(Spanned<String>, Option<Value>)> = let mut entries: Vec<(Spanned<String>, Option<Value>)> =
Vec::with_capacity(entries_list.len() as usize); Vec::with_capacity(entries_list.len() as usize);
@ -102,16 +102,16 @@ fn deserialize_named(span: Span, reader: evaluated_call::Reader) -> Result<Named
for entry_reader in entries_list { for entry_reader in entries_list {
let item = entry_reader let item = entry_reader
.get_key() .get_key()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))? .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?
.to_string(); .to_string();
let value = if entry_reader.has_value() { let value = if entry_reader.has_value() {
let value_reader = entry_reader let value_reader = entry_reader
.get_value() .get_value()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let value = value::deserialize_value(value_reader) let value = value::deserialize_value(value_reader)
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
Some(value) Some(value)
} else { } else {

View file

@ -1,3 +1,6 @@
pub mod call; mod call;
pub mod signature; mod plugin_call;
pub mod value; mod signature;
mod value;
pub use plugin_call::*;

View file

@ -1,9 +1,9 @@
use crate::plugin::{CallInfo, PluginCall, PluginResponse}; use crate::plugin::{CallInfo, LabeledError, PluginCall, PluginResponse};
use crate::plugin_capnp::{plugin_call, plugin_response}; use crate::plugin_capnp::{plugin_call, plugin_response};
use crate::serializers::signature::deserialize_signature; use crate::serializers::signature::deserialize_signature;
use crate::serializers::{call, signature, value}; use crate::serializers::{call, signature, value};
use capnp::serialize; use capnp::serialize;
use nu_protocol::{ShellError, Signature}; use nu_protocol::{ShellError, Signature, Span};
pub fn encode_call( pub fn encode_call(
plugin_call: &PluginCall, plugin_call: &PluginCall,
@ -25,54 +25,54 @@ pub fn encode_call(
let call_builder = call_info_builder let call_builder = call_info_builder
.reborrow() .reborrow()
.get_call() .get_call()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))?;
call::serialize_call(&call_info.call, call_builder) call::serialize_call(&call_info.call, call_builder)
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))?;
// Serializing the input value from the call info // Serializing the input value from the call info
let value_builder = call_info_builder let value_builder = call_info_builder
.reborrow() .reborrow()
.get_input() .get_input()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))?;
value::serialize_value(&call_info.input, value_builder); value::serialize_value(&call_info.input, value_builder);
} }
}; };
serialize::write_message(writer, &message) serialize::write_message(writer, &message)
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string())) .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))
} }
pub fn decode_call(reader: &mut impl std::io::BufRead) -> Result<PluginCall, ShellError> { pub fn decode_call(reader: &mut impl std::io::BufRead) -> Result<PluginCall, ShellError> {
let message_reader = serialize::read_message(reader, ::capnp::message::ReaderOptions::new()) let message_reader = serialize::read_message(reader, ::capnp::message::ReaderOptions::new())
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let reader = message_reader let reader = message_reader
.get_root::<plugin_call::Reader>() .get_root::<plugin_call::Reader>()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
match reader.which() { match reader.which() {
Err(capnp::NotInSchema(_)) => { Err(capnp::NotInSchema(_)) => Err(ShellError::PluginFailedToDecode(
Err(ShellError::PluginFailedToLoad("value not in schema".into())) "value not in schema".into(),
} )),
Ok(plugin_call::Signature(())) => Ok(PluginCall::Signature), Ok(plugin_call::Signature(())) => Ok(PluginCall::Signature),
Ok(plugin_call::CallInfo(reader)) => { Ok(plugin_call::CallInfo(reader)) => {
let reader = reader.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; let reader = reader.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let name = reader let name = reader
.get_name() .get_name()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let call_reader = reader let call_reader = reader
.get_call() .get_call()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let call = call::deserialize_call(call_reader)?; let call = call::deserialize_call(call_reader)?;
let input_reader = reader let input_reader = reader
.get_input() .get_input()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let input = value::deserialize_value(input_reader)?; let input = value::deserialize_value(input_reader)?;
@ -94,7 +94,17 @@ pub fn encode_response(
let mut builder = message.init_root::<plugin_response::Builder>(); let mut builder = message.init_root::<plugin_response::Builder>();
match &plugin_response { match &plugin_response {
PluginResponse::Error(msg) => builder.reborrow().set_error(msg.as_str()), PluginResponse::Error(msg) => {
let mut error_builder = builder.reborrow().init_error();
error_builder.set_label(&msg.label);
error_builder.set_msg(&msg.msg);
if let Some(span) = msg.span {
let mut span_builder = error_builder.reborrow().init_span();
span_builder.set_start(span.start as u64);
span_builder.set_end(span.end as u64);
}
}
PluginResponse::Signature(signatures) => { PluginResponse::Signature(signatures) => {
let mut signature_list_builder = let mut signature_list_builder =
builder.reborrow().init_signature(signatures.len() as u32); builder.reborrow().init_signature(signatures.len() as u32);
@ -111,28 +121,55 @@ pub fn encode_response(
}; };
serialize::write_message(writer, &message) serialize::write_message(writer, &message)
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string())) .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))
} }
pub fn decode_response(reader: &mut impl std::io::BufRead) -> Result<PluginResponse, ShellError> { pub fn decode_response(reader: &mut impl std::io::BufRead) -> Result<PluginResponse, ShellError> {
let message_reader = serialize::read_message(reader, ::capnp::message::ReaderOptions::new()) let message_reader = serialize::read_message(reader, ::capnp::message::ReaderOptions::new())
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let reader = message_reader let reader = message_reader
.get_root::<plugin_response::Reader>() .get_root::<plugin_response::Reader>()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
match reader.which() { match reader.which() {
Err(capnp::NotInSchema(_)) => { Err(capnp::NotInSchema(_)) => Err(ShellError::PluginFailedToDecode(
Err(ShellError::PluginFailedToLoad("value not in schema".into())) "value not in schema".into(),
} )),
Ok(plugin_response::Error(reader)) => { Ok(plugin_response::Error(reader)) => {
let msg = reader.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; let reader = reader.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
Ok(PluginResponse::Error(msg.to_string())) let msg = reader
.get_msg()
.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let label = reader
.get_label()
.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let span = if reader.has_span() {
let span = reader
.get_span()
.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
Some(Span {
start: span.get_start() as usize,
end: span.get_end() as usize,
})
} else {
None
};
let error = LabeledError {
label: label.into(),
msg: msg.into(),
span,
};
Ok(PluginResponse::Error(error))
} }
Ok(plugin_response::Signature(reader)) => { Ok(plugin_response::Signature(reader)) => {
let reader = reader.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; let reader = reader.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let signatures = reader let signatures = reader
.iter() .iter()
@ -142,9 +179,9 @@ pub fn decode_response(reader: &mut impl std::io::BufRead) -> Result<PluginRespo
Ok(PluginResponse::Signature(signatures)) Ok(PluginResponse::Signature(signatures))
} }
Ok(plugin_response::Value(reader)) => { Ok(plugin_response::Value(reader)) => {
let reader = reader.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; let reader = reader.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let val = value::deserialize_value(reader) let val = value::deserialize_value(reader)
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
Ok(PluginResponse::Value(Box::new(val))) Ok(PluginResponse::Value(Box::new(val)))
} }
@ -327,8 +364,12 @@ mod tests {
#[test] #[test]
fn response_round_trip_error() { fn response_round_trip_error() {
let message = "some error".to_string(); let error = LabeledError {
let response = PluginResponse::Error(message.clone()); label: "label".into(),
msg: "msg".into(),
span: Some(Span { start: 2, end: 30 }),
};
let response = PluginResponse::Error(error.clone());
let mut buffer: Vec<u8> = Vec::new(); let mut buffer: Vec<u8> = Vec::new();
encode_response(&response, &mut buffer).expect("unable to serialize message"); encode_response(&response, &mut buffer).expect("unable to serialize message");
@ -336,7 +377,7 @@ mod tests {
decode_response(&mut buffer.as_slice()).expect("unable to deserialize message"); decode_response(&mut buffer.as_slice()).expect("unable to deserialize message");
match returned { match returned {
PluginResponse::Error(msg) => assert_eq!(message, msg), PluginResponse::Error(msg) => assert_eq!(error, msg),
PluginResponse::Signature(_) => panic!("returned wrong call type"), PluginResponse::Signature(_) => panic!("returned wrong call type"),
PluginResponse::Value(_) => panic!("returned wrong call type"), PluginResponse::Value(_) => panic!("returned wrong call type"),
} }

View file

@ -96,18 +96,18 @@ fn serialize_flag(arg: &Flag, mut builder: flag::Builder) {
pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result<Signature, ShellError> { pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result<Signature, ShellError> {
let name = reader let name = reader
.get_name() .get_name()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let usage = reader let usage = reader
.get_usage() .get_usage()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let extra_usage = reader let extra_usage = reader
.get_extra_usage() .get_extra_usage()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let is_filter = reader.get_is_filter(); let is_filter = reader.get_is_filter();
let category = match reader let category = match reader
.get_category() .get_category()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))? .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?
{ {
PluginCategory::Default => Category::Default, PluginCategory::Default => Category::Default,
PluginCategory::Conversions => Category::Conversions, PluginCategory::Conversions => Category::Conversions,
@ -127,7 +127,7 @@ pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result<Signatu
// Deserializing required arguments // Deserializing required arguments
let required_list = reader let required_list = reader
.get_required_positional() .get_required_positional()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let required_positional = required_list let required_positional = required_list
.iter() .iter()
@ -137,7 +137,7 @@ pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result<Signatu
// Deserializing optional arguments // Deserializing optional arguments
let optional_list = reader let optional_list = reader
.get_optional_positional() .get_optional_positional()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let optional_positional = optional_list let optional_positional = optional_list
.iter() .iter()
@ -148,7 +148,7 @@ pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result<Signatu
let rest_positional = if reader.has_rest() { let rest_positional = if reader.has_rest() {
let argument_reader = reader let argument_reader = reader
.get_rest() .get_rest()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
Some(deserialize_argument(argument_reader)?) Some(deserialize_argument(argument_reader)?)
} else { } else {
@ -158,7 +158,7 @@ pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result<Signatu
// Deserializing named arguments // Deserializing named arguments
let named_list = reader let named_list = reader
.get_named() .get_named()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let named = named_list let named = named_list
.iter() .iter()
@ -182,15 +182,15 @@ pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result<Signatu
fn deserialize_argument(reader: argument::Reader) -> Result<PositionalArg, ShellError> { fn deserialize_argument(reader: argument::Reader) -> Result<PositionalArg, ShellError> {
let name = reader let name = reader
.get_name() .get_name()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let desc = reader let desc = reader
.get_desc() .get_desc()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let shape = reader let shape = reader
.get_shape() .get_shape()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let shape = match shape { let shape = match shape {
Shape::String => SyntaxShape::String, Shape::String => SyntaxShape::String,
@ -212,18 +212,18 @@ fn deserialize_argument(reader: argument::Reader) -> Result<PositionalArg, Shell
fn deserialize_flag(reader: flag::Reader) -> Result<Flag, ShellError> { fn deserialize_flag(reader: flag::Reader) -> Result<Flag, ShellError> {
let long = reader let long = reader
.get_long() .get_long()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let desc = reader let desc = reader
.get_desc() .get_desc()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let required = reader.get_required(); let required = reader.get_required();
let short = if reader.has_short() { let short = if reader.has_short() {
let short_reader = reader let short_reader = reader
.get_short() .get_short()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
short_reader.chars().next() short_reader.chars().next()
} else { } else {
@ -232,7 +232,7 @@ fn deserialize_flag(reader: flag::Reader) -> Result<Flag, ShellError> {
let arg = reader let arg = reader
.get_arg() .get_arg()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let arg = match arg { let arg = match arg {
Shape::None => None, Shape::None => None,
@ -270,7 +270,7 @@ mod tests {
serialize_signature(signature, builder); serialize_signature(signature, builder);
serialize::write_message(writer, &message) serialize::write_message(writer, &message)
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string())) .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))
} }
pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result<Signature, ShellError> { pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result<Signature, ShellError> {
@ -279,7 +279,7 @@ mod tests {
let reader = message_reader let reader = message_reader
.get_root::<signature::Reader>() .get_root::<signature::Reader>()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))?;
deserialize_signature(reader) deserialize_signature(reader)
} }

View file

@ -63,7 +63,7 @@ pub(crate) fn serialize_value(value: &Value, mut builder: value::Builder) {
pub(crate) fn deserialize_value(reader: value::Reader) -> Result<Value, ShellError> { pub(crate) fn deserialize_value(reader: value::Reader) -> Result<Value, ShellError> {
let span_reader = reader let span_reader = reader
.get_span() .get_span()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let span = Span { let span = Span {
start: span_reader.get_start() as usize, start: span_reader.get_start() as usize,
@ -77,26 +77,26 @@ pub(crate) fn deserialize_value(reader: value::Reader) -> Result<Value, ShellErr
Ok(value::Float(val)) => Ok(Value::Float { val, span }), Ok(value::Float(val)) => Ok(Value::Float { val, span }),
Ok(value::String(val)) => { Ok(value::String(val)) => {
let string = val let string = val
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))? .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?
.to_string(); .to_string();
Ok(Value::String { val: string, span }) Ok(Value::String { val: string, span })
} }
Ok(value::Record(record)) => { Ok(value::Record(record)) => {
let record = record.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; let record = record.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let cols = record let cols = record
.get_cols() .get_cols()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))? .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?
.iter() .iter()
.map(|col| { .map(|col| {
col.map_err(|e| ShellError::PluginFailedToLoad(e.to_string())) col.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))
.map(|col| col.to_string()) .map(|col| col.to_string())
}) })
.collect::<Result<Vec<String>, ShellError>>()?; .collect::<Result<Vec<String>, ShellError>>()?;
let vals = record let vals = record
.get_vals() .get_vals()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))? .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?
.iter() .iter()
.map(deserialize_value) .map(deserialize_value)
.collect::<Result<Vec<Value>, ShellError>>()?; .collect::<Result<Vec<Value>, ShellError>>()?;
@ -104,7 +104,7 @@ pub(crate) fn deserialize_value(reader: value::Reader) -> Result<Value, ShellErr
Ok(Value::Record { cols, vals, span }) Ok(Value::Record { cols, vals, span })
} }
Ok(value::List(vals)) => { Ok(value::List(vals)) => {
let values = vals.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; let values = vals.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
let values_list = values let values_list = values
.iter() .iter()
@ -136,7 +136,7 @@ mod tests {
serialize_value(value, builder.reborrow()); serialize_value(value, builder.reborrow());
serialize::write_message(writer, &message) serialize::write_message(writer, &message)
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string())) .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))
} }
pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result<Value, ShellError> { pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result<Value, ShellError> {
@ -145,7 +145,7 @@ mod tests {
let reader = message_reader let reader = message_reader
.get_root::<value::Reader>() .get_root::<value::Reader>()
.map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?;
deserialize_value(reader.reborrow()) deserialize_value(reader.reborrow())
} }

View file

@ -217,11 +217,17 @@ impl EngineState {
use std::io::Write; use std::io::Write;
// Updating the signatures plugin file with the added signatures // Updating the signatures plugin file with the added signatures
if let Some(plugin_path) = &self.plugin_signatures { self.plugin_signatures
// Always create the file, which will erase previous signatures .as_ref()
if let Ok(mut plugin_file) = std::fs::File::create(plugin_path.as_path()) { .ok_or_else(|| ShellError::PluginFailedToLoad("Plugin file not found".into()))
.and_then(|plugin_path| {
// Always create the file, which will erase previous signatures
std::fs::File::create(plugin_path.as_path())
.map_err(|err| ShellError::PluginFailedToLoad(err.to_string()))
})
.and_then(|mut plugin_file| {
// Plugin definitions with parsed signature // Plugin definitions with parsed signature
for decl in self.plugin_decls() { self.plugin_decls().try_for_each(|decl| {
// A successful plugin registration already includes the plugin filename // A successful plugin registration already includes the plugin filename
// No need to check the None option // No need to check the None option
let path = decl.is_plugin().expect("plugin should have file name"); let path = decl.is_plugin().expect("plugin should have file name");
@ -234,19 +240,9 @@ impl EngineState {
plugin_file plugin_file
.write_all(line.as_bytes()) .write_all(line.as_bytes())
.map_err(|err| ShellError::PluginFailedToLoad(err.to_string())) .map_err(|err| ShellError::PluginFailedToLoad(err.to_string()))
})?; })
} })
Ok(()) })
} else {
Err(ShellError::PluginFailedToLoad(
"Plugin file not found".into(),
))
}
} else {
Err(ShellError::PluginFailedToLoad(
"Plugin file not found".into(),
))
}
} }
pub fn num_files(&self) -> usize { pub fn num_files(&self) -> usize {

View file

@ -170,9 +170,17 @@ pub enum ShellError {
FileNotFoundCustom(String, #[label("{0}")] Span), FileNotFoundCustom(String, #[label("{0}")] Span),
#[error("Plugin failed to load")] #[error("Plugin failed to load")]
#[diagnostic(code(nu::shell::plugin_fialed_to_load), url(docsrs))] #[diagnostic(code(nu::shell::plugin_failed_to_load), url(docsrs))]
PluginFailedToLoad(String), PluginFailedToLoad(String),
#[error("Plugin failed to encode")]
#[diagnostic(code(nu::shell::plugin_failed_to_encode), url(docsrs))]
PluginFailedToEncode(String),
#[error("Plugin failed to decode")]
#[diagnostic(code(nu::shell::plugin_failed_to_decode), url(docsrs))]
PluginFailedToDecode(String),
#[error("I/O error")] #[error("I/O error")]
#[diagnostic(code(nu::shell::io_error), url(docsrs))] #[diagnostic(code(nu::shell::io_error), url(docsrs))]
IOError(String), IOError(String),
@ -224,12 +232,16 @@ pub enum ShellError {
NonUtf8(#[label = "non-UTF8 string"] Span), NonUtf8(#[label = "non-UTF8 string"] Span),
#[error("Casting error")] #[error("Casting error")]
#[diagnostic(code(nu::parser::downcast_not_possible), url(docsrs))] #[diagnostic(code(nu::shell::downcast_not_possible), url(docsrs))]
DowncastNotPossible(String, #[label("{0}")] Span), DowncastNotPossible(String, #[label("{0}")] Span),
#[error("{0}")] #[error("{0}")]
#[diagnostic()] #[diagnostic()]
LabeledError(String, String, #[label("{1}")] Span), SpannedLabeledError(String, String, #[label("{1}")] Span),
#[error("{0}")]
#[diagnostic()]
LabeledError(String, String),
} }
impl From<std::io::Error> for ShellError { impl From<std::io::Error> for ShellError {

View file

@ -1,5 +1,5 @@
use nu_plugin::{serve_plugin, EvaluatedCall, Plugin}; use nu_plugin::{serve_plugin, EvaluatedCall, LabeledError, Plugin};
use nu_protocol::{Category, ShellError, Signature, SyntaxShape, Value}; use nu_protocol::{Category, Signature, SyntaxShape, Value};
fn main() { fn main() {
serve_plugin(&mut Example {}) serve_plugin(&mut Example {})
@ -29,7 +29,16 @@ impl Plugin for Example {
.switch("flag", "a flag for the signature", Some('f')) .switch("flag", "a flag for the signature", Some('f'))
.optional("opt", SyntaxShape::Int, "Optional number") .optional("opt", SyntaxShape::Int, "Optional number")
.named("named", SyntaxShape::String, "named string", Some('n')) .named("named", SyntaxShape::String, "named string", Some('n'))
.rest("rest", SyntaxShape::Int, "rest value int") .rest("rest", SyntaxShape::String, "rest value string")
.category(Category::Experimental),
Signature::build("test-3")
.desc("Signature test 3 for plugin. Returns labeled error")
.required("a", SyntaxShape::Int, "required integer value")
.required("b", SyntaxShape::String, "required string value")
.switch("flag", "a flag for the signature", Some('f'))
.optional("opt", SyntaxShape::Int, "Optional number")
.named("named", SyntaxShape::String, "named string", Some('n'))
.rest("rest", SyntaxShape::String, "rest value string")
.category(Category::Experimental), .category(Category::Experimental),
] ]
} }
@ -39,25 +48,26 @@ impl Plugin for Example {
name: &str, name: &str,
call: &EvaluatedCall, call: &EvaluatedCall,
input: &Value, input: &Value,
) -> Result<Value, ShellError> { ) -> Result<Value, LabeledError> {
// You can use the name to identify what plugin signature was called // You can use the name to identify what plugin signature was called
match name { match name {
"test-1" => test1(call, input), "test-1" => test1(call, input),
"test-2" => test2(call, input), "test-2" => test2(call, input),
_ => Err(ShellError::LabeledError( "test-3" => test3(call, input),
"Plugin call with wrong name signature".into(), _ => Err(LabeledError {
"using the wrong signature".into(), label: "Plugin call with wrong name signature".into(),
call.head, msg: "using the wrong signature".into(),
)), span: Some(call.head),
}),
} }
} }
} }
fn test1(call: &EvaluatedCall, input: &Value) -> Result<Value, ShellError> { fn print_values(index: u32, call: &EvaluatedCall, input: &Value) -> Result<(), LabeledError> {
// Note. When debugging your plugin, you may want to print something to the console // Note. When debugging your plugin, you may want to print something to the console
// Use the eprintln macro to print your messages. Trying to print to stdout will // Use the eprintln macro to print your messages. Trying to print to stdout will
// cause a decoding error for your message // cause a decoding error for your message
eprintln!("Calling test1 signature"); eprintln!("Calling test {} signature", index);
eprintln!("value received {:?}", input); eprintln!("value received {:?}", input);
// To extract the arguments from the Call object you can use the functions req, has_flag, // To extract the arguments from the Call object you can use the functions req, has_flag,
@ -90,36 +100,17 @@ fn test1(call: &EvaluatedCall, input: &Value) -> Result<Value, ShellError> {
None => eprintln!("No named value found"), None => eprintln!("No named value found"),
} }
Ok(())
}
fn test1(call: &EvaluatedCall, input: &Value) -> Result<Value, LabeledError> {
print_values(1, call, input)?;
Ok(Value::Nothing { span: call.head }) Ok(Value::Nothing { span: call.head })
} }
fn test2(call: &EvaluatedCall, input: &Value) -> Result<Value, ShellError> { fn test2(call: &EvaluatedCall, input: &Value) -> Result<Value, LabeledError> {
eprintln!("Calling test1 signature"); print_values(2, call, input)?;
eprintln!("value received {:?}", input);
eprintln!("Arguments received");
let a: i64 = call.req(0)?;
let b: String = call.req(1)?;
let flag = call.has_flag("flag");
let opt: Option<i64> = call.opt(2)?;
let named: Option<String> = call.get_flag("named")?;
let rest: Vec<i64> = call.rest(3)?;
eprintln!("Required values");
eprintln!("a: {:}", a);
eprintln!("b: {:}", b);
eprintln!("flag: {:}", flag);
eprintln!("rest: {:?}", rest);
match opt {
Some(v) => eprintln!("Found optional value opt: {:}", v),
None => eprintln!("No optional value found"),
}
match named {
Some(v) => eprintln!("Named value: {:?}", v),
None => eprintln!("No named value found"),
}
let cols = vec!["one".to_string(), "two".to_string(), "three".to_string()]; let cols = vec!["one".to_string(), "two".to_string(), "three".to_string()];
@ -145,3 +136,13 @@ fn test2(call: &EvaluatedCall, input: &Value) -> Result<Value, ShellError> {
span: call.head, span: call.head,
}) })
} }
fn test3(call: &EvaluatedCall, input: &Value) -> Result<Value, LabeledError> {
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),
})
}

View file

@ -1,4 +1,5 @@
use nu_protocol::{ShellError, Span, Value}; use nu_plugin::LabeledError;
use nu_protocol::{Span, Value};
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum Action { pub enum Action {
@ -81,7 +82,7 @@ impl Inc {
"Usage: inc field [--major|--minor|--patch]" "Usage: inc field [--major|--minor|--patch]"
} }
pub fn inc(&self, head: Span, value: &Value) -> Result<Value, ShellError> { pub fn inc(&self, head: Span, value: &Value) -> Result<Value, LabeledError> {
match value { match value {
Value::Int { val, span } => Ok(Value::Int { Value::Int { val, span } => Ok(Value::Int {
val: val + 1, val: val + 1,
@ -89,19 +90,21 @@ impl Inc {
}), }),
Value::String { val, .. } => Ok(self.apply(val)), Value::String { val, .. } => Ok(self.apply(val)),
x => { x => {
if let Ok(span) = x.span() { let msg = x.as_string().map_err(|e| LabeledError {
Err(ShellError::PipelineMismatch( label: "Unable to extract string".into(),
"incrementable value".into(), msg: format!(
head, "value cannot be converted to string {:?} - {}",
span, x,
)) e.to_string()
} else { ),
Err(ShellError::LabeledError( span: Some(head),
"Expected incrementable value".into(), })?;
"incrementable value".into(),
head, Err(LabeledError {
)) label: "Incorrect value".into(),
} msg,
span: Some(head),
})
} }
} }
} }

View file

@ -1,7 +1,7 @@
use crate::inc::SemVerAction; use crate::inc::SemVerAction;
use crate::Inc; use crate::Inc;
use nu_plugin::{EvaluatedCall, Plugin}; use nu_plugin::{EvaluatedCall, LabeledError, Plugin};
use nu_protocol::{ShellError, Signature, Span, Value}; use nu_protocol::{Signature, Span, Value};
impl Plugin for Inc { impl Plugin for Inc {
fn signature(&self) -> Vec<Signature> { fn signature(&self) -> Vec<Signature> {
@ -29,7 +29,7 @@ impl Plugin for Inc {
name: &str, name: &str,
call: &EvaluatedCall, call: &EvaluatedCall,
input: &Value, input: &Value,
) -> Result<Value, ShellError> { ) -> Result<Value, LabeledError> {
if name != "inc" { if name != "inc" {
return Ok(Value::Nothing { return Ok(Value::Nothing {
span: Span::unknown(), span: Span::unknown(),