nu_plugin_core/serializers/
json.rs

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