cbor-cli 0.7.0

Encode, decode, and inspect CBOR (RFC 8949) from the command line. Convert between CBOR, JSON, YAML, and TOML via serde, with streaming support for CBOR sequences.
Documentation
use std::io;

use cbor2::de::Deserializer;
use cbor2::value::Value as CborValue;
use anyhow::Context;

/// Adapter that bridges `std::io::Write` to `std::fmt::Write`, so a `Display`
/// value can be formatted straight onto an I/O sink with no intermediate
/// `String` allocation.
struct FmtToIo<W: io::Write>(W);

impl<W: io::Write> std::fmt::Write for FmtToIo<W> {
  fn write_str(&mut self, s: &str) -> std::fmt::Result {
    self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error)
  }
}

/// Deep inspection of CBOR files.
///
/// Decodes each top-level CBOR item and writes its Concise Diagnostic Notation
/// (CDN, RFC 8949 §8) representation followed by a newline. CDN is a
/// human-readable, standard rendering of CBOR: byte strings appear as
/// `h'...'`, tags and bignums are rendered faithfully, and the output is more
/// compact and standard than Rust's `{:?}` debug format.
///
/// Reads from `reader` in a fully streaming fashion — one item at a time — so
/// the input is never buffered in memory. Each item's diagnostic text is
/// written straight to `writer` with no intermediate allocation on this side
/// of the crate boundary.
pub fn inspect_from_reader<R: io::Read, W: io::Write>(reader: R, mut writer: W) -> anyhow::Result<()> {
  for v in Deserializer::from_reader(reader).into_iter::<CborValue>() {
    let v = v?;
    use std::fmt::Write as _;
    write!(FmtToIo(&mut writer), "{v}").context("failed to write diagnostic notation")?;
    writer.write_all(b"\n")?;
  }
  Ok(())
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_inspect() {
    let cases: &[(&[u8], &[u8])] = &[
      (&[0x82, 0x01, 0x02], b"[1, 2]\n"),
      (&[0xa1, 0x61, 0x61, 0x61, 0x62], b"{\"a\": \"b\"}\n"),
      (&[0x42, 0x01, 0x02], b"h'0102'\n"),
      (&[0x01, 0x02], b"1\n2\n"),
    ];
    for &(input, expected) in cases {
      let mut output = Vec::new();
      inspect_from_reader(input, &mut output).unwrap();
      assert_eq!(output, expected, "input: {input:02x?}");
    }
  }
}