1use std::io;
5
6use byteorder::{BigEndian, WriteBytesExt};
7use nu_engine::command_prelude::*;
8use nu_protocol::{
9 Signals, Spanned, ast::PathMember, shell_error::generic::GenericError, shell_error::io::IoError,
10};
11use rmp::encode as mp;
12
13const MAX_DEPTH: usize = 50;
15
16#[derive(Clone)]
17pub struct ToMsgpack;
18
19impl Command for ToMsgpack {
20 fn name(&self) -> &str {
21 "to msgpack"
22 }
23
24 fn signature(&self) -> Signature {
25 Signature::build(self.name())
26 .input_output_type(Type::Any, Type::Binary)
27 .switch(
28 "serialize",
29 "Serialize nushell types that cannot be deserialized.",
30 Some('s'),
31 )
32 .category(Category::Formats)
33 }
34
35 fn description(&self) -> &str {
36 "Convert Nu values into MessagePack."
37 }
38
39 fn extra_description(&self) -> &str {
40 "
41Not all values are representable as MessagePack.
42
43The datetime extension type is used for dates. Binaries are represented with
44the native MessagePack binary type. Most other types are represented in an
45analogous way to `to json`, and may not convert to the exact same type when
46deserialized with `from msgpack`.
47
48MessagePack: https://msgpack.org/
49"
50 .trim()
51 }
52
53 fn examples(&self) -> Vec<Example<'_>> {
54 vec![
55 Example {
56 description: "Convert a list of values to MessagePack.",
57 example: "[foo, 42, false] | to msgpack",
58 result: Some(Value::test_binary(b"\x93\xA3\x66\x6F\x6F\x2A\xC2")),
59 },
60 Example {
61 description: "Convert a range to a MessagePack array.",
62 example: "1..10 | to msgpack",
63 result: Some(Value::test_binary(b"\x9A\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A"))
64 },
65 Example {
66 description: "Convert a table to MessagePack.",
67 example: "[
68 [event_name time];
69 ['Apollo 11 Landing' 1969-07-24T16:50:35]
70 ['Nushell first commit' 2019-05-10T09:59:12-07:00]
71 ] | to msgpack",
72 result: Some(Value::test_binary(b"\x92\x82\xAA\x65\x76\x65\x6E\x74\x5F\x6E\x61\x6D\x65\xB1\x41\x70\x6F\x6C\x6C\x6F\x20\x31\x31\x20\x4C\x61\x6E\x64\x69\x6E\x67\xA4\x74\x69\x6D\x65\xC7\x0C\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x2C\xAB\x5B\x82\xAA\x65\x76\x65\x6E\x74\x5F\x6E\x61\x6D\x65\xB4\x4E\x75\x73\x68\x65\x6C\x6C\x20\x66\x69\x72\x73\x74\x20\x63\x6F\x6D\x6D\x69\x74\xA4\x74\x69\x6D\x65\xD6\xFF\x5C\xD5\xAD\xE0")),
73 },
74 ]
75 }
76
77 fn run(
78 &self,
79 engine_state: &EngineState,
80 stack: &mut Stack,
81 call: &Call,
82 mut input: PipelineData,
83 ) -> Result<PipelineData, ShellError> {
84 let metadata = input
85 .take_metadata()
86 .unwrap_or_default()
87 .with_content_type(Some("application/x-msgpack".into()));
88
89 let value_span = input.span().unwrap_or(call.head);
90 let value = input.into_value(value_span)?;
91 let mut out = vec![];
92
93 let serialize_types = call.has_flag(engine_state, stack, "serialize")?;
94
95 write_value(
96 &mut out,
97 &value,
98 0,
99 engine_state,
100 call.head,
101 serialize_types,
102 )?;
103
104 Ok(Value::binary(out, call.head).into_pipeline_data_with_metadata(Some(metadata)))
105 }
106}
107
108#[derive(Debug)]
109pub(crate) enum WriteError {
110 MaxDepth(Span),
111 Rmp(mp::ValueWriteError<io::Error>, Span),
112 Io(io::Error, Span),
113 Shell(Box<ShellError>),
114}
115
116impl From<Spanned<mp::ValueWriteError<io::Error>>> for WriteError {
117 fn from(v: Spanned<mp::ValueWriteError<io::Error>>) -> Self {
118 Self::Rmp(v.item, v.span)
119 }
120}
121
122impl From<Spanned<io::Error>> for WriteError {
123 fn from(v: Spanned<io::Error>) -> Self {
124 Self::Io(v.item, v.span)
125 }
126}
127
128impl From<Box<ShellError>> for WriteError {
129 fn from(v: Box<ShellError>) -> Self {
130 Self::Shell(v)
131 }
132}
133
134impl From<ShellError> for WriteError {
135 fn from(value: ShellError) -> Self {
136 Box::new(value).into()
137 }
138}
139
140impl From<WriteError> for ShellError {
141 fn from(value: WriteError) -> Self {
142 match value {
143 WriteError::MaxDepth(span) => ShellError::Generic(GenericError::new(
144 "MessagePack data is nested too deeply",
145 format!("exceeded depth limit ({MAX_DEPTH})"),
146 span,
147 )),
148 WriteError::Rmp(err, span) => ShellError::Generic(GenericError::new(
149 "Failed to encode MessagePack data",
150 err.to_string(),
151 span,
152 )),
153 WriteError::Io(err, span) => ShellError::Io(IoError::new(err, span, None)),
154 WriteError::Shell(err) => *err,
155 }
156 }
157}
158
159pub(crate) fn write_value(
160 out: &mut impl io::Write,
161 value: &Value,
162 depth: usize,
163 engine_state: &EngineState,
164 call_span: Span,
165 serialize_types: bool,
166) -> Result<(), WriteError> {
167 use mp::ValueWriteError::InvalidMarkerWrite;
168 let span = value.span();
169 if depth >= MAX_DEPTH {
171 return Err(WriteError::MaxDepth(span));
172 }
173 match value {
174 Value::Bool { val, .. } => {
175 mp::write_bool(out, *val)
176 .map_err(InvalidMarkerWrite)
177 .err_span(span)?;
178 }
179 Value::Int { val, .. } => {
180 mp::write_sint(out, *val).err_span(span)?;
181 }
182 Value::Float { val, .. } => {
183 mp::write_f64(out, *val).err_span(span)?;
184 }
185 Value::Filesize { val, .. } => {
186 mp::write_sint(out, val.get()).err_span(span)?;
187 }
188 Value::Duration { val, .. } => {
189 mp::write_sint(out, *val).err_span(span)?;
190 }
191 Value::Date { val, .. } => {
192 if val.timestamp_subsec_nanos() == 0
193 && val.timestamp() >= 0
194 && val.timestamp() < u32::MAX as i64
195 {
196 mp::write_ext_meta(out, 4, -1).err_span(span)?;
198 out.write_u32::<BigEndian>(val.timestamp() as u32)
199 .err_span(span)?;
200 } else {
201 mp::write_ext_meta(out, 12, -1).err_span(span)?;
203 out.write_u32::<BigEndian>(val.timestamp_subsec_nanos())
204 .err_span(span)?;
205 out.write_i64::<BigEndian>(val.timestamp()).err_span(span)?;
206 }
207 }
208 Value::Range { val, .. } => {
209 write_value(
211 out,
212 &Value::list(val.into_range_iter(span, Signals::empty()).collect(), span),
213 depth,
214 engine_state,
215 call_span,
216 serialize_types,
217 )?;
218 }
219 Value::String { val, .. } => {
220 mp::write_str(out, val).err_span(span)?;
221 }
222 Value::Glob { val, .. } => {
223 mp::write_str(out, val).err_span(span)?;
224 }
225 Value::Record { val, .. } => {
226 mp::write_map_len(out, convert(val.len(), span)?).err_span(span)?;
227 for (k, v) in val.iter() {
228 mp::write_str(out, k).err_span(span)?;
229 write_value(out, v, depth + 1, engine_state, call_span, serialize_types)?;
230 }
231 }
232 Value::List { vals, .. } => {
233 mp::write_array_len(out, convert(vals.len(), span)?).err_span(span)?;
234 for val in vals {
235 write_value(
236 out,
237 val,
238 depth + 1,
239 engine_state,
240 call_span,
241 serialize_types,
242 )?;
243 }
244 }
245 Value::Nothing { .. } => {
246 mp::write_nil(out)
247 .map_err(InvalidMarkerWrite)
248 .err_span(span)?;
249 }
250 Value::Closure { val, .. } => {
251 if serialize_types {
252 let closure_string = val
253 .coerce_into_string(engine_state, span)
254 .map_err(|err| WriteError::Shell(Box::new(err)))?;
255 mp::write_str(out, &closure_string).err_span(span)?;
256 } else {
257 return Err(WriteError::Shell(Box::new(ShellError::UnsupportedInput {
258 msg: "closures are currently not deserializable (use --serialize to serialize as a string)".into(),
259 input: "value originates from here".into(),
260 msg_span: call_span,
261 input_span: span,
262 })));
263 }
264 }
265 Value::Error { error, .. } => {
266 return Err(WriteError::Shell(error.clone()));
267 }
268 Value::CellPath { val, .. } => {
269 mp::write_array_len(out, convert(val.members.len(), span)?).err_span(span)?;
271 for member in &val.members {
272 match member {
273 PathMember::String { val, .. } => {
274 mp::write_str(out, val).err_span(span)?;
275 }
276 PathMember::Int { val, .. } => {
277 mp::write_uint(out, *val as u64).err_span(span)?;
278 }
279 }
280 }
281 }
282 Value::Binary { val, .. } => {
283 mp::write_bin(out, val).err_span(span)?;
284 }
285 Value::Custom { val, .. } => {
286 write_value(
287 out,
288 &val.to_base_value(span)?,
289 depth,
290 engine_state,
291 call_span,
292 serialize_types,
293 )?;
294 }
295 }
296 Ok(())
297}
298
299fn convert<T, U>(value: T, span: Span) -> Result<U, ShellError>
300where
301 U: TryFrom<T>,
302 <U as TryFrom<T>>::Error: std::fmt::Display,
303{
304 value.try_into().map_err(|err: <U as TryFrom<T>>::Error| {
305 ShellError::Generic(GenericError::new(
306 "Value not compatible with MessagePack",
307 err.to_string(),
308 span,
309 ))
310 })
311}
312
313#[cfg(test)]
314mod test {
315 use nu_cmd_lang::eval_pipeline_without_terminal_expression;
316
317 use crate::{Get, Metadata};
318
319 use super::*;
320
321 #[test]
322 fn test_examples() -> nu_test_support::Result {
323 nu_test_support::test().examples(ToMsgpack)
324 }
325
326 #[test]
327 fn test_content_type_metadata() {
328 let mut engine_state = Box::new(EngineState::new());
329 let delta = {
330 let mut working_set = StateWorkingSet::new(&engine_state);
333
334 working_set.add_decl(Box::new(ToMsgpack {}));
335 working_set.add_decl(Box::new(Metadata {}));
336 working_set.add_decl(Box::new(Get {}));
337
338 working_set.render()
339 };
340
341 engine_state
342 .merge_delta(delta)
343 .expect("Error merging delta");
344
345 let cmd = "{a: 1 b: 2} | to msgpack | metadata | get content_type | $in";
346 let result = eval_pipeline_without_terminal_expression(
347 cmd,
348 std::env::temp_dir().as_ref(),
349 &mut engine_state,
350 );
351 assert_eq!(
352 Value::test_string("application/x-msgpack"),
353 result.expect("There should be a result")
354 );
355 }
356}