From daa2148136c8aa84e8c83e167de5511e18751689 Mon Sep 17 00:00:00 2001 From: Mathspy Date: Mon, 25 Jul 2022 12:32:56 -0400 Subject: [PATCH] Add CustomValue support to plugins (#6070) * Skeleton implementation Lots and lots of TODOs * Bootstrap simple CustomValue plugin support test * Create nu_plugin_custom_value * Skeleton for nu_plugin_custom_values * Return a custom value from plugin * Encode CustomValues from plugin calls as PluginResponse::PluginData * Add new PluginCall variant CollapseCustomValue * Handle CollapseCustomValue plugin calls * Add CallInput::Data variant to CallInfo inputs * Handle CallInfo with CallInput::Data plugin calls * Send CallInput::Data if Value is PluginCustomValue from plugin calls * Remove unnecessary boxing of plugins CallInfo * Add fields needed to collapse PluginCustomValue to it * Document PluginCustomValue and its purpose * Impl collapsing using plugin calls in PluginCustomValue::to_base_value * Implement proper typetag based deserialization for CoolCustomValue * Test demonstrating that passing back a custom value to plugin works * Added a failing test for describing plugin CustomValues * Support describe for PluginCustomValues - Add name to PluginResponse::PluginData - Also turn it into a struct for clarity - Add name to PluginCustomValue - Return name field from PluginCustomValue * Demonstrate that plugins can create and handle multiple CustomValues * Add bincode to nu-plugin dependencies This is for demonstration purposes, any schemaless binary seralization format will work. I picked bincode since it's the most popular for Rust but there are defintely better options out there for this usecase * serde_json::Value -> Vec * Update capnp schema for new CallInfo.input field * Move call_input capnp serialization and deserialization into new file * Deserialize Value's span from Value itself instead of passing call.head I am not sure if this was correct and I am breaking it or if it was a bug, I don't fully understand how nu creates and uses Spans. What should reuse spans and what should recreate new ones? But yeah it felt weird that the Value's Span was being ignored since in the json serializer just uses the Value's Span * Add call_info value round trip test * Add capnp CallInput::Data serialization and deserialization support * Add CallInfo::CollapseCustomValue to capnp schema * Add capnp PluginCall::CollapseCustomValue serialization and deserialization support * Add PluginResponse::PluginData to capnp schema * Add capnp PluginResponse::PluginData serialization and deserialization support * Switch plugins::custom_values tests to capnp Both json and capnp would work now! Sadly I can't choose both at the same time :( * Add missing JsonSerializer round trip tests * Handle plugin returning PluginData as a response to CollapseCustomValue * Refactor plugin calling into a reusable function Many less levels of indentation now! * Export PluginData from nu_plugin So plugins can create their very own serve_plugin with whatever CustomValue behavior they may desire * Error if CustomValue cannot be handled by Plugin --- Cargo.lock | 20 + Cargo.toml | 1 + crates/nu-plugin/Cargo.toml | 1 + crates/nu-plugin/src/lib.rs | 2 +- crates/nu-plugin/src/plugin/declaration.rs | 103 +- crates/nu-plugin/src/plugin/mod.rs | 79 +- crates/nu-plugin/src/plugin_capnp.rs | 927 +++++++++++++++++- crates/nu-plugin/src/protocol/mod.rs | 16 +- .../src/protocol/plugin_custom_value.rs | 126 +++ crates/nu-plugin/src/protocol/plugin_data.rs | 8 + .../src/serializers/capnp/call_input.rs | 113 +++ crates/nu-plugin/src/serializers/capnp/mod.rs | 4 +- .../src/serializers/capnp/plugin_call.rs | 129 ++- .../src/serializers/capnp/plugin_data.rs | 79 ++ .../src/serializers/capnp/schema/plugin.capnp | 21 +- crates/nu-plugin/src/serializers/json.rs | 85 +- crates/nu-plugin/src/serializers/mod.rs | 2 +- crates/nu_plugin_custom_values/Cargo.toml | 12 + .../src/cool_custom_value.rs | 67 ++ crates/nu_plugin_custom_values/src/main.rs | 78 ++ .../src/second_custom_value.rs | 70 ++ tests/plugins/custom_values.rs | 84 ++ tests/plugins/mod.rs | 1 + 23 files changed, 1944 insertions(+), 84 deletions(-) create mode 100644 crates/nu-plugin/src/protocol/plugin_custom_value.rs create mode 100644 crates/nu-plugin/src/protocol/plugin_data.rs create mode 100644 crates/nu-plugin/src/serializers/capnp/call_input.rs create mode 100644 crates/nu-plugin/src/serializers/capnp/plugin_data.rs create mode 100644 crates/nu_plugin_custom_values/Cargo.toml create mode 100644 crates/nu_plugin_custom_values/src/cool_custom_value.rs create mode 100644 crates/nu_plugin_custom_values/src/main.rs create mode 100644 crates/nu_plugin_custom_values/src/second_custom_value.rs create mode 100644 tests/plugins/custom_values.rs diff --git a/Cargo.lock b/Cargo.lock index 03e499e99..bcec8fb5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,6 +271,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.59.2" @@ -2720,6 +2729,7 @@ dependencies = [ name = "nu-plugin" version = "0.65.1" dependencies = [ + "bincode", "capnp", "nu-engine", "nu-protocol", @@ -2809,6 +2819,16 @@ dependencies = [ "lscolors", ] +[[package]] +name = "nu_plugin_custom_values" +version = "0.1.0" +dependencies = [ + "nu-plugin", + "nu-protocol", + "serde", + "typetag", +] + [[package]] name = "nu_plugin_example" version = "0.65.1" diff --git a/Cargo.toml b/Cargo.toml index a1f3f9cb4..0ffa80973 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ members = [ "crates/nu_plugin_gstat", "crates/nu_plugin_example", "crates/nu_plugin_query", + "crates/nu_plugin_custom_values", "crates/nu-utils", ] diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index 32c3baa51..71b137065 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -7,6 +7,7 @@ name = "nu-plugin" version = "0.65.1" [dependencies] +bincode = "1.3.3" capnp = "0.14.3" nu-protocol = { path = "../nu-protocol", version = "0.65.1" } nu-engine = { path = "../nu-engine", version = "0.65.1" } diff --git a/crates/nu-plugin/src/lib.rs b/crates/nu-plugin/src/lib.rs index de50aa1ff..e0a58ee56 100644 --- a/crates/nu-plugin/src/lib.rs +++ b/crates/nu-plugin/src/lib.rs @@ -6,5 +6,5 @@ mod serializers; mod plugin_capnp; pub use plugin::{get_signature, serve_plugin, Plugin, PluginDeclaration}; -pub use protocol::{EvaluatedCall, LabeledError}; +pub use protocol::{EvaluatedCall, LabeledError, PluginData}; pub use serializers::{capnp::CapnpSerializer, json::JsonSerializer, EncodingType}; diff --git a/crates/nu-plugin/src/plugin/declaration.rs b/crates/nu-plugin/src/plugin/declaration.rs index 6221225ab..31c52d696 100644 --- a/crates/nu-plugin/src/plugin/declaration.rs +++ b/crates/nu-plugin/src/plugin/declaration.rs @@ -1,13 +1,14 @@ use crate::{EncodingType, EvaluatedCall}; -use super::{create_command, OUTPUT_BUFFER_SIZE}; -use crate::protocol::{CallInfo, PluginCall, PluginResponse}; -use std::io::BufReader; +use super::{call_plugin, create_command}; +use crate::protocol::{ + CallInfo, CallInput, PluginCall, PluginCustomValue, PluginData, PluginResponse, +}; use std::path::{Path, PathBuf}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ast::Call, Signature}; -use nu_protocol::{PipelineData, ShellError}; +use nu_protocol::{PipelineData, ShellError, Value}; #[derive(Clone)] pub struct PluginDeclaration { @@ -73,28 +74,41 @@ impl Command for PluginDeclaration { })?; let input = input.into_value(call.head); + let input = match input { + Value::CustomValue { val, span } => { + match val.as_any().downcast_ref::() { + Some(plugin_data) if plugin_data.filename == self.filename => { + CallInput::Data(PluginData { + data: plugin_data.data.clone(), + span, + }) + } + _ => { + let custom_value_name = val.value_string(); + return Err(ShellError::GenericError( + format!( + "Plugin {} can not handle the custom value {}", + self.name, custom_value_name + ), + format!("custom value {}", custom_value_name), + Some(span), + None, + Vec::new(), + )); + } + } + } + value => CallInput::Value(value), + }; - // Create message to plugin to indicate that signature is required and - // send call to plugin asking for signature - if let Some(mut stdin_writer) = child.stdin.take() { - let encoding_clone = self.encoding.clone(); - let plugin_call = PluginCall::CallInfo(Box::new(CallInfo { - name: self.name.clone(), - call: EvaluatedCall::try_from_call(call, engine_state, stack)?, - input, - })); - std::thread::spawn(move || { - // PluginCall information - encoding_clone.encode_call(&plugin_call, &mut stdin_writer) - }); - } + let plugin_call = PluginCall::CallInfo(CallInfo { + name: self.name.clone(), + call: EvaluatedCall::try_from_call(call, engine_state, stack)?, + input, + }); - // Deserialize response from plugin to extract the resulting value - 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 = self.encoding.decode_response(&mut buf_read).map_err(|err| { + let response = + call_plugin(&mut child, plugin_call, &self.encoding, call.head).map_err(|err| { let decl = engine_state.get_decl(call.decl_id); ShellError::GenericError( format!("Unable to decode call for {}", decl.name()), @@ -105,28 +119,33 @@ impl Command for PluginDeclaration { ) }); - match response { - Ok(PluginResponse::Value(value)) => { - Ok(PipelineData::Value(value.as_ref().clone(), None)) - } - Ok(PluginResponse::Error(err)) => Err(err.into()), - Ok(PluginResponse::Signature(..)) => Err(ShellError::GenericError( - "Plugin missing value".into(), - "Received a signature from plugin instead of value".into(), - Some(call.head), - None, - Vec::new(), - )), - Err(err) => Err(err), + let pipeline_data = match response { + Ok(PluginResponse::Value(value)) => { + Ok(PipelineData::Value(value.as_ref().clone(), None)) } - } else { - Err(ShellError::GenericError( - "Error with stdout reader".into(), - "no stdout reader".into(), + Ok(PluginResponse::PluginData(name, plugin_data)) => Ok(PipelineData::Value( + Value::CustomValue { + val: Box::new(PluginCustomValue { + name, + data: plugin_data.data, + filename: self.filename.clone(), + shell: self.shell.clone(), + encoding: self.encoding.clone(), + source: engine_state.get_decl(call.decl_id).name().to_owned(), + }), + span: plugin_data.span, + }, + None, + )), + Ok(PluginResponse::Error(err)) => Err(err.into()), + Ok(PluginResponse::Signature(..)) => Err(ShellError::GenericError( + "Plugin missing value".into(), + "Received a signature from plugin instead of value".into(), Some(call.head), None, Vec::new(), - )) + )), + Err(err) => Err(err), }; // We need to call .wait() on the child, or we'll risk summoning the zombie horde diff --git a/crates/nu-plugin/src/plugin/mod.rs b/crates/nu-plugin/src/plugin/mod.rs index 91e1e1657..9c43d98a0 100644 --- a/crates/nu-plugin/src/plugin/mod.rs +++ b/crates/nu-plugin/src/plugin/mod.rs @@ -1,18 +1,18 @@ mod declaration; pub use declaration::PluginDeclaration; -use crate::protocol::{LabeledError, PluginCall, PluginResponse}; +use crate::protocol::{CallInput, LabeledError, PluginCall, PluginData, PluginResponse}; use crate::EncodingType; use std::io::BufReader; use std::path::{Path, PathBuf}; -use std::process::{Command as CommandSys, Stdio}; +use std::process::{Child, Command as CommandSys, Stdio}; -use nu_protocol::ShellError; +use nu_protocol::{CustomValue, ShellError, Span}; use nu_protocol::{Signature, Value}; use super::EvaluatedCall; -const OUTPUT_BUFFER_SIZE: usize = 8192; +pub(crate) const OUTPUT_BUFFER_SIZE: usize = 8192; pub trait PluginEncoder: Clone { fn encode_call( @@ -35,7 +35,7 @@ pub trait PluginEncoder: Clone { ) -> Result; } -fn create_command(path: &Path, shell: &Option) -> CommandSys { +pub(crate) fn create_command(path: &Path, shell: &Option) -> CommandSys { let mut process = match (path.extension(), shell) { (_, Some(shell)) => { let mut process = std::process::Command::new(shell); @@ -77,6 +77,37 @@ fn create_command(path: &Path, shell: &Option) -> CommandSys { process } +pub(crate) fn call_plugin( + child: &mut Child, + plugin_call: PluginCall, + encoding: &EncodingType, + span: Span, +) -> Result { + if let Some(mut stdin_writer) = child.stdin.take() { + let encoding_clone = encoding.clone(); + std::thread::spawn(move || { + // PluginCall information + encoding_clone.encode_call(&plugin_call, &mut stdin_writer) + }); + } + + // Deserialize response from plugin to extract the resulting value + if let Some(stdout_reader) = &mut child.stdout { + let reader = stdout_reader; + let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader); + + encoding.decode_response(&mut buf_read) + } else { + Err(ShellError::GenericError( + "Error with stdout reader".into(), + "no stdout reader".into(), + Some(span), + None, + Vec::new(), + )) + } +} + pub fn get_signature( path: &Path, encoding: &EncodingType, @@ -172,9 +203,33 @@ pub fn serve_plugin(plugin: &mut impl Plugin, encoder: impl PluginEncoder) { .expect("Error encoding response"); } PluginCall::CallInfo(call_info) => { - let value = plugin.run(&call_info.name, &call_info.call, &call_info.input); + let input = match call_info.input { + CallInput::Value(value) => Ok(value), + CallInput::Data(plugin_data) => { + bincode::deserialize::>(&plugin_data.data) + .map(|custom_value| Value::CustomValue { + val: custom_value, + span: plugin_data.span, + }) + .map_err(|err| ShellError::PluginFailedToDecode(err.to_string())) + } + }; + + let value = match input { + Ok(input) => plugin.run(&call_info.name, &call_info.call, &input), + Err(err) => Err(err.into()), + }; let response = match value { + Ok(Value::CustomValue { val, span }) => match bincode::serialize(&val) { + Ok(data) => { + let name = val.value_string(); + PluginResponse::PluginData(name, PluginData { data, span }) + } + Err(err) => PluginResponse::Error( + ShellError::PluginFailedToEncode(err.to_string()).into(), + ), + }, Ok(value) => PluginResponse::Value(Box::new(value)), Err(err) => PluginResponse::Error(err), }; @@ -182,6 +237,18 @@ pub fn serve_plugin(plugin: &mut impl Plugin, encoder: impl PluginEncoder) { .encode_response(&response, &mut std::io::stdout()) .expect("Error encoding response"); } + PluginCall::CollapseCustomValue(plugin_data) => { + let response = bincode::deserialize::>(&plugin_data.data) + .map_err(|err| ShellError::PluginFailedToDecode(err.to_string())) + .and_then(|val| val.to_base_value(plugin_data.span)) + .map(Box::new) + .map_err(LabeledError::from) + .map_or_else(PluginResponse::Error, PluginResponse::Value); + + encoder + .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 9e06c11ca..4598cef02 100644 --- a/crates/nu-plugin/src/plugin_capnp.rs +++ b/crates/nu-plugin/src/plugin_capnp.rs @@ -105,12 +105,14 @@ pub mod err { pub fn total_size(&self) -> ::capnp::Result<::capnp::MessageSize> { self.reader.total_size() } + #[inline] pub fn has_err(&self) -> bool { if self.reader.get_data_field::(0) != 0 { return false; } !self.reader.get_pointer_field(0).is_null() } + #[inline] pub fn has_ok(&self) -> bool { if self.reader.get_data_field::(0) != 1 { return false; @@ -244,6 +246,7 @@ pub mod err { self.builder.set_data_field::(0, 0); self.builder.get_pointer_field(0).init_text(size) } + #[inline] pub fn has_err(&self) -> bool { if self.builder.get_data_field::(0) != 0 { return false; @@ -272,6 +275,7 @@ pub mod err { self.builder.set_data_field::(0, 1); ::capnp::any_pointer::Builder::new(self.builder.get_pointer_field(0)).init_as() } + #[inline] pub fn has_ok(&self) -> bool { if self.builder.get_data_field::(0) != 1 { return false; @@ -460,6 +464,7 @@ pub mod map { ::core::option::Option::None, ) } + #[inline] pub fn has_entries(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } @@ -606,6 +611,7 @@ pub mod map { size, ) } + #[inline] pub fn has_entries(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -758,6 +764,7 @@ pub mod map { ::core::option::Option::None, ) } + #[inline] pub fn has_key(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } @@ -770,6 +777,7 @@ pub mod map { ::core::option::Option::None, ) } + #[inline] pub fn has_value(&self) -> bool { !self.reader.get_pointer_field(1).is_null() } @@ -909,6 +917,7 @@ pub mod map { pub fn init_key(self) -> >::Builder { ::capnp::any_pointer::Builder::new(self.builder.get_pointer_field(0)).init_as() } + #[inline] pub fn has_key(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -944,6 +953,7 @@ pub mod map { pub fn init_value(self) -> >::Builder { ::capnp::any_pointer::Builder::new(self.builder.get_pointer_field(1)).init_as() } + #[inline] pub fn has_value(&self) -> bool { !self.builder.get_pointer_field(1).is_null() } @@ -1243,21 +1253,25 @@ pub mod value { ::core::option::Option::None, ) } + #[inline] pub fn has_span(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } + #[inline] pub fn has_string(&self) -> bool { if self.reader.get_data_field::(0) != 4 { return false; } !self.reader.get_pointer_field(1).is_null() } + #[inline] pub fn has_list(&self) -> bool { if self.reader.get_data_field::(0) != 5 { return false; } !self.reader.get_pointer_field(1).is_null() } + #[inline] pub fn has_record(&self) -> bool { if self.reader.get_data_field::(0) != 6 { return false; @@ -1385,6 +1399,7 @@ pub mod value { pub fn init_span(self) -> crate::plugin_capnp::span::Builder<'a> { ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) } + #[inline] pub fn has_span(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -1417,6 +1432,7 @@ pub mod value { self.builder.set_data_field::(0, 4); self.builder.get_pointer_field(1).init_text(size) } + #[inline] pub fn has_string(&self) -> bool { if self.builder.get_data_field::(0) != 4 { return false; @@ -1446,6 +1462,7 @@ pub mod value { size, ) } + #[inline] pub fn has_list(&self) -> bool { if self.builder.get_data_field::(0) != 5 { return false; @@ -1469,6 +1486,7 @@ pub mod value { self.builder.set_data_field::(0, 6); ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(1), 0) } + #[inline] pub fn has_record(&self) -> bool { if self.builder.get_data_field::(0) != 6 { return false; @@ -1620,6 +1638,7 @@ pub mod record { ::core::option::Option::None, ) } + #[inline] pub fn has_cols(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } @@ -1633,6 +1652,7 @@ pub mod record { ::core::option::Option::None, ) } + #[inline] pub fn has_vals(&self) -> bool { !self.reader.get_pointer_field(1).is_null() } @@ -1729,6 +1749,7 @@ pub mod record { size, ) } + #[inline] pub fn has_cols(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -1763,6 +1784,7 @@ pub mod record { size, ) } + #[inline] pub fn has_vals(&self) -> bool { !self.builder.get_pointer_field(1).is_null() } @@ -1860,6 +1882,7 @@ pub mod signature { ::core::option::Option::None, ) } + #[inline] pub fn has_name(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } @@ -1870,6 +1893,7 @@ pub mod signature { ::core::option::Option::None, ) } + #[inline] pub fn has_usage(&self) -> bool { !self.reader.get_pointer_field(1).is_null() } @@ -1880,6 +1904,7 @@ pub mod signature { ::core::option::Option::None, ) } + #[inline] pub fn has_extra_usage(&self) -> bool { !self.reader.get_pointer_field(2).is_null() } @@ -1890,6 +1915,7 @@ pub mod signature { ::core::option::Option::None, ) } + #[inline] pub fn has_search_terms(&self) -> bool { !self.reader.get_pointer_field(3).is_null() } @@ -1903,6 +1929,7 @@ pub mod signature { ::core::option::Option::None, ) } + #[inline] pub fn has_required_positional(&self) -> bool { !self.reader.get_pointer_field(4).is_null() } @@ -1916,6 +1943,7 @@ pub mod signature { ::core::option::Option::None, ) } + #[inline] pub fn has_optional_positional(&self) -> bool { !self.reader.get_pointer_field(5).is_null() } @@ -1926,6 +1954,7 @@ pub mod signature { ::core::option::Option::None, ) } + #[inline] pub fn has_rest(&self) -> bool { !self.reader.get_pointer_field(6).is_null() } @@ -1939,6 +1968,7 @@ pub mod signature { ::core::option::Option::None, ) } + #[inline] pub fn has_named(&self) -> bool { !self.reader.get_pointer_field(7).is_null() } @@ -2038,6 +2068,7 @@ pub mod signature { pub fn init_name(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(0).init_text(size) } + #[inline] pub fn has_name(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -2056,6 +2087,7 @@ pub mod signature { pub fn init_usage(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(1).init_text(size) } + #[inline] pub fn has_usage(&self) -> bool { !self.builder.get_pointer_field(1).is_null() } @@ -2074,6 +2106,7 @@ pub mod signature { pub fn init_extra_usage(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(2).init_text(size) } + #[inline] pub fn has_extra_usage(&self) -> bool { !self.builder.get_pointer_field(2).is_null() } @@ -2102,6 +2135,7 @@ pub mod signature { size, ) } + #[inline] pub fn has_search_terms(&self) -> bool { !self.builder.get_pointer_field(3).is_null() } @@ -2136,6 +2170,7 @@ pub mod signature { size, ) } + #[inline] pub fn has_required_positional(&self) -> bool { !self.builder.get_pointer_field(4).is_null() } @@ -2170,6 +2205,7 @@ pub mod signature { size, ) } + #[inline] pub fn has_optional_positional(&self) -> bool { !self.builder.get_pointer_field(5).is_null() } @@ -2195,6 +2231,7 @@ pub mod signature { pub fn init_rest(self) -> crate::plugin_capnp::argument::Builder<'a> { ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(6), 0) } + #[inline] pub fn has_rest(&self) -> bool { !self.builder.get_pointer_field(6).is_null() } @@ -2229,6 +2266,7 @@ pub mod signature { size, ) } + #[inline] pub fn has_named(&self) -> bool { !self.builder.get_pointer_field(7).is_null() } @@ -2411,6 +2449,7 @@ pub mod flag { ::core::option::Option::None, ) } + #[inline] pub fn has_long(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } @@ -2421,6 +2460,7 @@ pub mod flag { ::core::option::Option::None, ) } + #[inline] pub fn has_short(&self) -> bool { !self.reader.get_pointer_field(1).is_null() } @@ -2441,6 +2481,7 @@ pub mod flag { ::core::option::Option::None, ) } + #[inline] pub fn has_desc(&self) -> bool { !self.reader.get_pointer_field(2).is_null() } @@ -2530,6 +2571,7 @@ pub mod flag { pub fn init_long(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(0).init_text(size) } + #[inline] pub fn has_long(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -2548,6 +2590,7 @@ pub mod flag { pub fn init_short(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(1).init_text(size) } + #[inline] pub fn has_short(&self) -> bool { !self.builder.get_pointer_field(1).is_null() } @@ -2584,6 +2627,7 @@ pub mod flag { pub fn init_desc(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(2).init_text(size) } + #[inline] pub fn has_desc(&self) -> bool { !self.builder.get_pointer_field(2).is_null() } @@ -2681,6 +2725,7 @@ pub mod argument { ::core::option::Option::None, ) } + #[inline] pub fn has_name(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } @@ -2691,6 +2736,7 @@ pub mod argument { ::core::option::Option::None, ) } + #[inline] pub fn has_desc(&self) -> bool { !self.reader.get_pointer_field(1).is_null() } @@ -2786,6 +2832,7 @@ pub mod argument { pub fn init_name(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(0).init_text(size) } + #[inline] pub fn has_name(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -2804,6 +2851,7 @@ pub mod argument { pub fn init_desc(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(1).init_text(size) } + #[inline] pub fn has_desc(&self) -> bool { !self.builder.get_pointer_field(1).is_null() } @@ -2948,6 +2996,7 @@ pub mod evaluated_call { ::core::option::Option::None, ) } + #[inline] pub fn has_head(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } @@ -2961,6 +3010,7 @@ pub mod evaluated_call { ::core::option::Option::None, ) } + #[inline] pub fn has_positional(&self) -> bool { !self.reader.get_pointer_field(1).is_null() } @@ -2979,6 +3029,7 @@ pub mod evaluated_call { ::core::option::Option::None, ) } + #[inline] pub fn has_named(&self) -> bool { !self.reader.get_pointer_field(2).is_null() } @@ -3075,6 +3126,7 @@ pub mod evaluated_call { pub fn init_head(self) -> crate::plugin_capnp::span::Builder<'a> { ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) } + #[inline] pub fn has_head(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -3109,6 +3161,7 @@ pub mod evaluated_call { size, ) } + #[inline] pub fn has_positional(&self) -> bool { !self.builder.get_pointer_field(1).is_null() } @@ -3156,6 +3209,7 @@ pub mod evaluated_call { > { ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(2), 0) } + #[inline] pub fn has_named(&self) -> bool { !self.builder.get_pointer_field(2).is_null() } @@ -3194,6 +3248,505 @@ pub mod evaluated_call { } } +pub mod plugin_data { + #[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_data(self) -> ::capnp::Result<::capnp::data::Reader<'a>> { + ::capnp::traits::FromPointerReader::get_from_pointer( + &self.reader.get_pointer_field(0), + ::core::option::Option::None, + ) + } + #[inline] + pub fn has_data(&self) -> bool { + !self.reader.get_pointer_field(0).is_null() + } + #[inline] + pub fn get_span(self) -> ::capnp::Result> { + ::capnp::traits::FromPointerReader::get_from_pointer( + &self.reader.get_pointer_field(1), + ::core::option::Option::None, + ) + } + #[inline] + pub fn has_span(&self) -> bool { + !self.reader.get_pointer_field(1).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_data(self) -> ::capnp::Result<::capnp::data::Builder<'a>> { + ::capnp::traits::FromPointerBuilder::get_from_pointer( + self.builder.get_pointer_field(0), + ::core::option::Option::None, + ) + } + #[inline] + pub fn set_data(&mut self, value: ::capnp::data::Reader<'_>) { + self.builder.get_pointer_field(0).set_data(value); + } + #[inline] + pub fn init_data(self, size: u32) -> ::capnp::data::Builder<'a> { + self.builder.get_pointer_field(0).init_data(size) + } + #[inline] + pub fn has_data(&self) -> bool { + !self.builder.get_pointer_field(0).is_null() + } + #[inline] + pub fn get_span(self) -> ::capnp::Result> { + ::capnp::traits::FromPointerBuilder::get_from_pointer( + self.builder.get_pointer_field(1), + ::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(1), + value, + false, + ) + } + #[inline] + pub fn init_span(self) -> crate::plugin_capnp::span::Builder<'a> { + ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(1), 0) + } + #[inline] + pub fn has_span(&self) -> bool { + !self.builder.get_pointer_field(1).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(1)) + } + } + mod _private { + use capnp::private::layout; + pub const STRUCT_SIZE: layout::StructSize = layout::StructSize { + data: 0, + pointers: 2, + }; + pub const TYPE_ID: u64 = 0xe394_537e_5286_0449; + } +} + +pub mod call_input { + pub use self::Which::{PluginData, Value}; + + #[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 has_value(&self) -> bool { + if self.reader.get_data_field::(0) != 0 { + return false; + } + !self.reader.get_pointer_field(0).is_null() + } + #[inline] + pub fn has_plugin_data(&self) -> bool { + if self.reader.get_data_field::(0) != 1 { + return false; + } + !self.reader.get_pointer_field(0).is_null() + } + #[inline] + pub fn which(self) -> ::core::result::Result, ::capnp::NotInSchema> { + match self.reader.get_data_field::(0) { + 0 => ::core::result::Result::Ok(Value( + ::capnp::traits::FromPointerReader::get_from_pointer( + &self.reader.get_pointer_field(0), + ::core::option::Option::None, + ), + )), + 1 => ::core::result::Result::Ok(PluginData( + ::capnp::traits::FromPointerReader::get_from_pointer( + &self.reader.get_pointer_field(0), + ::core::option::Option::None, + ), + )), + x => ::core::result::Result::Err(::capnp::NotInSchema(x)), + } + } + } + + 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 set_value( + &mut self, + value: crate::plugin_capnp::value::Reader<'_>, + ) -> ::capnp::Result<()> { + self.builder.set_data_field::(0, 0); + ::capnp::traits::SetPointerBuilder::set_pointer_builder( + self.builder.get_pointer_field(0), + value, + false, + ) + } + #[inline] + pub fn init_value(self) -> crate::plugin_capnp::value::Builder<'a> { + self.builder.set_data_field::(0, 0); + ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) + } + #[inline] + pub fn has_value(&self) -> bool { + if self.builder.get_data_field::(0) != 0 { + return false; + } + !self.builder.get_pointer_field(0).is_null() + } + #[inline] + pub fn set_plugin_data( + &mut self, + value: crate::plugin_capnp::plugin_data::Reader<'_>, + ) -> ::capnp::Result<()> { + self.builder.set_data_field::(0, 1); + ::capnp::traits::SetPointerBuilder::set_pointer_builder( + self.builder.get_pointer_field(0), + value, + false, + ) + } + #[inline] + pub fn init_plugin_data(self) -> crate::plugin_capnp::plugin_data::Builder<'a> { + self.builder.set_data_field::(0, 1); + ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) + } + #[inline] + pub fn has_plugin_data(&self) -> bool { + if self.builder.get_data_field::(0) != 1 { + return false; + } + !self.builder.get_pointer_field(0).is_null() + } + #[inline] + pub fn which(self) -> ::core::result::Result, ::capnp::NotInSchema> { + match self.builder.get_data_field::(0) { + 0 => ::core::result::Result::Ok(Value( + ::capnp::traits::FromPointerBuilder::get_from_pointer( + self.builder.get_pointer_field(0), + ::core::option::Option::None, + ), + )), + 1 => ::core::result::Result::Ok(PluginData( + ::capnp::traits::FromPointerBuilder::get_from_pointer( + self.builder.get_pointer_field(0), + ::core::option::Option::None, + ), + )), + x => ::core::result::Result::Err(::capnp::NotInSchema(x)), + } + } + } + + 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 {} + mod _private { + use capnp::private::layout; + pub const STRUCT_SIZE: layout::StructSize = layout::StructSize { + data: 1, + pointers: 1, + }; + pub const TYPE_ID: u64 = 0xe0a9_a296_3cb2_e15f; + } + pub enum Which { + Value(A0), + PluginData(A1), + } + pub type WhichReader<'a> = Which< + ::capnp::Result>, + ::capnp::Result>, + >; + pub type WhichBuilder<'a> = Which< + ::capnp::Result>, + ::capnp::Result>, + >; +} + pub mod call_info { #[derive(Copy, Clone)] pub struct Owned(()); @@ -3265,6 +3818,7 @@ pub mod call_info { ::core::option::Option::None, ) } + #[inline] pub fn has_name(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } @@ -3275,16 +3829,18 @@ pub mod call_info { ::core::option::Option::None, ) } + #[inline] pub fn has_call(&self) -> bool { !self.reader.get_pointer_field(1).is_null() } #[inline] - pub fn get_input(self) -> ::capnp::Result> { + pub fn get_input(self) -> ::capnp::Result> { ::capnp::traits::FromPointerReader::get_from_pointer( &self.reader.get_pointer_field(2), ::core::option::Option::None, ) } + #[inline] pub fn has_input(&self) -> bool { !self.reader.get_pointer_field(2).is_null() } @@ -3374,6 +3930,7 @@ pub mod call_info { pub fn init_name(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(0).init_text(size) } + #[inline] pub fn has_name(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -3399,11 +3956,12 @@ pub mod call_info { pub fn init_call(self) -> crate::plugin_capnp::evaluated_call::Builder<'a> { ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(1), 0) } + #[inline] pub fn has_call(&self) -> bool { !self.builder.get_pointer_field(1).is_null() } #[inline] - pub fn get_input(self) -> ::capnp::Result> { + pub fn get_input(self) -> ::capnp::Result> { ::capnp::traits::FromPointerBuilder::get_from_pointer( self.builder.get_pointer_field(2), ::core::option::Option::None, @@ -3412,7 +3970,7 @@ pub mod call_info { #[inline] pub fn set_input( &mut self, - value: crate::plugin_capnp::value::Reader<'_>, + value: crate::plugin_capnp::call_input::Reader<'_>, ) -> ::capnp::Result<()> { ::capnp::traits::SetPointerBuilder::set_pointer_builder( self.builder.get_pointer_field(2), @@ -3421,9 +3979,10 @@ pub mod call_info { ) } #[inline] - pub fn init_input(self) -> crate::plugin_capnp::value::Builder<'a> { + pub fn init_input(self) -> crate::plugin_capnp::call_input::Builder<'a> { ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(2), 0) } + #[inline] pub fn has_input(&self) -> bool { !self.builder.get_pointer_field(2).is_null() } @@ -3443,7 +4002,7 @@ pub mod call_info { pub fn get_call(&self) -> crate::plugin_capnp::evaluated_call::Pipeline { ::capnp::capability::FromTypelessPipeline::new(self._typeless.get_pointer_field(1)) } - pub fn get_input(&self) -> crate::plugin_capnp::value::Pipeline { + pub fn get_input(&self) -> crate::plugin_capnp::call_input::Pipeline { ::capnp::capability::FromTypelessPipeline::new(self._typeless.get_pointer_field(2)) } } @@ -3458,7 +4017,7 @@ pub mod call_info { } pub mod plugin_call { - pub use self::Which::{CallInfo, Signature}; + pub use self::Which::{CallInfo, CollapseCustomValue, Signature}; #[derive(Copy, Clone)] pub struct Owned(()); @@ -3523,6 +4082,7 @@ pub mod plugin_call { pub fn total_size(&self) -> ::capnp::Result<::capnp::MessageSize> { self.reader.total_size() } + #[inline] pub fn has_call_info(&self) -> bool { if self.reader.get_data_field::(0) != 1 { return false; @@ -3530,6 +4090,13 @@ pub mod plugin_call { !self.reader.get_pointer_field(0).is_null() } #[inline] + pub fn has_collapse_custom_value(&self) -> bool { + if self.reader.get_data_field::(0) != 2 { + return false; + } + !self.reader.get_pointer_field(0).is_null() + } + #[inline] pub fn which(self) -> ::core::result::Result, ::capnp::NotInSchema> { match self.reader.get_data_field::(0) { 0 => ::core::result::Result::Ok(Signature(())), @@ -3539,6 +4106,12 @@ pub mod plugin_call { ::core::option::Option::None, ), )), + 2 => ::core::result::Result::Ok(CollapseCustomValue( + ::capnp::traits::FromPointerReader::get_from_pointer( + &self.reader.get_pointer_field(0), + ::core::option::Option::None, + ), + )), x => ::core::result::Result::Err(::capnp::NotInSchema(x)), } } @@ -3634,6 +4207,7 @@ pub mod plugin_call { self.builder.set_data_field::(0, 1); ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) } + #[inline] pub fn has_call_info(&self) -> bool { if self.builder.get_data_field::(0) != 1 { return false; @@ -3641,6 +4215,30 @@ pub mod plugin_call { !self.builder.get_pointer_field(0).is_null() } #[inline] + pub fn set_collapse_custom_value( + &mut self, + value: crate::plugin_capnp::plugin_data::Reader<'_>, + ) -> ::capnp::Result<()> { + self.builder.set_data_field::(0, 2); + ::capnp::traits::SetPointerBuilder::set_pointer_builder( + self.builder.get_pointer_field(0), + value, + false, + ) + } + #[inline] + pub fn init_collapse_custom_value(self) -> crate::plugin_capnp::plugin_data::Builder<'a> { + self.builder.set_data_field::(0, 2); + ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) + } + #[inline] + pub fn has_collapse_custom_value(&self) -> bool { + if self.builder.get_data_field::(0) != 2 { + return false; + } + !self.builder.get_pointer_field(0).is_null() + } + #[inline] pub fn which(self) -> ::core::result::Result, ::capnp::NotInSchema> { match self.builder.get_data_field::(0) { 0 => ::core::result::Result::Ok(Signature(())), @@ -3650,6 +4248,12 @@ pub mod plugin_call { ::core::option::Option::None, ), )), + 2 => ::core::result::Result::Ok(CollapseCustomValue( + ::capnp::traits::FromPointerBuilder::get_from_pointer( + self.builder.get_pointer_field(0), + ::core::option::Option::None, + ), + )), x => ::core::result::Result::Err(::capnp::NotInSchema(x)), } } @@ -3674,16 +4278,23 @@ pub mod plugin_call { }; pub const TYPE_ID: u64 = 0xde86_64b2_7f80_4db1; } - pub enum Which { + pub enum Which { Signature(()), CallInfo(A0), + CollapseCustomValue(A1), } - pub type WhichReader<'a> = Which<::capnp::Result>>; - pub type WhichBuilder<'a> = Which<::capnp::Result>>; + pub type WhichReader<'a> = Which< + ::capnp::Result>, + ::capnp::Result>, + >; + pub type WhichBuilder<'a> = Which< + ::capnp::Result>, + ::capnp::Result>, + >; } pub mod plugin_response { - pub use self::Which::{Error, Signature, Value}; + pub use self::Which::{Error, PluginData, Signature, Value}; #[derive(Copy, Clone)] pub struct Owned(()); @@ -3748,18 +4359,21 @@ pub mod plugin_response { pub fn total_size(&self) -> ::capnp::Result<::capnp::MessageSize> { self.reader.total_size() } + #[inline] pub fn has_error(&self) -> bool { if self.reader.get_data_field::(0) != 0 { return false; } !self.reader.get_pointer_field(0).is_null() } + #[inline] pub fn has_signature(&self) -> bool { if self.reader.get_data_field::(0) != 1 { return false; } !self.reader.get_pointer_field(0).is_null() } + #[inline] pub fn has_value(&self) -> bool { if self.reader.get_data_field::(0) != 2 { return false; @@ -3767,6 +4381,13 @@ pub mod plugin_response { !self.reader.get_pointer_field(0).is_null() } #[inline] + pub fn has_plugin_data(&self) -> bool { + if self.reader.get_data_field::(0) != 3 { + return false; + } + !self.reader.get_pointer_field(0).is_null() + } + #[inline] pub fn which(self) -> ::core::result::Result, ::capnp::NotInSchema> { match self.reader.get_data_field::(0) { 0 => ::core::result::Result::Ok(Error( @@ -3787,6 +4408,12 @@ pub mod plugin_response { ::core::option::Option::None, ), )), + 3 => ::core::result::Result::Ok(PluginData( + ::capnp::traits::FromPointerReader::get_from_pointer( + &self.reader.get_pointer_field(0), + ::core::option::Option::None, + ), + )), x => ::core::result::Result::Err(::capnp::NotInSchema(x)), } } @@ -3878,6 +4505,7 @@ pub mod plugin_response { self.builder.set_data_field::(0, 0); ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) } + #[inline] pub fn has_error(&self) -> bool { if self.builder.get_data_field::(0) != 0 { return false; @@ -3907,6 +4535,7 @@ pub mod plugin_response { size, ) } + #[inline] pub fn has_signature(&self) -> bool { if self.builder.get_data_field::(0) != 1 { return false; @@ -3930,6 +4559,7 @@ pub mod plugin_response { self.builder.set_data_field::(0, 2); ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) } + #[inline] pub fn has_value(&self) -> bool { if self.builder.get_data_field::(0) != 2 { return false; @@ -3937,6 +4567,32 @@ pub mod plugin_response { !self.builder.get_pointer_field(0).is_null() } #[inline] + pub fn set_plugin_data( + &mut self, + value: crate::plugin_capnp::plugin_response::plugin_data_response::Reader<'_>, + ) -> ::capnp::Result<()> { + self.builder.set_data_field::(0, 3); + ::capnp::traits::SetPointerBuilder::set_pointer_builder( + self.builder.get_pointer_field(0), + value, + false, + ) + } + #[inline] + pub fn init_plugin_data( + self, + ) -> crate::plugin_capnp::plugin_response::plugin_data_response::Builder<'a> { + self.builder.set_data_field::(0, 3); + ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) + } + #[inline] + pub fn has_plugin_data(&self) -> bool { + if self.builder.get_data_field::(0) != 3 { + return false; + } + !self.builder.get_pointer_field(0).is_null() + } + #[inline] pub fn which(self) -> ::core::result::Result, ::capnp::NotInSchema> { match self.builder.get_data_field::(0) { 0 => ::core::result::Result::Ok(Error( @@ -3957,6 +4613,12 @@ pub mod plugin_response { ::core::option::Option::None, ), )), + 3 => ::core::result::Result::Ok(PluginData( + ::capnp::traits::FromPointerBuilder::get_from_pointer( + self.builder.get_pointer_field(0), + ::core::option::Option::None, + ), + )), x => ::core::result::Result::Err(::capnp::NotInSchema(x)), } } @@ -3981,21 +4643,258 @@ pub mod plugin_response { }; pub const TYPE_ID: u64 = 0xb9ba_b3c7_9388_b7db; } - pub enum Which { + pub enum Which { Error(A0), Signature(A1), Value(A2), + PluginData(A3), } pub type WhichReader<'a> = Which< ::capnp::Result>, ::capnp::Result<::capnp::struct_list::Reader<'a, crate::plugin_capnp::signature::Owned>>, ::capnp::Result>, + ::capnp::Result>, >; pub type WhichBuilder<'a> = Which< ::capnp::Result>, ::capnp::Result<::capnp::struct_list::Builder<'a, crate::plugin_capnp::signature::Owned>>, ::capnp::Result>, + ::capnp::Result>, >; + + pub mod plugin_data_response { + #[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_name(self) -> ::capnp::Result<::capnp::text::Reader<'a>> { + ::capnp::traits::FromPointerReader::get_from_pointer( + &self.reader.get_pointer_field(0), + ::core::option::Option::None, + ) + } + #[inline] + pub fn has_name(&self) -> bool { + !self.reader.get_pointer_field(0).is_null() + } + #[inline] + pub fn get_data(self) -> ::capnp::Result> { + ::capnp::traits::FromPointerReader::get_from_pointer( + &self.reader.get_pointer_field(1), + ::core::option::Option::None, + ) + } + #[inline] + pub fn has_data(&self) -> bool { + !self.reader.get_pointer_field(1).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_name(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_name(&mut self, value: ::capnp::text::Reader<'_>) { + self.builder.get_pointer_field(0).set_text(value); + } + #[inline] + pub fn init_name(self, size: u32) -> ::capnp::text::Builder<'a> { + self.builder.get_pointer_field(0).init_text(size) + } + #[inline] + pub fn has_name(&self) -> bool { + !self.builder.get_pointer_field(0).is_null() + } + #[inline] + pub fn get_data( + self, + ) -> ::capnp::Result> { + ::capnp::traits::FromPointerBuilder::get_from_pointer( + self.builder.get_pointer_field(1), + ::core::option::Option::None, + ) + } + #[inline] + pub fn set_data( + &mut self, + value: crate::plugin_capnp::plugin_data::Reader<'_>, + ) -> ::capnp::Result<()> { + ::capnp::traits::SetPointerBuilder::set_pointer_builder( + self.builder.get_pointer_field(1), + value, + false, + ) + } + #[inline] + pub fn init_data(self) -> crate::plugin_capnp::plugin_data::Builder<'a> { + ::capnp::traits::FromPointerBuilder::init_pointer( + self.builder.get_pointer_field(1), + 0, + ) + } + #[inline] + pub fn has_data(&self) -> bool { + !self.builder.get_pointer_field(1).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_data(&self) -> crate::plugin_capnp::plugin_data::Pipeline { + ::capnp::capability::FromTypelessPipeline::new(self._typeless.get_pointer_field(1)) + } + } + mod _private { + use capnp::private::layout; + pub const STRUCT_SIZE: layout::StructSize = layout::StructSize { + data: 0, + pointers: 2, + }; + pub const TYPE_ID: u64 = 0xa0e9_dd1c_d7fe_17a7; + } + } } pub mod labeled_error { @@ -4069,6 +4968,7 @@ pub mod labeled_error { ::core::option::Option::None, ) } + #[inline] pub fn has_label(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } @@ -4079,6 +4979,7 @@ pub mod labeled_error { ::core::option::Option::None, ) } + #[inline] pub fn has_msg(&self) -> bool { !self.reader.get_pointer_field(1).is_null() } @@ -4089,6 +4990,7 @@ pub mod labeled_error { ::core::option::Option::None, ) } + #[inline] pub fn has_span(&self) -> bool { !self.reader.get_pointer_field(2).is_null() } @@ -4178,6 +5080,7 @@ pub mod labeled_error { pub fn init_label(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(0).init_text(size) } + #[inline] pub fn has_label(&self) -> bool { !self.builder.get_pointer_field(0).is_null() } @@ -4196,6 +5099,7 @@ pub mod labeled_error { pub fn init_msg(self, size: u32) -> ::capnp::text::Builder<'a> { self.builder.get_pointer_field(1).init_text(size) } + #[inline] pub fn has_msg(&self) -> bool { !self.builder.get_pointer_field(1).is_null() } @@ -4221,6 +5125,7 @@ pub mod labeled_error { pub fn init_span(self) -> crate::plugin_capnp::span::Builder<'a> { ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(2), 0) } + #[inline] pub fn has_span(&self) -> bool { !self.builder.get_pointer_field(2).is_null() } diff --git a/crates/nu-plugin/src/protocol/mod.rs b/crates/nu-plugin/src/protocol/mod.rs index 4eebcd947..e80e13e7c 100644 --- a/crates/nu-plugin/src/protocol/mod.rs +++ b/crates/nu-plugin/src/protocol/mod.rs @@ -1,21 +1,32 @@ mod evaluated_call; +mod plugin_custom_value; +mod plugin_data; pub use evaluated_call::EvaluatedCall; use nu_protocol::{ShellError, Signature, Span, Value}; +pub use plugin_custom_value::PluginCustomValue; +pub use plugin_data::PluginData; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] pub struct CallInfo { pub name: String, pub call: EvaluatedCall, - pub input: Value, + pub input: CallInput, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub enum CallInput { + Value(Value), + Data(PluginData), } // Information sent to the plugin #[derive(Serialize, Deserialize, Debug)] pub enum PluginCall { Signature, - CallInfo(Box), + CallInfo(CallInfo), + CollapseCustomValue(PluginData), } #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] @@ -88,4 +99,5 @@ pub enum PluginResponse { Error(LabeledError), Signature(Vec), Value(Box), + PluginData(String, PluginData), } diff --git a/crates/nu-plugin/src/protocol/plugin_custom_value.rs b/crates/nu-plugin/src/protocol/plugin_custom_value.rs new file mode 100644 index 000000000..a85be449e --- /dev/null +++ b/crates/nu-plugin/src/protocol/plugin_custom_value.rs @@ -0,0 +1,126 @@ +use std::path::PathBuf; + +use nu_protocol::{CustomValue, ShellError, Value}; +use serde::Serialize; + +use crate::{ + plugin::{call_plugin, create_command}, + EncodingType, +}; + +use super::{PluginCall, PluginData, PluginResponse}; + +/// An opaque container for a custom value that is handled fully by a plugin +/// +/// This is constructed by the main nushell engine when it receives [`PluginResponse::PluginData`] +/// it stores that data as well as metadata related to the plugin to be able to call the plugin +/// later. +/// Since the data in it is opaque to the engine, there are only two final destinations for it: +/// either it will be sent back to the plugin that generated it across a pipeline, or it will be +/// sent to the plugin with a request to collapse it into a base value +#[derive(Clone, Debug, Serialize)] +pub struct PluginCustomValue { + /// The name of the custom value as defined by the plugin + pub name: String, + pub data: Vec, + pub filename: PathBuf, + + // PluginCustomValue must implement Serialize because all CustomValues must implement Serialize + // However, the main place where values are serialized and deserialized is when they are being + // sent between plugins and nushell's main engine. PluginCustomValue is never meant to be sent + // between that boundary + #[serde(skip)] + pub shell: Option, + #[serde(skip)] + pub encoding: EncodingType, + #[serde(skip)] + pub source: String, +} + +impl CustomValue for PluginCustomValue { + fn clone_value(&self, span: nu_protocol::Span) -> nu_protocol::Value { + Value::CustomValue { + val: Box::new(self.clone()), + span, + } + } + + fn value_string(&self) -> String { + self.name.clone() + } + + fn to_base_value( + &self, + span: nu_protocol::Span, + ) -> Result { + let mut plugin_cmd = create_command(&self.filename, &self.shell); + + let mut child = plugin_cmd.spawn().map_err(|err| { + ShellError::GenericError( + format!( + "Unable to spawn plugin for {} to get base value", + self.source + ), + format!("{}", err), + Some(span), + None, + Vec::new(), + ) + })?; + + let plugin_call = PluginCall::CollapseCustomValue(PluginData { + data: self.data.clone(), + span, + }); + + let response = call_plugin(&mut child, plugin_call, &self.encoding, span).map_err(|err| { + ShellError::GenericError( + format!( + "Unable to decode call for {} to get base value", + self.source + ), + format!("{}", err), + Some(span), + None, + Vec::new(), + ) + }); + + let value = match response { + Ok(PluginResponse::Value(value)) => Ok(*value), + Ok(PluginResponse::PluginData(..)) => Err(ShellError::GenericError( + "Plugin misbehaving".into(), + "Plugin returned custom data as a response to a collapse call".into(), + Some(span), + None, + Vec::new(), + )), + Ok(PluginResponse::Error(err)) => Err(err.into()), + Ok(PluginResponse::Signature(..)) => Err(ShellError::GenericError( + "Plugin missing value".into(), + "Received a signature from plugin instead of value".into(), + Some(span), + None, + Vec::new(), + )), + Err(err) => Err(err), + }; + + // We need to call .wait() on the child, or we'll risk summoning the zombie horde + let _ = child.wait(); + + value + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn typetag_name(&self) -> &'static str { + "PluginCustomValue" + } + + fn typetag_deserialize(&self) { + unimplemented!("typetag_deserialize") + } +} diff --git a/crates/nu-plugin/src/protocol/plugin_data.rs b/crates/nu-plugin/src/protocol/plugin_data.rs new file mode 100644 index 000000000..6b6b2ccdb --- /dev/null +++ b/crates/nu-plugin/src/protocol/plugin_data.rs @@ -0,0 +1,8 @@ +use nu_protocol::Span; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct PluginData { + pub data: Vec, + pub span: Span, +} diff --git a/crates/nu-plugin/src/serializers/capnp/call_input.rs b/crates/nu-plugin/src/serializers/capnp/call_input.rs new file mode 100644 index 000000000..bc20a0d9b --- /dev/null +++ b/crates/nu-plugin/src/serializers/capnp/call_input.rs @@ -0,0 +1,113 @@ +use super::{plugin_data, value}; +use crate::{plugin_capnp::call_input, protocol::CallInput}; +use nu_protocol::{ShellError, Span}; + +pub(crate) fn serialize_call_input(call_input: &CallInput, builder: call_input::Builder) { + match call_input { + CallInput::Value(value) => { + value::serialize_value(value, builder.init_value()); + } + CallInput::Data(plugin_data) => { + let builder = builder.init_plugin_data(); + + plugin_data::serialize_plugin_data(plugin_data, builder); + } + }; +} + +pub(crate) fn deserialize_call_input(reader: call_input::Reader) -> Result { + match reader.which() { + Err(capnp::NotInSchema(_)) => Err(ShellError::PluginFailedToDecode( + "value not in schema".into(), + )), + Ok(call_input::Value(value_reader)) => { + let value_reader = + value_reader.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + let span_reader = value_reader + .get_span() + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + let span = Span { + start: span_reader.get_start() as usize, + end: span_reader.get_end() as usize, + }; + + Ok(CallInput::Value(value::deserialize_value( + value_reader, + span, + )?)) + } + Ok(call_input::PluginData(plugin_data_reader)) => { + let plugin_data_reader = + plugin_data_reader.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + let plugin_data = plugin_data::deserialize_plugin_data(plugin_data_reader)?; + + Ok(CallInput::Data(plugin_data)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::protocol::{CallInput, PluginData}; + use capnp::serialize; + use nu_protocol::{Span, Value}; + + pub fn write_buffer( + call_input: &CallInput, + writer: &mut impl std::io::Write, + ) -> Result<(), ShellError> { + let mut message = ::capnp::message::Builder::new_default(); + + let mut builder = message.init_root::(); + + serialize_call_input(call_input, builder.reborrow()); + + serialize::write_message(writer, &message) + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string())) + } + + pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { + let message_reader = + serialize::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let reader = message_reader + .get_root::() + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + deserialize_call_input(reader.reborrow()) + } + + #[test] + fn callinput_value_round_trip() { + let call_input = CallInput::Value(Value::String { + val: "abc".to_string(), + span: Span { start: 1, end: 20 }, + }); + + let mut buffer: Vec = Vec::new(); + write_buffer(&call_input, &mut buffer).expect("unable to serialize message"); + let returned_call_input = + read_buffer(&mut buffer.as_slice()).expect("unable to deserialize message"); + + assert_eq!(call_input, returned_call_input) + } + + #[test] + fn callinput_data_round_trip() { + let call_input = CallInput::Data(PluginData { + data: vec![1, 2, 3, 4, 5, 6, 7], + span: Span { start: 1, end: 20 }, + }); + + let mut buffer: Vec = Vec::new(); + write_buffer(&call_input, &mut buffer).expect("unable to serialize message"); + let returned_call_input = + read_buffer(&mut buffer.as_slice()).expect("unable to deserialize message"); + + assert_eq!(call_input, returned_call_input) + } +} diff --git a/crates/nu-plugin/src/serializers/capnp/mod.rs b/crates/nu-plugin/src/serializers/capnp/mod.rs index 74707cd32..96bd87ddf 100644 --- a/crates/nu-plugin/src/serializers/capnp/mod.rs +++ b/crates/nu-plugin/src/serializers/capnp/mod.rs @@ -1,5 +1,7 @@ mod call; +mod call_input; mod plugin_call; +mod plugin_data; mod signature; mod value; @@ -7,7 +9,7 @@ use nu_protocol::ShellError; use crate::{plugin::PluginEncoder, protocol::PluginResponse}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct CapnpSerializer; impl PluginEncoder for CapnpSerializer { diff --git a/crates/nu-plugin/src/serializers/capnp/plugin_call.rs b/crates/nu-plugin/src/serializers/capnp/plugin_call.rs index 3a4cd1e9a..8bdc14234 100644 --- a/crates/nu-plugin/src/serializers/capnp/plugin_call.rs +++ b/crates/nu-plugin/src/serializers/capnp/plugin_call.rs @@ -1,5 +1,5 @@ use super::signature::deserialize_signature; -use super::{call, signature, value}; +use super::{call, call_input, plugin_data, signature, value}; use crate::plugin_capnp::{plugin_call, plugin_response}; use crate::protocol::{CallInfo, LabeledError, PluginCall, PluginResponse}; use capnp::serialize; @@ -31,12 +31,17 @@ pub fn encode_call( .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))?; // Serializing the input value from the call info - let value_builder = call_info_builder + let call_input_builder = call_info_builder .reborrow() .get_input() .map_err(|e| ShellError::PluginFailedToEncode(e.to_string()))?; - value::serialize_value(&call_info.input, value_builder); + call_input::serialize_call_input(&call_info.input, call_input_builder); + } + PluginCall::CollapseCustomValue(plugin_data) => { + let builder = builder.init_collapse_custom_value(); + + plugin_data::serialize_plugin_data(plugin_data, builder); } }; @@ -74,13 +79,20 @@ pub fn decode_call(reader: &mut impl std::io::BufRead) -> Result { + let reader = reader.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + let plugin_data = plugin_data::deserialize_plugin_data(reader)?; + + Ok(PluginCall::CollapseCustomValue(plugin_data)) } } } @@ -118,6 +130,12 @@ pub fn encode_response( let value_builder = builder.reborrow().init_value(); value::serialize_value(val, value_builder); } + PluginResponse::PluginData(name, plugin_data) => { + let mut plugin_data_builder = builder.reborrow().init_plugin_data(); + + plugin_data_builder.set_name(name); + plugin_data::serialize_plugin_data(plugin_data, plugin_data_builder.init_data()); + } }; serialize::write_message(writer, &message) @@ -196,13 +214,29 @@ pub fn decode_response(reader: &mut impl std::io::BufRead) -> Result { + let reader = reader.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + let name = reader + .get_name() + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + let plugin_data_reader = reader + .get_data() + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + let plugin_data = plugin_data::deserialize_plugin_data(plugin_data_reader)?; + + Ok(PluginResponse::PluginData(name.to_string(), plugin_data)) + } } } #[cfg(test)] mod tests { use super::*; - use crate::protocol::{EvaluatedCall, LabeledError, PluginCall, PluginResponse}; + use crate::protocol::{ + CallInput, EvaluatedCall, LabeledError, PluginCall, PluginData, PluginResponse, + }; use nu_protocol::{Signature, Span, Spanned, SyntaxShape, Value}; #[test] @@ -216,6 +250,7 @@ mod tests { match returned { PluginCall::Signature => {} PluginCall::CallInfo(_) => panic!("decoded into wrong value"), + PluginCall::CollapseCustomValue(_) => panic!("decoded into wrong value"), } } @@ -223,10 +258,10 @@ mod tests { fn callinfo_round_trip_callinfo() { let name = "test".to_string(); - let input = Value::Bool { + let input = CallInput::Value(Value::Bool { val: false, span: Span { start: 1, end: 20 }, - }; + }); let call = EvaluatedCall { head: Span { start: 0, end: 10 }, @@ -252,11 +287,18 @@ mod tests { )], }; - let plugin_call = PluginCall::CallInfo(Box::new(CallInfo { + let plugin_call = PluginCall::CallInfo(CallInfo { name: name.clone(), call: call.clone(), - input: input.clone(), - })); + // Avoiding having to implement Clone on CallInput just for tests + input: match &input { + CallInput::Value(value) => CallInput::Value(value.clone()), + CallInput::Data(plugin_data) => CallInput::Data(PluginData { + data: plugin_data.data.clone(), + span: plugin_data.span, + }), + }, + }); let mut buffer: Vec = Vec::new(); encode_call(&plugin_call, &mut buffer).expect("unable to serialize message"); @@ -266,7 +308,7 @@ mod tests { PluginCall::Signature => panic!("returned wrong call type"), PluginCall::CallInfo(call_info) => { assert_eq!(name, call_info.name); - assert_eq!(input, call_info.input); + assert_eq!(&input, &call_info.input); assert_eq!(call.head, call_info.call.head); assert_eq!(call.positional.len(), call_info.call.positional.len()); @@ -289,6 +331,31 @@ mod tests { } }); } + PluginCall::CollapseCustomValue(_) => panic!("returned wrong call type"), + } + } + + #[test] + fn callinfo_round_trip_collapsecustomvalue() { + let data = vec![1, 2, 3, 4, 5, 6, 7]; + let span = Span { start: 0, end: 20 }; + + let collapse_custom_value = PluginCall::CollapseCustomValue(PluginData { + data: data.clone(), + span, + }); + + let mut buffer: Vec = Vec::new(); + encode_call(&collapse_custom_value, &mut buffer).expect("unable to serialize message"); + let returned = decode_call(&mut buffer.as_slice()).expect("unable to deserialize message"); + + match returned { + PluginCall::Signature => panic!("returned wrong call type"), + PluginCall::CallInfo(_) => panic!("returned wrong call type"), + PluginCall::CollapseCustomValue(plugin_data) => { + assert_eq!(data, plugin_data.data); + assert_eq!(span, plugin_data.span); + } } } @@ -316,6 +383,7 @@ mod tests { match returned { PluginResponse::Error(_) => panic!("returned wrong call type"), PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(..) => panic!("returned wrong call type"), PluginResponse::Signature(returned_signature) => { assert!(returned_signature.len() == 1); assert_eq!(signature.name, returned_signature[0].name); @@ -366,12 +434,45 @@ mod tests { match returned { PluginResponse::Error(_) => panic!("returned wrong call type"), PluginResponse::Signature(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(..) => panic!("returned wrong call type"), PluginResponse::Value(returned_value) => { assert_eq!(&value, returned_value.as_ref()) } } } + #[test] + fn response_round_trip_plugin_data() { + let name = "test".to_string(); + + let data = vec![1, 2, 3, 4, 5]; + let span = Span { start: 2, end: 30 }; + + let response = PluginResponse::PluginData( + name.clone(), + PluginData { + data: data.clone(), + span, + }, + ); + + let mut buffer: Vec = Vec::new(); + encode_response(&response, &mut buffer).expect("unable to serialize message"); + let returned = + decode_response(&mut buffer.as_slice()).expect("unable to deserialize message"); + + match returned { + PluginResponse::Error(_) => panic!("returned wrong call type"), + PluginResponse::Signature(_) => panic!("returned wrong call type"), + PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(returned_name, returned_plugin_data) => { + assert_eq!(name, returned_name); + assert_eq!(data, returned_plugin_data.data); + assert_eq!(span, returned_plugin_data.span); + } + } + } + #[test] fn response_round_trip_error() { let error = LabeledError { @@ -390,6 +491,7 @@ mod tests { PluginResponse::Error(msg) => assert_eq!(error, msg), PluginResponse::Signature(_) => panic!("returned wrong call type"), PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(..) => panic!("returned wrong call type"), } } @@ -411,6 +513,7 @@ mod tests { PluginResponse::Error(msg) => assert_eq!(error, msg), PluginResponse::Signature(_) => panic!("returned wrong call type"), PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(..) => panic!("returned wrong call type"), } } } diff --git a/crates/nu-plugin/src/serializers/capnp/plugin_data.rs b/crates/nu-plugin/src/serializers/capnp/plugin_data.rs new file mode 100644 index 000000000..5b007d365 --- /dev/null +++ b/crates/nu-plugin/src/serializers/capnp/plugin_data.rs @@ -0,0 +1,79 @@ +use crate::{plugin_capnp::plugin_data, protocol::PluginData}; +use nu_protocol::{ShellError, Span}; + +pub(crate) fn serialize_plugin_data(plugin_data: &PluginData, mut builder: plugin_data::Builder) { + builder.set_data(&plugin_data.data); + + let mut span_builder = builder.init_span(); + span_builder.set_start(plugin_data.span.start as u64); + span_builder.set_end(plugin_data.span.end as u64); +} + +pub(crate) fn deserialize_plugin_data( + reader: plugin_data::Reader, +) -> Result { + let data = reader + .get_data() + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + let span_reader = reader + .get_span() + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + let span = Span { + start: span_reader.get_start() as usize, + end: span_reader.get_end() as usize, + }; + + Ok(PluginData { + data: data.to_vec(), + span, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use capnp::serialize; + use nu_protocol::Span; + + pub fn write_buffer( + plugin_data: &PluginData, + writer: &mut impl std::io::Write, + ) -> Result<(), ShellError> { + let mut message = ::capnp::message::Builder::new_default(); + + let mut builder = message.init_root::(); + + serialize_plugin_data(plugin_data, builder.reborrow()); + + serialize::write_message(writer, &message) + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string())) + } + + pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { + let message_reader = + serialize::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let reader = message_reader + .get_root::() + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + deserialize_plugin_data(reader.reborrow()) + } + + #[test] + fn plugin_data_round_trip() { + let plugin_data = PluginData { + data: vec![1, 2, 3, 4, 5, 6, 7], + span: Span { start: 1, end: 20 }, + }; + + let mut buffer: Vec = Vec::new(); + write_buffer(&plugin_data, &mut buffer).expect("unable to serialize message"); + let returned_plugin_data = + read_buffer(&mut buffer.as_slice()).expect("unable to deserialize message"); + + assert_eq!(plugin_data, returned_plugin_data) + } +} diff --git a/crates/nu-plugin/src/serializers/capnp/schema/plugin.capnp b/crates/nu-plugin/src/serializers/capnp/schema/plugin.capnp index e148d0957..483c3a349 100644 --- a/crates/nu-plugin/src/serializers/capnp/schema/plugin.capnp +++ b/crates/nu-plugin/src/serializers/capnp/schema/plugin.capnp @@ -121,10 +121,22 @@ struct EvaluatedCall { named @2 :Map(Text, Value); } +struct PluginData { + data @0 :Data; + span @1 :Span; +} + +struct CallInput { + union { + value @0 :Value; + pluginData @1 :PluginData; + } +} + struct CallInfo { name @0 :Text; call @1 :EvaluatedCall; - input @2 :Value; + input @2 :CallInput; } # Main communication structs with the plugin @@ -132,6 +144,7 @@ struct PluginCall { union { signature @0 :Void; callInfo @1 :CallInfo; + collapseCustomValue @2 :PluginData; } } @@ -140,6 +153,12 @@ struct PluginResponse { error @0 :LabeledError; signature @1 :List(Signature); value @2 :Value; + pluginData @3 :PluginDataResponse; + } + + struct PluginDataResponse { + name @0 :Text; + data @1 :PluginData; } } diff --git a/crates/nu-plugin/src/serializers/json.rs b/crates/nu-plugin/src/serializers/json.rs index 7d16cab30..87a43d050 100644 --- a/crates/nu-plugin/src/serializers/json.rs +++ b/crates/nu-plugin/src/serializers/json.rs @@ -2,7 +2,7 @@ use nu_protocol::ShellError; use crate::{plugin::PluginEncoder, protocol::PluginResponse}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct JsonSerializer; impl PluginEncoder for JsonSerializer { @@ -44,7 +44,9 @@ impl PluginEncoder for JsonSerializer { #[cfg(test)] mod tests { use super::*; - use crate::protocol::{CallInfo, EvaluatedCall, LabeledError, PluginCall, PluginResponse}; + use crate::protocol::{ + CallInfo, CallInput, EvaluatedCall, LabeledError, PluginCall, PluginData, PluginResponse, + }; use nu_protocol::{Signature, Span, Spanned, SyntaxShape, Value}; #[test] @@ -63,6 +65,7 @@ mod tests { match returned { PluginCall::Signature => {} PluginCall::CallInfo(_) => panic!("decoded into wrong value"), + PluginCall::CollapseCustomValue(_) => panic!("decoded into wrong value"), } } @@ -99,11 +102,11 @@ mod tests { )], }; - let plugin_call = PluginCall::CallInfo(Box::new(CallInfo { + let plugin_call = PluginCall::CallInfo(CallInfo { name: name.clone(), call: call.clone(), - input: input.clone(), - })); + input: CallInput::Value(input.clone()), + }); let encoder = JsonSerializer {}; let mut buffer: Vec = Vec::new(); @@ -118,7 +121,7 @@ mod tests { PluginCall::Signature => panic!("returned wrong call type"), PluginCall::CallInfo(call_info) => { assert_eq!(name, call_info.name); - assert_eq!(input, call_info.input); + assert_eq!(CallInput::Value(input), call_info.input); assert_eq!(call.head, call_info.call.head); assert_eq!(call.positional.len(), call_info.call.positional.len()); @@ -141,6 +144,36 @@ mod tests { } }); } + PluginCall::CollapseCustomValue(_) => panic!("returned wrong call type"), + } + } + + #[test] + fn callinfo_round_trip_collapsecustomvalue() { + let data = vec![1, 2, 3, 4, 5, 6, 7]; + let span = Span { start: 0, end: 20 }; + + let collapse_custom_value = PluginCall::CollapseCustomValue(PluginData { + data: data.clone(), + span, + }); + + let encoder = JsonSerializer {}; + let mut buffer: Vec = Vec::new(); + encoder + .encode_call(&collapse_custom_value, &mut buffer) + .expect("unable to serialize message"); + let returned = encoder + .decode_call(&mut buffer.as_slice()) + .expect("unable to deserialize message"); + + match returned { + PluginCall::Signature => panic!("returned wrong call type"), + PluginCall::CallInfo(_) => panic!("returned wrong call type"), + PluginCall::CollapseCustomValue(plugin_data) => { + assert_eq!(data, plugin_data.data); + assert_eq!(span, plugin_data.span); + } } } @@ -172,6 +205,7 @@ mod tests { match returned { PluginResponse::Error(_) => panic!("returned wrong call type"), PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(..) => panic!("returned wrong call type"), PluginResponse::Signature(returned_signature) => { assert!(returned_signature.len() == 1); assert_eq!(signature.name, returned_signature[0].name); @@ -226,12 +260,49 @@ mod tests { match returned { PluginResponse::Error(_) => panic!("returned wrong call type"), PluginResponse::Signature(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(..) => panic!("returned wrong call type"), PluginResponse::Value(returned_value) => { assert_eq!(&value, returned_value.as_ref()) } } } + #[test] + fn response_round_trip_plugin_data() { + let name = "test".to_string(); + + let data = vec![1, 2, 3, 4, 5]; + let span = Span { start: 2, end: 30 }; + + let response = PluginResponse::PluginData( + name.clone(), + PluginData { + data: data.clone(), + span, + }, + ); + + let encoder = JsonSerializer {}; + let mut buffer: Vec = Vec::new(); + encoder + .encode_response(&response, &mut buffer) + .expect("unable to serialize message"); + let returned = encoder + .decode_response(&mut buffer.as_slice()) + .expect("unable to deserialize message"); + + match returned { + PluginResponse::Error(_) => panic!("returned wrong call type"), + PluginResponse::Signature(_) => panic!("returned wrong call type"), + PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(returned_name, returned_plugin_data) => { + assert_eq!(name, returned_name); + assert_eq!(data, returned_plugin_data.data); + assert_eq!(span, returned_plugin_data.span); + } + } + } + #[test] fn response_round_trip_error() { let error = LabeledError { @@ -254,6 +325,7 @@ mod tests { PluginResponse::Error(msg) => assert_eq!(error, msg), PluginResponse::Signature(_) => panic!("returned wrong call type"), PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(..) => panic!("returned wrong call type"), } } @@ -279,6 +351,7 @@ mod tests { PluginResponse::Error(msg) => assert_eq!(error, msg), PluginResponse::Signature(_) => panic!("returned wrong call type"), PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::PluginData(..) => panic!("returned wrong call type"), } } } diff --git a/crates/nu-plugin/src/serializers/mod.rs b/crates/nu-plugin/src/serializers/mod.rs index 937732b12..2c06d75f3 100644 --- a/crates/nu-plugin/src/serializers/mod.rs +++ b/crates/nu-plugin/src/serializers/mod.rs @@ -8,7 +8,7 @@ use crate::{ pub mod capnp; pub mod json; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum EncodingType { Capnp(capnp::CapnpSerializer), Json(json::JsonSerializer), diff --git a/crates/nu_plugin_custom_values/Cargo.toml b/crates/nu_plugin_custom_values/Cargo.toml new file mode 100644 index 000000000..9f9ecf39a --- /dev/null +++ b/crates/nu_plugin_custom_values/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nu_plugin_custom_values" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nu-plugin = { path = "../nu-plugin", version = "0.65.1" } +nu-protocol = { path = "../nu-protocol", version = "0.65.1", features = ["plugin"] } +serde = { version = "1.0", features = ["derive"] } +typetag = "0.1.8" diff --git a/crates/nu_plugin_custom_values/src/cool_custom_value.rs b/crates/nu_plugin_custom_values/src/cool_custom_value.rs new file mode 100644 index 000000000..6e125784c --- /dev/null +++ b/crates/nu_plugin_custom_values/src/cool_custom_value.rs @@ -0,0 +1,67 @@ +use nu_protocol::{CustomValue, ShellError, Span, Value}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CoolCustomValue { + pub(crate) cool: String, +} + +impl CoolCustomValue { + pub fn new(content: &str) -> Self { + Self { + cool: content.to_owned(), + } + } + + pub fn into_value(self, span: Span) -> Value { + Value::CustomValue { + val: Box::new(self), + span, + } + } + + pub fn try_from_value(value: &Value) -> Result { + match value { + Value::CustomValue { val, span } => match val.as_any().downcast_ref::() { + Some(cool) => Ok(cool.clone()), + None => Err(ShellError::CantConvert( + "cool".into(), + "non-cool".into(), + *span, + None, + )), + }, + x => Err(ShellError::CantConvert( + "cool".into(), + x.get_type().to_string(), + x.span()?, + None, + )), + } + } +} + +#[typetag::serde] +impl CustomValue for CoolCustomValue { + fn clone_value(&self, span: nu_protocol::Span) -> Value { + Value::CustomValue { + val: Box::new(self.clone()), + span, + } + } + + fn value_string(&self) -> String { + self.typetag_name().to_string() + } + + fn to_base_value(&self, span: nu_protocol::Span) -> Result { + Ok(Value::String { + val: format!("I used to be a custom value! My data was ({})", self.cool), + span, + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/crates/nu_plugin_custom_values/src/main.rs b/crates/nu_plugin_custom_values/src/main.rs new file mode 100644 index 000000000..77f4c5e46 --- /dev/null +++ b/crates/nu_plugin_custom_values/src/main.rs @@ -0,0 +1,78 @@ +mod cool_custom_value; +mod second_custom_value; + +use cool_custom_value::CoolCustomValue; +use nu_plugin::{serve_plugin, CapnpSerializer, Plugin}; +use nu_plugin::{EvaluatedCall, LabeledError}; +use nu_protocol::{Category, ShellError, Signature, Value}; +use second_custom_value::SecondCustomValue; + +struct CustomValuePlugin; + +impl Plugin for CustomValuePlugin { + fn signature(&self) -> Vec { + vec![ + Signature::build("custom-value generate") + .usage("Signature for a plugin that generates a custom value") + .category(Category::Experimental), + Signature::build("custom-value generate2") + .usage("Signature for a plugin that generates a different custom value") + .category(Category::Experimental), + Signature::build("custom-value update") + .usage("Signature for a plugin that updates a custom value") + .category(Category::Experimental), + ] + } + + fn run( + &mut self, + name: &str, + call: &EvaluatedCall, + input: &Value, + ) -> Result { + match name { + "custom-value generate" => self.generate(call, input), + "custom-value generate2" => self.generate2(call, input), + "custom-value update" => self.update(call, input), + _ => Err(LabeledError { + label: "Plugin call with wrong name signature".into(), + msg: "the signature used to call the plugin does not match any name in the plugin signature vector".into(), + span: Some(call.head), + }), + } + } +} + +impl CustomValuePlugin { + fn generate(&mut self, call: &EvaluatedCall, _input: &Value) -> Result { + Ok(CoolCustomValue::new("abc").into_value(call.head)) + } + + fn generate2(&mut self, call: &EvaluatedCall, _input: &Value) -> Result { + Ok(SecondCustomValue::new("xyz").into_value(call.head)) + } + + fn update(&mut self, call: &EvaluatedCall, input: &Value) -> Result { + if let Ok(mut value) = CoolCustomValue::try_from_value(input) { + value.cool += "xyz"; + return Ok(value.into_value(call.head)); + } + + if let Ok(mut value) = SecondCustomValue::try_from_value(input) { + value.something += "abc"; + return Ok(value.into_value(call.head)); + } + + Err(ShellError::CantConvert( + "cool or second".into(), + "non-cool and non-second".into(), + call.head, + None, + ) + .into()) + } +} + +fn main() { + serve_plugin(&mut CustomValuePlugin, CapnpSerializer {}) +} diff --git a/crates/nu_plugin_custom_values/src/second_custom_value.rs b/crates/nu_plugin_custom_values/src/second_custom_value.rs new file mode 100644 index 000000000..17e5b3c26 --- /dev/null +++ b/crates/nu_plugin_custom_values/src/second_custom_value.rs @@ -0,0 +1,70 @@ +use nu_protocol::{CustomValue, ShellError, Span, Value}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SecondCustomValue { + pub(crate) something: String, +} + +impl SecondCustomValue { + pub fn new(content: &str) -> Self { + Self { + something: content.to_owned(), + } + } + + pub fn into_value(self, span: Span) -> Value { + Value::CustomValue { + val: Box::new(self), + span, + } + } + + pub fn try_from_value(value: &Value) -> Result { + match value { + Value::CustomValue { val, span } => match val.as_any().downcast_ref::() { + Some(value) => Ok(value.clone()), + None => Err(ShellError::CantConvert( + "cool".into(), + "non-cool".into(), + *span, + None, + )), + }, + x => Err(ShellError::CantConvert( + "cool".into(), + x.get_type().to_string(), + x.span()?, + None, + )), + } + } +} + +#[typetag::serde] +impl CustomValue for SecondCustomValue { + fn clone_value(&self, span: nu_protocol::Span) -> Value { + Value::CustomValue { + val: Box::new(self.clone()), + span, + } + } + + fn value_string(&self) -> String { + self.typetag_name().to_string() + } + + fn to_base_value(&self, span: nu_protocol::Span) -> Result { + Ok(Value::String { + val: format!( + "I used to be a DIFFERENT custom value! ({})", + self.something + ), + span, + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/tests/plugins/custom_values.rs b/tests/plugins/custom_values.rs new file mode 100644 index 000000000..f7bc946f2 --- /dev/null +++ b/tests/plugins/custom_values.rs @@ -0,0 +1,84 @@ +use nu_test_support::nu_with_plugins; + +#[test] +fn can_get_custom_value_from_plugin_and_instantly_collapse_it() { + let actual = nu_with_plugins!( + cwd: "tests", + plugin: ("capnp", "nu_plugin_custom_values"), + "custom-value generate" + ); + + assert_eq!(actual.out, "I used to be a custom value! My data was (abc)"); +} + +#[test] +fn can_get_custom_value_from_plugin_and_pass_it_over() { + let actual = nu_with_plugins!( + cwd: "tests", + plugin: ("capnp", "nu_plugin_custom_values"), + "custom-value generate | custom-value update" + ); + + assert_eq!( + actual.out, + "I used to be a custom value! My data was (abcxyz)" + ); +} + +#[test] +fn can_generate_and_updated_multiple_types_of_custom_values() { + let actual = nu_with_plugins!( + cwd: "tests", + plugin: ("capnp", "nu_plugin_custom_values"), + "custom-value generate2 | custom-value update" + ); + + assert_eq!( + actual.out, + "I used to be a DIFFERENT custom value! (xyzabc)" + ); +} + +#[test] +fn can_get_describe_plugin_custom_values() { + let actual = nu_with_plugins!( + cwd: "tests", + plugin: ("capnp", "nu_plugin_custom_values"), + "custom-value generate | describe" + ); + + assert_eq!(actual.out, "CoolCustomValue"); +} + +// There are currently no custom values defined by the engine that aren't hidden behind an extra +// feature, both database and dataframes are hidden behind --features=extra so we need to guard +// this test +#[cfg(feature = "database")] +#[test] +fn fails_if_passing_engine_custom_values_to_plugins() { + let actual = nu_with_plugins!( + cwd: "tests/fixtures/formats", + plugin: ("capnp", "nu_plugin_custom_values"), + "open-db sample.db | custom-value update" + ); + + assert!(actual + .err + .contains("Plugin custom-value update can not handle the custom value SQLiteDatabase")); +} + +#[test] +fn fails_if_passing_custom_values_across_plugins() { + let actual = nu_with_plugins!( + cwd: "tests", + plugins: [ + ("capnp", "nu_plugin_custom_values"), + ("json", "nu_plugin_inc") + ], + "custom-value generate | inc --major" + ); + + assert!(actual + .err + .contains("Plugin inc can not handle the custom value CoolCustomValue")); +} diff --git a/tests/plugins/mod.rs b/tests/plugins/mod.rs index e3d59b220..78bdd59ac 100644 --- a/tests/plugins/mod.rs +++ b/tests/plugins/mod.rs @@ -1 +1,2 @@ mod core_inc; +mod custom_values;