mirror of
https://github.com/nushell/nushell.git
synced 2025-04-29 23:54:26 +02:00
# Description This breaks `nu-plugin` up into four crates: - `nu-plugin-protocol`: just the type definitions for the protocol, no I/O. If someone wanted to wire up something more bare metal, maybe for async I/O, they could use this. - `nu-plugin-core`: the shared stuff between engine/plugin. Less stable interface. - `nu-plugin-engine`: everything required for the engine to talk to plugins. Less stable interface. - `nu-plugin`: everything required for the plugin to talk to the engine, what plugin developers use. Should be the most stable interface. No changes are made to the interface exposed by `nu-plugin` - it should all still be there. Re-exports from `nu-plugin-protocol` or `nu-plugin-core` are used as required. Plugins shouldn't ever have to use those crates directly. This should be somewhat faster to compile as `nu-plugin-engine` and `nu-plugin` can compile in parallel, and the engine doesn't need `nu-plugin` and plugins don't need `nu-plugin-engine` (except for test support), so that should reduce what needs to be compiled too. The only significant change here other than splitting stuff up was to break the `source` out of `PluginCustomValue` and create a new `PluginCustomValueWithSource` type that contains that instead. One bonus of that is we get rid of the option and it's now more type-safe, but it also means that the logic for that stuff (actually running the plugin for custom value ops) can live entirely within the `nu-plugin-engine` crate. # User-Facing Changes - New crates. - Added `local-socket` feature for `nu` to try to make it possible to compile without that support if needed. # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib`
134 lines
4.0 KiB
Rust
134 lines
4.0 KiB
Rust
use nu_plugin_protocol::{PluginInput, PluginOutput};
|
|
use nu_protocol::ShellError;
|
|
use serde::Deserialize;
|
|
|
|
use crate::{Encoder, PluginEncoder};
|
|
|
|
/// A `PluginEncoder` that enables the plugin to communicate with Nushell with JSON
|
|
/// serialized data.
|
|
///
|
|
/// Each message in the stream is followed by a newline when serializing, but is not required for
|
|
/// deserialization. The output is not pretty printed and each object does not contain newlines.
|
|
/// If it is more convenient, a plugin may choose to separate messages by newline.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct JsonSerializer;
|
|
|
|
impl PluginEncoder for JsonSerializer {
|
|
fn name(&self) -> &str {
|
|
"json"
|
|
}
|
|
}
|
|
|
|
impl Encoder<PluginInput> for JsonSerializer {
|
|
fn encode(
|
|
&self,
|
|
plugin_input: &PluginInput,
|
|
writer: &mut impl std::io::Write,
|
|
) -> Result<(), nu_protocol::ShellError> {
|
|
serde_json::to_writer(&mut *writer, plugin_input).map_err(json_encode_err)?;
|
|
writer.write_all(b"\n").map_err(|err| ShellError::IOError {
|
|
msg: err.to_string(),
|
|
})
|
|
}
|
|
|
|
fn decode(
|
|
&self,
|
|
reader: &mut impl std::io::BufRead,
|
|
) -> Result<Option<PluginInput>, nu_protocol::ShellError> {
|
|
let mut de = serde_json::Deserializer::from_reader(reader);
|
|
PluginInput::deserialize(&mut de)
|
|
.map(Some)
|
|
.or_else(json_decode_err)
|
|
}
|
|
}
|
|
|
|
impl Encoder<PluginOutput> for JsonSerializer {
|
|
fn encode(
|
|
&self,
|
|
plugin_output: &PluginOutput,
|
|
writer: &mut impl std::io::Write,
|
|
) -> Result<(), ShellError> {
|
|
serde_json::to_writer(&mut *writer, plugin_output).map_err(json_encode_err)?;
|
|
writer.write_all(b"\n").map_err(|err| ShellError::IOError {
|
|
msg: err.to_string(),
|
|
})
|
|
}
|
|
|
|
fn decode(
|
|
&self,
|
|
reader: &mut impl std::io::BufRead,
|
|
) -> Result<Option<PluginOutput>, ShellError> {
|
|
let mut de = serde_json::Deserializer::from_reader(reader);
|
|
PluginOutput::deserialize(&mut de)
|
|
.map(Some)
|
|
.or_else(json_decode_err)
|
|
}
|
|
}
|
|
|
|
/// Handle a `serde_json` encode error.
|
|
fn json_encode_err(err: serde_json::Error) -> ShellError {
|
|
if err.is_io() {
|
|
ShellError::IOError {
|
|
msg: err.to_string(),
|
|
}
|
|
} else {
|
|
ShellError::PluginFailedToEncode {
|
|
msg: err.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle a `serde_json` decode error. Returns `Ok(None)` on eof.
|
|
fn json_decode_err<T>(err: serde_json::Error) -> Result<Option<T>, ShellError> {
|
|
if err.is_eof() {
|
|
Ok(None)
|
|
} else if err.is_io() {
|
|
Err(ShellError::IOError {
|
|
msg: err.to_string(),
|
|
})
|
|
} else {
|
|
Err(ShellError::PluginFailedToDecode {
|
|
msg: err.to_string(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
crate::serializers::tests::generate_tests!(JsonSerializer {});
|
|
|
|
#[test]
|
|
fn json_ends_in_newline() {
|
|
let mut out = vec![];
|
|
JsonSerializer {}
|
|
.encode(&PluginInput::Call(0, PluginCall::Signature), &mut out)
|
|
.expect("serialization error");
|
|
let string = std::str::from_utf8(&out).expect("utf-8 error");
|
|
assert!(
|
|
string.ends_with('\n'),
|
|
"doesn't end with newline: {:?}",
|
|
string
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn json_has_no_other_newlines() {
|
|
let mut out = vec![];
|
|
// use something deeply nested, to try to trigger any pretty printing
|
|
let output = PluginOutput::Data(
|
|
0,
|
|
StreamData::List(Value::test_list(vec![
|
|
Value::test_int(4),
|
|
// in case escaping failed
|
|
Value::test_string("newline\ncontaining\nstring"),
|
|
])),
|
|
);
|
|
JsonSerializer {}
|
|
.encode(&output, &mut out)
|
|
.expect("serialization error");
|
|
let string = std::str::from_utf8(&out).expect("utf-8 error");
|
|
assert_eq!(1, string.chars().filter(|ch| *ch == '\n').count());
|
|
}
|
|
}
|