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