nu_plugin_core/serializers/
json.rs

1use nu_plugin_protocol::{PluginInput, PluginOutput};
2use nu_protocol::{location, shell_error::io::IoError, ShellError};
3use serde::Deserialize;
4
5use crate::{Encoder, PluginEncoder};
6
7/// A `PluginEncoder` that enables the plugin to communicate with Nushell with JSON
8/// serialized data.
9///
10/// Each message in the stream is followed by a newline when serializing, but is not required for
11/// deserialization. The output is not pretty printed and each object does not contain newlines.
12/// If it is more convenient, a plugin may choose to separate messages by newline.
13#[derive(Clone, Copy, Debug)]
14pub struct JsonSerializer;
15
16impl PluginEncoder for JsonSerializer {
17    fn name(&self) -> &str {
18        "json"
19    }
20}
21
22impl Encoder<PluginInput> for JsonSerializer {
23    fn encode(
24        &self,
25        plugin_input: &PluginInput,
26        writer: &mut impl std::io::Write,
27    ) -> Result<(), nu_protocol::ShellError> {
28        serde_json::to_writer(&mut *writer, plugin_input).map_err(json_encode_err)?;
29        writer.write_all(b"\n").map_err(|err| {
30            ShellError::Io(IoError::new_internal(
31                err.kind(),
32                "Failed to write final line break",
33                location!(),
34            ))
35        })
36    }
37
38    fn decode(
39        &self,
40        reader: &mut impl std::io::BufRead,
41    ) -> Result<Option<PluginInput>, nu_protocol::ShellError> {
42        let mut de = serde_json::Deserializer::from_reader(reader);
43        PluginInput::deserialize(&mut de)
44            .map(Some)
45            .or_else(json_decode_err)
46    }
47}
48
49impl Encoder<PluginOutput> for JsonSerializer {
50    fn encode(
51        &self,
52        plugin_output: &PluginOutput,
53        writer: &mut impl std::io::Write,
54    ) -> Result<(), ShellError> {
55        serde_json::to_writer(&mut *writer, plugin_output).map_err(json_encode_err)?;
56        writer.write_all(b"\n").map_err(|err| {
57            ShellError::Io(IoError::new_internal(
58                err.kind(),
59                "JsonSerializer could not encode linebreak",
60                nu_protocol::location!(),
61            ))
62        })
63    }
64
65    fn decode(
66        &self,
67        reader: &mut impl std::io::BufRead,
68    ) -> Result<Option<PluginOutput>, ShellError> {
69        let mut de = serde_json::Deserializer::from_reader(reader);
70        PluginOutput::deserialize(&mut de)
71            .map(Some)
72            .or_else(json_decode_err)
73    }
74}
75
76/// Handle a `serde_json` encode error.
77fn json_encode_err(err: serde_json::Error) -> ShellError {
78    if err.is_io() {
79        ShellError::Io(IoError::new_internal(
80            err.io_error_kind().expect("is io"),
81            "Could not encode with json",
82            nu_protocol::location!(),
83        ))
84    } else {
85        ShellError::PluginFailedToEncode {
86            msg: err.to_string(),
87        }
88    }
89}
90
91/// Handle a `serde_json` decode error. Returns `Ok(None)` on eof.
92fn json_decode_err<T>(err: serde_json::Error) -> Result<Option<T>, ShellError> {
93    if err.is_eof() {
94        Ok(None)
95    } else if err.is_io() {
96        Err(ShellError::Io(IoError::new_internal(
97            err.io_error_kind().expect("is io"),
98            "Could not decode with json",
99            nu_protocol::location!(),
100        )))
101    } else {
102        Err(ShellError::PluginFailedToDecode {
103            msg: err.to_string(),
104        })
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    crate::serializers::tests::generate_tests!(JsonSerializer {});
112
113    #[test]
114    fn json_ends_in_newline() {
115        let mut out = vec![];
116        JsonSerializer {}
117            .encode(&PluginInput::Call(0, PluginCall::Signature), &mut out)
118            .expect("serialization error");
119        let string = std::str::from_utf8(&out).expect("utf-8 error");
120        assert!(
121            string.ends_with('\n'),
122            "doesn't end with newline: {:?}",
123            string
124        );
125    }
126
127    #[test]
128    fn json_has_no_other_newlines() {
129        let mut out = vec![];
130        // use something deeply nested, to try to trigger any pretty printing
131        let output = PluginOutput::Data(
132            0,
133            StreamData::List(Value::test_list(vec![
134                Value::test_int(4),
135                // in case escaping failed
136                Value::test_string("newline\ncontaining\nstring"),
137            ])),
138        );
139        JsonSerializer {}
140            .encode(&output, &mut out)
141            .expect("serialization error");
142        let string = std::str::from_utf8(&out).expect("utf-8 error");
143        assert_eq!(1, string.chars().filter(|ch| *ch == '\n').count());
144    }
145}