diff --git a/crates/nu-command/src/core_commands/hide.rs b/crates/nu-command/src/core_commands/hide.rs index 618acf7fe..05fa5a252 100644 --- a/crates/nu-command/src/core_commands/hide.rs +++ b/crates/nu-command/src/core_commands/hide.rs @@ -38,7 +38,7 @@ impl Command for Hide { { pat } else { - return Err(ShellError::LabeledError( + return Err(ShellError::SpannedLabeledError( "Unexpected import".into(), "import pattern not supported".into(), call.head, diff --git a/crates/nu-command/src/core_commands/use_.rs b/crates/nu-command/src/core_commands/use_.rs index 32218d122..20df8b2d9 100644 --- a/crates/nu-command/src/core_commands/use_.rs +++ b/crates/nu-command/src/core_commands/use_.rs @@ -35,7 +35,7 @@ impl Command for Use { { pat } else { - return Err(ShellError::LabeledError( + return Err(ShellError::SpannedLabeledError( "Unexpected import".into(), "import pattern not supported".into(), call.head, diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 83bb60196..011c89e67 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -83,32 +83,32 @@ impl Command for Cp { let prompt = format!( "Are you shure that you want to copy {} to {}?", file.as_ref() - .map_err(|err| ShellError::LabeledError( + .map_err(|err| ShellError::SpannedLabeledError( "Reference error".into(), err.to_string(), call.head ))? .file_name() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "File name error".into(), "Unable to get file name".into(), call.head ))? .to_str() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "Unable to get str error".into(), "Unable to convert to str file name".into(), call.head ))?, destination .file_name() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "File name error".into(), "Unable to get file name".into(), call.head ))? .to_str() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "Unable to get str error".into(), "Unable to convert to str file name".into(), call.head diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index bb3d5f08d..955d9684b 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -56,7 +56,7 @@ impl Command for Ls { let call_span = call.head; let glob = glob::glob(&pattern).map_err(|err| { - nu_protocol::ShellError::LabeledError( + nu_protocol::ShellError::SpannedLabeledError( "Error extracting glob pattern".into(), err.to_string(), call.head, diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index f24063a91..c0f2cfe97 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -67,32 +67,32 @@ impl Command for Mv { let prompt = format!( "Are you shure that you want to move {} to {}?", file.as_ref() - .map_err(|err| ShellError::LabeledError( + .map_err(|err| ShellError::SpannedLabeledError( "Reference error".into(), err.to_string(), call.head ))? .file_name() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "File name error".into(), "Unable to get file name".into(), call.head ))? .to_str() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "Unable to get str error".into(), "Unable to convert to str file name".into(), call.head ))?, destination .file_name() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "File name error".into(), "Unable to get file name".into(), call.head ))? .to_str() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "Unable to get str error".into(), "Unable to convert to str file name".into(), call.head diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 51b32a898..615948378 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -126,13 +126,13 @@ fn rm( "Are you sure that you what to delete {}?", file.1 .file_name() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "File name error".into(), "Unable to get file name".into(), call.head ))? .to_str() - .ok_or_else(|| ShellError::LabeledError( + .ok_or_else(|| ShellError::SpannedLabeledError( "Unable to get str error".into(), "Unable to convert to str file name".into(), call.head @@ -188,7 +188,7 @@ fn rm_helper(call: &Call, args: RmArgs) -> Vec { { if trash { let error = match call.get_flag_expr("trash").ok_or_else(|| { - ShellError::LabeledError( + ShellError::SpannedLabeledError( "Flag not found".into(), "trash flag not found".into(), call.head, diff --git a/crates/nu-command/src/filters/first.rs b/crates/nu-command/src/filters/first.rs index 9c9ba6180..f0d69734d 100644 --- a/crates/nu-command/src/filters/first.rs +++ b/crates/nu-command/src/filters/first.rs @@ -75,7 +75,7 @@ fn first_helper( match input_peek .peek() .ok_or_else(|| { - ShellError::LabeledError( + ShellError::SpannedLabeledError( "Error in first".into(), "unable to pick on next value".into(), call.head, diff --git a/crates/nu-command/src/platform/kill.rs b/crates/nu-command/src/platform/kill.rs index b7da74ab8..5f291a9e8 100644 --- a/crates/nu-command/src/platform/kill.rs +++ b/crates/nu-command/src/platform/kill.rs @@ -87,7 +87,7 @@ impl Command for Kill { left_span: call .get_named_arg("force") .ok_or_else(|| { - ShellError::LabeledError( + ShellError::SpannedLabeledError( "Flag error".into(), "flag force not found".into(), call.head, @@ -98,7 +98,7 @@ impl Command for Kill { right_span: span(&[ call.get_named_arg("signal") .ok_or_else(|| { - ShellError::LabeledError( + ShellError::SpannedLabeledError( "Flag error".into(), "flag signal not found".into(), call.head, diff --git a/crates/nu-command/src/viewers/icons.rs b/crates/nu-command/src/viewers/icons.rs index 5fd35c3b6..198ec2398 100644 --- a/crates/nu-command/src/viewers/icons.rs +++ b/crates/nu-command/src/viewers/icons.rs @@ -140,7 +140,7 @@ pub fn icon_for_file(file_path: &Path) -> Result { let str = file_path .file_name() .ok_or_else(|| { - ShellError::LabeledError( + ShellError::SpannedLabeledError( "File name error".into(), "Unable to get file name".into(), Span::unknown(), @@ -148,7 +148,7 @@ pub fn icon_for_file(file_path: &Path) -> Result { })? .to_str() .ok_or_else(|| { - ShellError::LabeledError( + ShellError::SpannedLabeledError( "Unable to get str error".into(), "Unable to convert to str file name".into(), Span::unknown(), @@ -164,7 +164,7 @@ pub fn icon_for_file(file_path: &Path) -> Result { Ok(icon) } else if let Some(ext) = file_path.extension().as_ref() { let str = ext.to_str().ok_or_else(|| { - ShellError::LabeledError( + ShellError::SpannedLabeledError( "Unable to get str error".into(), "Unable to convert to str file name".into(), Span::unknown(), diff --git a/crates/nu-plugin/schema/plugin.capnp b/crates/nu-plugin/schema/plugin.capnp index 3b2a23083..ffec75e72 100644 --- a/crates/nu-plugin/schema/plugin.capnp +++ b/crates/nu-plugin/schema/plugin.capnp @@ -57,7 +57,8 @@ struct Signature { extraUsage @2 :Text; requiredPositional @3 :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); isFilter @7 :Bool; category @8 :Category; @@ -81,7 +82,8 @@ enum Category { struct Flag { 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; required @3 :Bool; desc @4 :Text; @@ -113,9 +115,9 @@ struct EvaluatedCall { } struct CallInfo { - name @0: Text; - call @1: EvaluatedCall; - input @2: Value; + name @0 :Text; + call @1 :EvaluatedCall; + input @2 :Value; } # Main communication structs with the plugin @@ -128,8 +130,15 @@ struct PluginCall { struct PluginResponse { union { - error @0 :Text; + error @0 :LabeledError; signature @1 :List(Signature); value @2 :Value; } } + +struct LabeledError { + label @0 :Text; + msg @1 :Text; + # Optional Value. When decoding check if it exists (has_span) + span @2 :Span; +} diff --git a/crates/nu-plugin/src/evaluated_call.rs b/crates/nu-plugin/src/evaluated_call.rs index 70ad1478e..cfc08274d 100644 --- a/crates/nu-plugin/src/evaluated_call.rs +++ b/crates/nu-plugin/src/evaluated_call.rs @@ -5,6 +5,10 @@ use nu_protocol::{ 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)] pub struct EvaluatedCall { pub head: Span, diff --git a/crates/nu-plugin/src/lib.rs b/crates/nu-plugin/src/lib.rs index e9af55d45..04db8b604 100644 --- a/crates/nu-plugin/src/lib.rs +++ b/crates/nu-plugin/src/lib.rs @@ -1,8 +1,7 @@ pub mod evaluated_call; pub mod plugin; -pub mod plugin_call; pub mod plugin_capnp; pub mod serializers; pub use evaluated_call::EvaluatedCall; -pub use plugin::{serve_plugin, Plugin}; +pub use plugin::{serve_plugin, LabeledError, Plugin}; diff --git a/crates/nu-plugin/src/plugin.rs b/crates/nu-plugin/src/plugin.rs index 842d3961f..df816bee0 100644 --- a/crates/nu-plugin/src/plugin.rs +++ b/crates/nu-plugin/src/plugin.rs @@ -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::path::{Path, PathBuf}; use std::process::{Command as CommandSys, Stdio}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ast::Call, Signature, Value}; -use nu_protocol::{PipelineData, ShellError}; +use nu_protocol::{PipelineData, ShellError, Span}; use super::evaluated_call::EvaluatedCall; @@ -25,10 +25,73 @@ pub enum PluginCall { CallInfo(Box), } +#[derive(Clone, Debug, PartialEq)] +pub struct LabeledError { + pub label: String, + pub msg: String, + pub span: Option, +} + +impl From 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 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".into(), + msg: err.to_string(), + span: None, + }, + } + } +} + // Information received from the plugin #[derive(Debug)] pub enum PluginResponse { - Error(String), + Error(LabeledError), Signature(Vec), Value(Box), } @@ -44,21 +107,18 @@ pub fn get_signature(path: &Path) -> Result, ShellError> { // send call to plugin asking for signature if let Some(stdin_writer) = &mut child.stdin { 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 let signature = if let Some(stdout_reader) = &mut child.stdout { let reader = stdout_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 { PluginResponse::Signature(sign) => Ok(sign), - PluginResponse::Error(msg) => Err(ShellError::PluginFailedToLoad(format!( - "Plugin response error {}", - msg, - ))), + PluginResponse::Error(err) => Err(err.into()), _ => Err(ShellError::PluginFailedToLoad( "Plugin missing signature".into(), )), @@ -139,7 +199,7 @@ impl Command for PluginDeclaration { let mut child = plugin_cmd.spawn().map_err(|err| { let decl = engine_state.get_decl(call.decl_id); - ShellError::LabeledError( + ShellError::SpannedLabeledError( format!("Unable to spawn plugin for {}", decl.name()), format!("{}", err), call.head, @@ -170,9 +230,9 @@ impl Command for PluginDeclaration { 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); - ShellError::LabeledError( + ShellError::SpannedLabeledError( format!("Unable to encode call for {}", decl.name()), err.to_string(), call.head, @@ -184,9 +244,9 @@ impl Command for PluginDeclaration { let pipeline_data = if let Some(stdout_reader) = &mut child.stdout { let reader = stdout_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); - ShellError::LabeledError( + ShellError::SpannedLabeledError( format!("Unable to decode call for {}", decl.name()), err.to_string(), call.head, @@ -197,19 +257,15 @@ impl Command for PluginDeclaration { PluginResponse::Value(value) => { Ok(PipelineData::Value(value.as_ref().clone(), None)) } - PluginResponse::Error(msg) => Err(ShellError::LabeledError( - "Error received from plugin".into(), - msg, - call.head, - )), - _ => Err(ShellError::LabeledError( + PluginResponse::Error(err) => Err(err.into()), + PluginResponse::Signature(..) => Err(ShellError::SpannedLabeledError( "Plugin missing value".into(), - "No value received from plugin".into(), + "Received a signature from plugin instead of value".into(), call.head, )), } } else { - Err(ShellError::LabeledError( + Err(ShellError::SpannedLabeledError( "Error with stdout reader".into(), "no stdout reader".into(), 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 { fn signature(&self) -> Vec; - fn run(&mut self, name: &str, call: &EvaluatedCall, input: &Value) - -> Result; + fn run( + &mut self, + name: &str, + call: &EvaluatedCall, + input: &Value, + ) -> Result; } // Function used in the plugin definition for the communication protocol between // nushell and the external plugin. -// If you want to create a new plugin you have to use this function as the main -// entry point for the plugin +// When creating a new plugin you have to use this function as the main +// 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) { let mut stdin_buf = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, std::io::stdin()); let plugin_call = decode_call(&mut stdin_buf); match plugin_call { 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"); } Ok(plugin_call) => { @@ -259,7 +334,7 @@ pub fn serve_plugin(plugin: &mut impl Plugin) { let response = match 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()) .expect("Error encoding response"); diff --git a/crates/nu-plugin/src/plugin_capnp.rs b/crates/nu-plugin/src/plugin_capnp.rs index b1b9c45b0..c322e9927 100644 --- a/crates/nu-plugin/src/plugin_capnp.rs +++ b/crates/nu-plugin/src/plugin_capnp.rs @@ -3812,14 +3812,21 @@ pub mod plugin_response { self.builder.into_reader().total_size() } #[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::(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] - 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::(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 { if self.builder.get_data_field::(0) != 0 { @@ -3930,13 +3937,266 @@ pub mod plugin_response { Value(A2), } pub type WhichReader<'a> = Which< - ::capnp::Result<::capnp::text::Reader<'a>>, + ::capnp::Result>, ::capnp::Result<::capnp::struct_list::Reader<'a, crate::plugin_capnp::signature::Owned>>, ::capnp::Result>, >; pub type WhichBuilder<'a> = Which< - ::capnp::Result<::capnp::text::Builder<'a>>, + ::capnp::Result>, ::capnp::Result<::capnp::struct_list::Builder<'a, crate::plugin_capnp::signature::Owned>>, ::capnp::Result>, >; } + +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> { + ::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> { + ::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> { + ::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> { + ::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; + } +} diff --git a/crates/nu-plugin/src/serializers/call.rs b/crates/nu-plugin/src/serializers/call.rs index 20e755882..b898c3fff 100644 --- a/crates/nu-plugin/src/serializers/call.rs +++ b/crates/nu-plugin/src/serializers/call.rs @@ -38,7 +38,7 @@ fn serialize_named( entry_builder .reborrow() .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 { let value_builder = entry_builder.init_value(); @@ -54,7 +54,7 @@ pub(crate) fn deserialize_call( ) -> Result { let head_reader = reader .get_head() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let head = Span { start: head_reader.get_start() as usize, @@ -77,7 +77,7 @@ fn deserialize_positionals( ) -> Result, ShellError> { let positional_reader = reader .get_positional() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; positional_reader .iter() @@ -90,11 +90,11 @@ type NamedList = Vec<(Spanned, Option)>; fn deserialize_named(span: Span, reader: evaluated_call::Reader) -> Result { let named_reader = reader .get_named() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let entries_list = named_reader .get_entries() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let mut entries: Vec<(Spanned, Option)> = Vec::with_capacity(entries_list.len() as usize); @@ -102,16 +102,16 @@ fn deserialize_named(span: Span, reader: evaluated_call::Reader) -> Result Result { 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 .get_root::() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; match reader.which() { - Err(capnp::NotInSchema(_)) => { - Err(ShellError::PluginFailedToLoad("value not in schema".into())) - } + Err(capnp::NotInSchema(_)) => Err(ShellError::PluginFailedToDecode( + "value not in schema".into(), + )), Ok(plugin_call::Signature(())) => Ok(PluginCall::Signature), 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 .get_name() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let call_reader = reader .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 input_reader = reader .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)?; @@ -94,7 +94,17 @@ pub fn encode_response( let mut builder = message.init_root::(); 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) => { let mut signature_list_builder = builder.reborrow().init_signature(signatures.len() as u32); @@ -111,28 +121,55 @@ pub fn encode_response( }; 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 { 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 .get_root::() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; match reader.which() { - Err(capnp::NotInSchema(_)) => { - Err(ShellError::PluginFailedToLoad("value not in schema".into())) - } + Err(capnp::NotInSchema(_)) => Err(ShellError::PluginFailedToDecode( + "value not in schema".into(), + )), 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)) => { - 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 .iter() @@ -142,9 +179,9 @@ pub fn decode_response(reader: &mut impl std::io::BufRead) -> Result { - 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) - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; Ok(PluginResponse::Value(Box::new(val))) } @@ -327,8 +364,12 @@ mod tests { #[test] fn response_round_trip_error() { - let message = "some error".to_string(); - let response = PluginResponse::Error(message.clone()); + let error = LabeledError { + label: "label".into(), + msg: "msg".into(), + span: Some(Span { start: 2, end: 30 }), + }; + let response = PluginResponse::Error(error.clone()); let mut buffer: Vec = Vec::new(); 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"); match returned { - PluginResponse::Error(msg) => assert_eq!(message, msg), + PluginResponse::Error(msg) => assert_eq!(error, msg), PluginResponse::Signature(_) => panic!("returned wrong call type"), PluginResponse::Value(_) => panic!("returned wrong call type"), } diff --git a/crates/nu-plugin/src/serializers/signature.rs b/crates/nu-plugin/src/serializers/signature.rs index f42b41cf1..416da794e 100644 --- a/crates/nu-plugin/src/serializers/signature.rs +++ b/crates/nu-plugin/src/serializers/signature.rs @@ -96,18 +96,18 @@ fn serialize_flag(arg: &Flag, mut builder: flag::Builder) { pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result { let name = reader .get_name() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let usage = reader .get_usage() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let extra_usage = reader .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 category = match reader .get_category() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))? + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))? { PluginCategory::Default => Category::Default, PluginCategory::Conversions => Category::Conversions, @@ -127,7 +127,7 @@ pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result Result Result Result Result Result { let name = reader .get_name() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let desc = reader .get_desc() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let shape = reader .get_shape() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let shape = match shape { Shape::String => SyntaxShape::String, @@ -212,18 +212,18 @@ fn deserialize_argument(reader: argument::Reader) -> Result Result { let long = reader .get_long() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let desc = reader .get_desc() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let required = reader.get_required(); let short = if reader.has_short() { let short_reader = reader .get_short() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; short_reader.chars().next() } else { @@ -232,7 +232,7 @@ fn deserialize_flag(reader: flag::Reader) -> Result { let arg = reader .get_arg() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let arg = match arg { Shape::None => None, @@ -270,7 +270,7 @@ mod tests { serialize_signature(signature, builder); 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 { @@ -279,7 +279,7 @@ mod tests { let reader = message_reader .get_root::() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))?; deserialize_signature(reader) } diff --git a/crates/nu-plugin/src/serializers/value.rs b/crates/nu-plugin/src/serializers/value.rs index 861240596..9cfa5eeb5 100644 --- a/crates/nu-plugin/src/serializers/value.rs +++ b/crates/nu-plugin/src/serializers/value.rs @@ -63,7 +63,7 @@ pub(crate) fn serialize_value(value: &Value, mut builder: value::Builder) { pub(crate) fn deserialize_value(reader: value::Reader) -> Result { let span_reader = reader .get_span() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; let span = Span { start: span_reader.get_start() as usize, @@ -77,26 +77,26 @@ pub(crate) fn deserialize_value(reader: value::Reader) -> Result Ok(Value::Float { val, span }), Ok(value::String(val)) => { let string = val - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))? + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))? .to_string(); Ok(Value::String { val: string, span }) } 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 .get_cols() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))? + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))? .iter() .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()) }) .collect::, ShellError>>()?; let vals = record .get_vals() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))? + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))? .iter() .map(deserialize_value) .collect::, ShellError>>()?; @@ -104,7 +104,7 @@ pub(crate) fn deserialize_value(reader: value::Reader) -> Result { - 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 .iter() @@ -136,7 +136,7 @@ mod tests { serialize_value(value, builder.reborrow()); 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 { @@ -145,7 +145,7 @@ mod tests { let reader = message_reader .get_root::() - .map_err(|e| ShellError::PluginFailedToLoad(e.to_string()))?; + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; deserialize_value(reader.reborrow()) } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 629f263ce..fa51795c1 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -217,11 +217,17 @@ impl EngineState { use std::io::Write; // Updating the signatures plugin file with the added signatures - if let Some(plugin_path) = &self.plugin_signatures { - // Always create the file, which will erase previous signatures - if let Ok(mut plugin_file) = std::fs::File::create(plugin_path.as_path()) { + self.plugin_signatures + .as_ref() + .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 - for decl in self.plugin_decls() { + self.plugin_decls().try_for_each(|decl| { // A successful plugin registration already includes the plugin filename // No need to check the None option let path = decl.is_plugin().expect("plugin should have file name"); @@ -234,19 +240,9 @@ impl EngineState { plugin_file .write_all(line.as_bytes()) .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 { diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 3eb540bf1..2a719747d 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -170,9 +170,17 @@ pub enum ShellError { FileNotFoundCustom(String, #[label("{0}")] Span), #[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), + #[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")] #[diagnostic(code(nu::shell::io_error), url(docsrs))] IOError(String), @@ -224,12 +232,16 @@ pub enum ShellError { NonUtf8(#[label = "non-UTF8 string"] Span), #[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), #[error("{0}")] #[diagnostic()] - LabeledError(String, String, #[label("{1}")] Span), + SpannedLabeledError(String, String, #[label("{1}")] Span), + + #[error("{0}")] + #[diagnostic()] + LabeledError(String, String), } impl From for ShellError { diff --git a/crates/nu_plugin_example/src/main.rs b/crates/nu_plugin_example/src/main.rs index f4af7f7af..0259d619a 100644 --- a/crates/nu_plugin_example/src/main.rs +++ b/crates/nu_plugin_example/src/main.rs @@ -1,5 +1,5 @@ -use nu_plugin::{serve_plugin, EvaluatedCall, Plugin}; -use nu_protocol::{Category, ShellError, Signature, SyntaxShape, Value}; +use nu_plugin::{serve_plugin, EvaluatedCall, LabeledError, Plugin}; +use nu_protocol::{Category, Signature, SyntaxShape, Value}; fn main() { serve_plugin(&mut Example {}) @@ -29,7 +29,16 @@ impl Plugin for Example { .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::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), ] } @@ -39,25 +48,26 @@ impl Plugin for Example { name: &str, call: &EvaluatedCall, input: &Value, - ) -> Result { + ) -> Result { // You can use the name to identify what plugin signature was called match name { "test-1" => test1(call, input), "test-2" => test2(call, input), - _ => Err(ShellError::LabeledError( - "Plugin call with wrong name signature".into(), - "using the wrong signature".into(), - call.head, - )), + "test-3" => test3(call, input), + _ => Err(LabeledError { + label: "Plugin call with wrong name signature".into(), + msg: "using the wrong signature".into(), + span: Some(call.head), + }), } } } -fn test1(call: &EvaluatedCall, input: &Value) -> Result { +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 // Use the eprintln macro to print your messages. Trying to print to stdout will // cause a decoding error for your message - eprintln!("Calling test1 signature"); + eprintln!("Calling test {} signature", index); eprintln!("value received {:?}", input); // 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 { None => eprintln!("No named value found"), } + Ok(()) +} + +fn test1(call: &EvaluatedCall, input: &Value) -> Result { + print_values(1, call, input)?; + Ok(Value::Nothing { span: call.head }) } -fn test2(call: &EvaluatedCall, input: &Value) -> Result { - eprintln!("Calling test1 signature"); - 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 = call.opt(2)?; - let named: Option = call.get_flag("named")?; - let rest: Vec = 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"), - } +fn test2(call: &EvaluatedCall, input: &Value) -> Result { + print_values(2, call, input)?; let cols = vec!["one".to_string(), "two".to_string(), "three".to_string()]; @@ -145,3 +136,13 @@ fn test2(call: &EvaluatedCall, input: &Value) -> Result { span: call.head, }) } + +fn test3(call: &EvaluatedCall, input: &Value) -> Result { + 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), + }) +} diff --git a/crates/nu_plugin_inc/src/inc.rs b/crates/nu_plugin_inc/src/inc.rs index 7daf71b9c..9bd20b46c 100644 --- a/crates/nu_plugin_inc/src/inc.rs +++ b/crates/nu_plugin_inc/src/inc.rs @@ -1,4 +1,5 @@ -use nu_protocol::{ShellError, Span, Value}; +use nu_plugin::LabeledError; +use nu_protocol::{Span, Value}; #[derive(Debug, Eq, PartialEq)] pub enum Action { @@ -81,7 +82,7 @@ impl Inc { "Usage: inc field [--major|--minor|--patch]" } - pub fn inc(&self, head: Span, value: &Value) -> Result { + pub fn inc(&self, head: Span, value: &Value) -> Result { match value { Value::Int { val, span } => Ok(Value::Int { val: val + 1, @@ -89,19 +90,21 @@ impl Inc { }), Value::String { val, .. } => Ok(self.apply(val)), x => { - if let Ok(span) = x.span() { - Err(ShellError::PipelineMismatch( - "incrementable value".into(), - head, - span, - )) - } else { - Err(ShellError::LabeledError( - "Expected incrementable value".into(), - "incrementable value".into(), - head, - )) - } + let msg = x.as_string().map_err(|e| LabeledError { + label: "Unable to extract string".into(), + msg: format!( + "value cannot be converted to string {:?} - {}", + x, + e.to_string() + ), + span: Some(head), + })?; + + Err(LabeledError { + label: "Incorrect value".into(), + msg, + span: Some(head), + }) } } } diff --git a/crates/nu_plugin_inc/src/nu/mod.rs b/crates/nu_plugin_inc/src/nu/mod.rs index 7f74eda77..676cf0bb1 100644 --- a/crates/nu_plugin_inc/src/nu/mod.rs +++ b/crates/nu_plugin_inc/src/nu/mod.rs @@ -1,7 +1,7 @@ use crate::inc::SemVerAction; use crate::Inc; -use nu_plugin::{EvaluatedCall, Plugin}; -use nu_protocol::{ShellError, Signature, Span, Value}; +use nu_plugin::{EvaluatedCall, LabeledError, Plugin}; +use nu_protocol::{Signature, Span, Value}; impl Plugin for Inc { fn signature(&self) -> Vec { @@ -29,7 +29,7 @@ impl Plugin for Inc { name: &str, call: &EvaluatedCall, input: &Value, - ) -> Result { + ) -> Result { if name != "inc" { return Ok(Value::Nothing { span: Span::unknown(),