1pub fn to_cbor<T: serde::Serialize>(value: &T) -> Vec<u8> {
9 let mut buf = Vec::new();
10 ciborium::into_writer(value, &mut buf).expect("CBOR serialization should not fail");
11 buf
12}
13
14pub fn from_cbor<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T, CborError> {
16 ciborium::from_reader(bytes).map_err(|e| CborError(format!("CBOR decode failed: {e}")))
17}
18
19pub fn json_to_cbor(value: &serde_json::Value) -> Result<Vec<u8>, CborError> {
21 let mut buf = Vec::new();
22 ciborium::into_writer(value, &mut buf)
23 .map_err(|e| CborError(format!("JSON→CBOR encode failed: {e}")))?;
24 Ok(buf)
25}
26
27pub fn cbor_to_json(bytes: &[u8]) -> Result<serde_json::Value, CborError> {
29 ciborium::from_reader(bytes).map_err(|e| CborError(format!("CBOR→JSON decode failed: {e}")))
30}
31
32pub fn decode_content_data(data: &[u8], mime_type: Option<&str>) -> serde_json::Value {
38 let mime = mime_type.unwrap_or("application/cbor");
39
40 if mime.starts_with("text/") || mime == "application/json" || mime == "application/xml" {
41 serde_json::Value::String(String::from_utf8_lossy(data).into_owned())
43 } else if mime == "application/cbor" {
44 cbor_to_json(data).unwrap_or_else(|_| {
46 use base64::Engine as _;
47 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(data))
48 })
49 } else {
50 use base64::Engine as _;
52 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(data))
53 }
54}
55
56#[derive(Debug, Clone)]
58pub struct CborError(pub String);
59
60impl std::fmt::Display for CborError {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.write_str(&self.0)
63 }
64}
65
66impl std::error::Error for CborError {}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use serde_json::json;
72
73 #[test]
74 fn roundtrip_object() {
75 let input = json!({"a": 2, "b": 3});
76 let cbor = json_to_cbor(&input).unwrap();
77 let output = cbor_to_json(&cbor).unwrap();
78 assert_eq!(input, output);
79 }
80
81 #[test]
82 fn roundtrip_nested() {
83 let input = json!({"config": {"api_key": "abc123"}, "values": [1, 2, 3]});
84 let cbor = json_to_cbor(&input).unwrap();
85 let output = cbor_to_json(&cbor).unwrap();
86 assert_eq!(input, output);
87 }
88
89 #[test]
90 fn roundtrip_null() {
91 let input = json!(null);
92 let cbor = json_to_cbor(&input).unwrap();
93 let output = cbor_to_json(&cbor).unwrap();
94 assert_eq!(input, output);
95 }
96
97 #[test]
98 fn empty_bytes_is_error() {
99 assert!(cbor_to_json(&[]).is_err());
100 }
101
102 #[test]
103 fn generic_roundtrip() {
104 let input = 42u64;
105 let bytes = to_cbor(&input);
106 let output: u64 = from_cbor(&bytes).unwrap();
107 assert_eq!(input, output);
108 }
109
110 #[test]
111 fn decode_text_content() {
112 let data = b"hello world";
113 let result = decode_content_data(data, Some("text/plain"));
114 assert_eq!(result, json!("hello world"));
115 }
116
117 #[test]
118 fn decode_cbor_content() {
119 let data = to_cbor(&json!({"key": "value"}));
120 let result = decode_content_data(&data, None);
121 assert_eq!(result, json!({"key": "value"}));
122 }
123
124 #[test]
125 fn decode_json_content_as_text() {
126 let data = br#"{"pets": [1, 2, 3]}"#;
127 let result = decode_content_data(data, Some("application/json"));
128 assert_eq!(result, json!(r#"{"pets": [1, 2, 3]}"#));
129 }
130
131 #[test]
132 fn decode_invalid_cbor_falls_back_to_base64() {
133 let data = b"\xff\xfe";
134 let result = decode_content_data(data, Some("application/octet-stream"));
135 assert!(result.is_string());
137 }
138
139 #[test]
140 fn decode_image_content_to_base64() {
141 let data = vec![0x89, 0x50, 0x4E, 0x47]; let result = decode_content_data(&data, Some("image/png"));
143 assert!(result.is_string());
144 use base64::Engine as _;
145 let decoded = base64::engine::general_purpose::STANDARD
146 .decode(result.as_str().unwrap())
147 .unwrap();
148 assert_eq!(decoded, data);
149 }
150
151 #[test]
152 fn decode_octet_stream_to_base64() {
153 let data = vec![0xFF, 0xFE, 0x00];
154 let result = decode_content_data(&data, Some("application/octet-stream"));
155 assert!(result.is_string());
156 use base64::Engine as _;
157 let decoded = base64::engine::general_purpose::STANDARD
158 .decode(result.as_str().unwrap())
159 .unwrap();
160 assert_eq!(decoded, data);
161 }
162
163 #[test]
164 fn decode_html_as_text() {
165 let data = b"<h1>Hello</h1>";
166 let result = decode_content_data(data, Some("text/html"));
167 assert_eq!(result, json!("<h1>Hello</h1>"));
168 }
169
170 #[test]
171 fn decode_xml_as_text() {
172 let data = b"<root><item/></root>";
173 let result = decode_content_data(data, Some("application/xml"));
174 assert_eq!(result, json!("<root><item/></root>"));
175 }
176}