nu_plugin_core/serializers/
json.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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());
    }
}