Skip to main content

cbor_cli/
export.rs

1use std::io;
2
3use cbor2::de::Deserializer;
4use cbor2::value::Value as CborValue;
5use anyhow::Context;
6
7/// Convert a stream of CBOR bytes into a stream of some other format.
8///
9/// Each top-level CBOR item is decoded and re-serialized to `format`, with
10/// `delimiter` written between items. The input is streamed one item at a
11/// time and never buffered.
12///
13/// # Arguments
14///
15/// - `format`: one of `"json"`, `"yaml"`, `"toml"`, `"cbor"`.
16/// - `delimiter`: bytes written between consecutive items (e.g. `"\n"`). An
17///   empty input produces no output and no delimiter.
18/// - `pretty`: if true and `format` is `"json"`, use pretty-printed output.
19/// - `reader`: the CBOR input stream. Supports CBOR sequences (RFC 8742).
20/// - `writer`: the output sink.
21///
22/// # Example
23///
24/// ```
25/// use cbor_cli::export::export_from_reader;
26///
27/// // CBOR for the integer 42.
28/// let cbor = [0x18, 0x2a];
29/// let mut json = Vec::new();
30/// export_from_reader("json", "\n", false, &cbor[..], &mut json).unwrap();
31/// assert_eq!(json, b"42");
32/// ```
33pub fn export_from_reader<R: io::Read, W: io::Write>(
34  format: &str,
35  delimiter: &str,
36  pretty: bool,
37  reader: R,
38  writer: &mut W,
39) -> anyhow::Result<()> {
40  let mut first = true;
41  for v in Deserializer::from_reader(reader).into_iter::<CborValue>() {
42    let v = v?;
43    if !first {
44      writer.write_all(delimiter.as_bytes())?;
45    }
46    first = false;
47
48    if format == "json" {
49      if pretty {
50        serde_json::to_writer_pretty(&mut *writer, &v).context("failed to serialize JSON")?;
51      } else {
52        serde_json::to_writer(&mut *writer, &v).context("failed to serialize JSON")?;
53      }
54    } else if format == "yaml" {
55      serde_yaml::to_writer(&mut *writer, &v).context("failed to serialize YAML")?;
56    } else if format == "toml" {
57      let s = toml::ser::to_string(&v).context("failed to serialize TOML")?;
58      writer.write_all(s.as_bytes())?;
59    } else {
60      cbor2::to_writer(&v, &mut *writer).context("failed to serialize CBOR")?;
61    }
62  }
63  Ok(())
64}
65
66#[cfg(test)]
67mod tests {
68  use super::*;
69
70  #[test]
71  fn test_export_array() {
72    let cbor = [0x82, 0x01, 0x02];
73    let cases: [(&str, bool, &[u8]); 2] = [
74      ("json", false, b"[1,2]"),
75      ("yaml", false, b"- 1\n- 2\n"),
76    ];
77    for (format, pretty, expected) in cases {
78      let mut output = Vec::new();
79      export_from_reader(format, "\n", pretty, &cbor[..], &mut output).unwrap();
80      assert_eq!(output, expected, "format: {format}");
81    }
82  }
83
84  #[test]
85  fn test_export_cbor() {
86    let input = [0x82, 0x01, 0x02];
87    let mut output = Vec::new();
88    export_from_reader("cbor", "\n", false, &input[..], &mut output).unwrap();
89    assert_eq!(output, b"\x82\x01\x02" as &[u8]);
90  }
91
92  #[test]
93  fn test_export_sequence() {
94    // Two CBOR items: map {"a":"a"} then string "a"
95    let input = [0xa1, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61];
96    let mut output = Vec::new();
97    export_from_reader("json", "\n", false, &input[..], &mut output).unwrap();
98    assert_eq!(output, b"{\"a\":\"a\"}\n\"a\"" as &[u8]);
99  }
100
101  #[test]
102  fn test_export_pretty_json() {
103    let cbor = [0x82, 0x01, 0x02];
104    let mut compact = Vec::new();
105    export_from_reader("json", "\n", false, &cbor[..], &mut compact).unwrap();
106
107    let mut pretty = Vec::new();
108    export_from_reader("json", "\n", true, &cbor[..], &mut pretty).unwrap();
109
110    assert_eq!(compact, b"[1,2]");
111    assert_ne!(pretty, compact);
112    assert!(pretty.windows(2).any(|w| w == b"\n "));
113  }
114}