use std::fs;
use std::io::{BufReader, BufWriter};
use crate::{Error, Job, Result};
pub fn cbor_to_json(job: &Job) -> Result<()> {
let file = fs::File::open(job.input)?;
let value: ciborium::value::Value =
ciborium::de::from_reader(BufReader::new(file))
.map_err(|e| Error::other(format!("cbor decode: {e}")))?;
let json = cbor_to_serde(value)?;
let out = serde_json::to_string_pretty(&json)
.map_err(|e| Error::other(format!("json serialize: {e}")))?;
fs::write(job.output, out + "\n")?;
Ok(())
}
pub fn json_to_cbor(job: &Job) -> Result<()> {
let raw = fs::read_to_string(job.input)?;
let value: serde_json::Value = serde_json::from_str(&raw)
.map_err(|e| Error::other(format!("invalid JSON: {e}")))?;
let cbor = serde_to_cbor(value);
let file = fs::File::create(job.output)?;
ciborium::ser::into_writer(&cbor, BufWriter::new(file))
.map_err(|e| Error::other(format!("cbor encode: {e}")))?;
Ok(())
}
fn cbor_to_serde(value: ciborium::value::Value) -> Result<serde_json::Value> {
use ciborium::value::Value as C;
use serde_json::Value as J;
Ok(match value {
C::Null => J::Null,
C::Bool(b) => J::Bool(b),
C::Integer(i) => {
let n: i128 = i.into();
if let Ok(v) = i64::try_from(n) {
J::Number(v.into())
} else if let Ok(v) = u64::try_from(n) {
J::Number(v.into())
} else {
J::String(n.to_string())
}
}
C::Float(f) => serde_json::Number::from_f64(f)
.map(J::Number)
.unwrap_or(J::Null),
C::Text(s) => J::String(s),
C::Bytes(b) => {
let mut hex = String::with_capacity(b.len() * 2);
for byte in b {
let _ =
std::fmt::Write::write_fmt(&mut hex, format_args!("{byte:02x}"));
}
J::String(hex)
}
C::Array(items) => J::Array(
items
.into_iter()
.map(cbor_to_serde)
.collect::<Result<Vec<_>>>()?,
),
C::Map(pairs) => {
let mut map = serde_json::Map::with_capacity(pairs.len());
for (k, v) in pairs {
let key = match k {
C::Text(s) => s,
other => serde_json::to_string(&cbor_to_serde(other)?)
.map_err(|e| Error::other(format!("cbor key: {e}")))?,
};
map.insert(key, cbor_to_serde(v)?);
}
J::Object(map)
}
C::Tag(_, inner) => cbor_to_serde(*inner)?,
_ => J::Null,
})
}
fn serde_to_cbor(value: serde_json::Value) -> ciborium::value::Value {
use ciborium::value::Value as C;
use serde_json::Value as J;
match value {
J::Null => C::Null,
J::Bool(b) => C::Bool(b),
J::Number(n) => {
if let Some(i) = n.as_i64() {
C::Integer(i.into())
} else if let Some(u) = n.as_u64() {
C::Integer(u.into())
} else {
C::Float(n.as_f64().unwrap_or(0.0))
}
}
J::String(s) => C::Text(s),
J::Array(items) => C::Array(items.into_iter().map(serde_to_cbor).collect()),
J::Object(map) => C::Map(
map
.into_iter()
.map(|(k, v)| (C::Text(k), serde_to_cbor(v)))
.collect(),
),
}
}