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;

/// Convert a stream of some other format into a stream of CBOR bytes.
///
/// Each top-level item in the input is serialized to CBOR and written to
/// `writer`. The input is never buffered in memory — items are streamed one at
/// a time.
///
/// # Arguments
///
/// - `input_format`: one of `"json"`, `"yaml"`, `"toml"`, `"cbor"`. A `"cbor"`
///   input is re-emitted item-for-item (passthrough/normalization).
/// - `reader`: the input byte stream. Multiple concatenated top-level items
///   are supported for every format that allows it (all except yaml/toml,
///   which consume the whole stream as one value).
/// - `writer`: the CBOR output sink.
///
/// # Example
///
/// ```
/// use cbor_cli::import::import_from_reader;
///
/// let json = br#"{"key": "value"}"#;
/// let mut cbor = Vec::new();
/// import_from_reader("json", &json[..], &mut cbor).unwrap();
/// assert_eq!(cbor[0], 0xa1); // CBOR map header (major type 5, length 1)
/// ```
pub fn import_from_reader<R: io::Read, W: io::Write>(
  input_format: &str,
  mut reader: R,
  writer: &mut W,
) -> anyhow::Result<()> {
  match input_format {
    "json" => {
      for v in serde_json::de::Deserializer::from_reader(reader).into_iter::<serde_json::Value>() {
        let v = v?;
        cbor2::to_writer(&v, &mut *writer)?;
      }
    }
    "yaml" => {
      let result: serde_yaml::Value = serde_yaml::from_reader(reader)?;
      cbor2::to_writer(&result, writer)?;
    }
    "toml" => {
      let mut s = String::new();
      reader.read_to_string(&mut s)?;
      let result: toml::Value = toml::de::from_str(&s)?;
      cbor2::to_writer(&result, writer)?;
    }
    _ => {
      // CBOR passthrough: decode each consecutive top-level item and re-emit it.
      for v in Deserializer::from_reader(reader).into_iter::<CborValue>() {
        let v = v?;
        cbor2::to_writer(&v, &mut *writer)?;
      }
    }
  }
  Ok(())
}

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

  #[test]
  fn test_import_array() {
    let cases = [
      ("json", &b"[1,2]"[..], vec![0x82, 0x01, 0x02]),
      ("yaml", &b"- 1\n- 2\n"[..], vec![0x82, 0x01, 0x02]),
      ("cbor", &b"\x82\x01\x02"[..], vec![0x82, 0x01, 0x02]),
    ];
    for (format, input, expected) in cases {
      let mut output = Vec::new();
      import_from_reader(format, input, &mut output).unwrap();
      assert_eq!(output, expected, "format: {format}");
    }
  }

  #[test]
  fn test_import_map_from_toml() {
    let input = b"foo = 1\nbar = 2\n";
    let mut output = Vec::new();
    import_from_reader("toml", &input[..], &mut output).unwrap();
    // Expected hex: a2 63 66 6f 6f 01 63 62 61 72 02
    assert_eq!(output, vec![0xa2, 0x63, 0x66, 0x6f, 0x6f, 0x01, 0x63, 0x62, 0x61, 0x72, 0x02]);
  }

  #[test]
  fn test_import_invalid_json() {
    let input = b"not json";
    let mut output = Vec::new();
    let err = import_from_reader("json", &input[..], &mut output).unwrap_err();
    assert!(!err.to_string().is_empty(), "error should be non-empty: {err}");
  }
}