pub fn to_cbor<T: serde::Serialize>(value: &T) -> Vec<u8> {
let mut buf = Vec::new();
ciborium::into_writer(value, &mut buf).expect("CBOR serialization should not fail");
buf
}
pub fn from_cbor<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T, CborError> {
ciborium::from_reader(bytes).map_err(|e| CborError(format!("CBOR decode failed: {e}")))
}
pub fn json_to_cbor(value: &serde_json::Value) -> Result<Vec<u8>, CborError> {
let mut buf = Vec::new();
ciborium::into_writer(value, &mut buf)
.map_err(|e| CborError(format!("JSON→CBOR encode failed: {e}")))?;
Ok(buf)
}
pub fn cbor_to_json(bytes: &[u8]) -> Result<serde_json::Value, CborError> {
ciborium::from_reader(bytes).map_err(|e| CborError(format!("CBOR→JSON decode failed: {e}")))
}
pub fn decode_content_data(data: &[u8], mime_type: Option<&str>) -> serde_json::Value {
let mime = mime_type.unwrap_or("application/cbor");
if mime.starts_with("text/") || mime == "application/json" || mime == "application/xml" {
serde_json::Value::String(String::from_utf8_lossy(data).into_owned())
} else if mime == "application/cbor" {
cbor_to_json(data).unwrap_or_else(|_| {
use base64::Engine as _;
serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(data))
})
} else {
use base64::Engine as _;
serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(data))
}
}
#[derive(Debug, Clone)]
pub struct CborError(pub String);
impl std::fmt::Display for CborError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for CborError {}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn roundtrip_object() {
let input = json!({"a": 2, "b": 3});
let cbor = json_to_cbor(&input).unwrap();
let output = cbor_to_json(&cbor).unwrap();
assert_eq!(input, output);
}
#[test]
fn roundtrip_nested() {
let input = json!({"config": {"api_key": "abc123"}, "values": [1, 2, 3]});
let cbor = json_to_cbor(&input).unwrap();
let output = cbor_to_json(&cbor).unwrap();
assert_eq!(input, output);
}
#[test]
fn roundtrip_null() {
let input = json!(null);
let cbor = json_to_cbor(&input).unwrap();
let output = cbor_to_json(&cbor).unwrap();
assert_eq!(input, output);
}
#[test]
fn empty_bytes_is_error() {
assert!(cbor_to_json(&[]).is_err());
}
#[test]
fn generic_roundtrip() {
let input = 42u64;
let bytes = to_cbor(&input);
let output: u64 = from_cbor(&bytes).unwrap();
assert_eq!(input, output);
}
#[test]
fn decode_text_content() {
let data = b"hello world";
let result = decode_content_data(data, Some("text/plain"));
assert_eq!(result, json!("hello world"));
}
#[test]
fn decode_cbor_content() {
let data = to_cbor(&json!({"key": "value"}));
let result = decode_content_data(&data, None);
assert_eq!(result, json!({"key": "value"}));
}
#[test]
fn decode_json_content_as_text() {
let data = br#"{"pets": [1, 2, 3]}"#;
let result = decode_content_data(data, Some("application/json"));
assert_eq!(result, json!(r#"{"pets": [1, 2, 3]}"#));
}
#[test]
fn decode_invalid_cbor_falls_back_to_base64() {
let data = b"\xff\xfe";
let result = decode_content_data(data, Some("application/octet-stream"));
assert!(result.is_string());
}
#[test]
fn decode_image_content_to_base64() {
let data = vec![0x89, 0x50, 0x4E, 0x47]; let result = decode_content_data(&data, Some("image/png"));
assert!(result.is_string());
use base64::Engine as _;
let decoded = base64::engine::general_purpose::STANDARD
.decode(result.as_str().unwrap())
.unwrap();
assert_eq!(decoded, data);
}
#[test]
fn decode_octet_stream_to_base64() {
let data = vec![0xFF, 0xFE, 0x00];
let result = decode_content_data(&data, Some("application/octet-stream"));
assert!(result.is_string());
use base64::Engine as _;
let decoded = base64::engine::general_purpose::STANDARD
.decode(result.as_str().unwrap())
.unwrap();
assert_eq!(decoded, data);
}
#[test]
fn decode_html_as_text() {
let data = b"<h1>Hello</h1>";
let result = decode_content_data(data, Some("text/html"));
assert_eq!(result, json!("<h1>Hello</h1>"));
}
#[test]
fn decode_xml_as_text() {
let data = b"<root><item/></root>";
let result = decode_content_data(data, Some("application/xml"));
assert_eq!(result, json!("<root><item/></root>"));
}
}