Skip to main content

nodedb_types/json_msgpack/
transcoder.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Streaming msgpack → JSON string transcoder.
4//!
5//! Walks msgpack bytes and writes JSON text directly into a String.
6//! No intermediate `serde_json::Value` or `nodedb_types::Value`.
7//! Used ONLY at the outermost pgwire/HTTP layer for client compatibility.
8
9use std::fmt::Write as _;
10
11use super::reader::{Cursor, base64_encode};
12
13/// Transcode raw msgpack bytes to a JSON string without intermediate types.
14pub fn msgpack_to_json_string(bytes: &[u8]) -> zerompk::Result<String> {
15    if bytes.is_empty() {
16        return Ok(String::new());
17    }
18    let mut c = Cursor::new(bytes);
19    let mut out = String::with_capacity(bytes.len() * 2);
20    transcode_value(&mut c, &mut out)?;
21    Ok(out)
22}
23
24fn transcode_value(c: &mut Cursor<'_>, out: &mut String) -> zerompk::Result<()> {
25    if c.depth > 500 {
26        return Err(zerompk::Error::DepthLimitExceeded { max: 500 });
27    }
28
29    let marker = c.take()?;
30    match marker {
31        0xC0 => out.push_str("null"),
32        0xC2 => out.push_str("false"),
33        0xC3 => out.push_str("true"),
34
35        0x00..=0x7F => {
36            write_int(out, marker as i64);
37        }
38        0xE0..=0xFF => {
39            write_int(out, marker as i8 as i64);
40        }
41
42        0xCC => {
43            write_uint(out, c.take()? as u64);
44        }
45        0xCD => {
46            write_uint(out, c.read_u16_be()? as u64);
47        }
48        0xCE => {
49            write_uint(out, c.read_u32_be()? as u64);
50        }
51        0xCF => {
52            let b = c.take_n(8)?;
53            write_uint(
54                out,
55                u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]),
56            );
57        }
58
59        0xD0 => {
60            write_int(out, c.take()? as i8 as i64);
61        }
62        0xD1 => {
63            let b = c.take_n(2)?;
64            write_int(out, i16::from_be_bytes([b[0], b[1]]) as i64);
65        }
66        0xD2 => {
67            let b = c.take_n(4)?;
68            write_int(out, i32::from_be_bytes([b[0], b[1], b[2], b[3]]) as i64);
69        }
70        0xD3 => {
71            let b = c.take_n(8)?;
72            write_int(
73                out,
74                i64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]),
75            );
76        }
77
78        0xCA => {
79            let b = c.take_n(4)?;
80            write_float(out, f32::from_be_bytes([b[0], b[1], b[2], b[3]]) as f64);
81        }
82        0xCB => {
83            let b = c.take_n(8)?;
84            write_float(
85                out,
86                f64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]),
87            );
88        }
89
90        m @ 0xA0..=0xBF => transcode_str(c, out, (m & 0x1F) as usize)?,
91        0xD9 => {
92            let l = c.take()? as usize;
93            transcode_str(c, out, l)?;
94        }
95        0xDA => {
96            let l = c.read_u16_be()? as usize;
97            transcode_str(c, out, l)?;
98        }
99        0xDB => {
100            let l = c.read_u32_be()? as usize;
101            transcode_str(c, out, l)?;
102        }
103
104        0xC4 => {
105            let l = c.take()? as usize;
106            transcode_bin(c, out, l)?;
107        }
108        0xC5 => {
109            let l = c.read_u16_be()? as usize;
110            transcode_bin(c, out, l)?;
111        }
112        0xC6 => {
113            let l = c.read_u32_be()? as usize;
114            transcode_bin(c, out, l)?;
115        }
116
117        m @ 0x90..=0x9F => transcode_array(c, out, (m & 0x0F) as usize)?,
118        0xDC => {
119            let l = c.read_u16_be()? as usize;
120            transcode_array(c, out, l)?;
121        }
122        0xDD => {
123            let l = c.read_u32_be()? as usize;
124            transcode_array(c, out, l)?;
125        }
126
127        m @ 0x80..=0x8F => transcode_map(c, out, (m & 0x0F) as usize)?,
128        0xDE => {
129            let l = c.read_u16_be()? as usize;
130            transcode_map(c, out, l)?;
131        }
132        0xDF => {
133            let l = c.read_u32_be()? as usize;
134            transcode_map(c, out, l)?;
135        }
136
137        // ext types — render as null
138        0xD4 => {
139            c.take_n(2)?;
140            out.push_str("null");
141        }
142        0xD5 => {
143            c.take_n(3)?;
144            out.push_str("null");
145        }
146        0xD6 => {
147            c.take_n(5)?;
148            out.push_str("null");
149        }
150        0xD7 => {
151            c.take_n(9)?;
152            out.push_str("null");
153        }
154        0xD8 => {
155            c.take_n(17)?;
156            out.push_str("null");
157        }
158        0xC7 => {
159            let l = c.take()? as usize;
160            c.take_n(1 + l)?;
161            out.push_str("null");
162        }
163        0xC8 => {
164            let l = c.read_u16_be()? as usize;
165            c.take_n(1 + l)?;
166            out.push_str("null");
167        }
168        0xC9 => {
169            let l = c.read_u32_be()? as usize;
170            c.take_n(1 + l)?;
171            out.push_str("null");
172        }
173
174        _ => return Err(zerompk::Error::InvalidMarker(marker)),
175    }
176    Ok(())
177}
178
179fn write_int(out: &mut String, v: i64) {
180    let _ = write!(out, "{v}");
181}
182
183fn write_uint(out: &mut String, v: u64) {
184    let _ = write!(out, "{v}");
185}
186
187fn write_float(out: &mut String, v: f64) {
188    if v.is_nan() || v.is_infinite() {
189        out.push_str("null");
190    } else if v.fract() == 0.0 && v.abs() < (1i64 << 53) as f64 {
191        let _ = write!(out, "{v:.1}");
192    } else {
193        let _ = write!(out, "{v}");
194    }
195}
196
197fn transcode_str(c: &mut Cursor<'_>, out: &mut String, len: usize) -> zerompk::Result<()> {
198    let bytes = c.take_n(len)?;
199    let s = std::str::from_utf8(bytes).map_err(|_| zerompk::Error::InvalidMarker(0))?;
200    out.push('"');
201    for ch in s.chars() {
202        match ch {
203            '"' => out.push_str("\\\""),
204            '\\' => out.push_str("\\\\"),
205            '\n' => out.push_str("\\n"),
206            '\r' => out.push_str("\\r"),
207            '\t' => out.push_str("\\t"),
208            c if c < '\x20' => {
209                let _ = write!(out, "\\u{:04x}", c as u32);
210            }
211            c => out.push(c),
212        }
213    }
214    out.push('"');
215    Ok(())
216}
217
218fn transcode_bin(c: &mut Cursor<'_>, out: &mut String, len: usize) -> zerompk::Result<()> {
219    let bytes = c.take_n(len)?;
220    out.push('"');
221    out.push_str(&base64_encode(bytes));
222    out.push('"');
223    Ok(())
224}
225
226fn transcode_array(c: &mut Cursor<'_>, out: &mut String, len: usize) -> zerompk::Result<()> {
227    c.depth += 1;
228    out.push('[');
229    for i in 0..len {
230        if i > 0 {
231            out.push(',');
232        }
233        transcode_value(c, out)?;
234    }
235    out.push(']');
236    c.depth -= 1;
237    Ok(())
238}
239
240fn transcode_map(c: &mut Cursor<'_>, out: &mut String, len: usize) -> zerompk::Result<()> {
241    c.depth += 1;
242    out.push('{');
243    for i in 0..len {
244        if i > 0 {
245            out.push(',');
246        }
247        let key_marker = c.peek()?;
248        if (0xA0..=0xBF).contains(&key_marker)
249            || key_marker == 0xD9
250            || key_marker == 0xDA
251            || key_marker == 0xDB
252        {
253            transcode_value(c, out)?;
254        } else {
255            let mut tmp = String::new();
256            transcode_value(c, &mut tmp)?;
257            out.push('"');
258            out.push_str(&tmp);
259            out.push('"');
260        }
261        out.push(':');
262        transcode_value(c, out)?;
263    }
264    out.push('}');
265    c.depth -= 1;
266    Ok(())
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272    use crate::json_msgpack::writer::json_to_msgpack;
273
274    #[test]
275    fn basic_map() {
276        let val = serde_json::json!({"name": "alice", "age": 30, "active": true});
277        let mp = json_to_msgpack(&val).unwrap();
278        let json_str = msgpack_to_json_string(&mp).unwrap();
279        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
280        assert_eq!(parsed["name"], "alice");
281        assert_eq!(parsed["age"], 30);
282        assert_eq!(parsed["active"], true);
283    }
284
285    #[test]
286    fn array() {
287        let val = serde_json::json!([1, "two", null, false]);
288        let mp = json_to_msgpack(&val).unwrap();
289        let json_str = msgpack_to_json_string(&mp).unwrap();
290        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
291        assert_eq!(parsed, val);
292    }
293
294    #[test]
295    fn escaping() {
296        let val = serde_json::json!({"msg": "hello \"world\"\nnewline"});
297        let mp = json_to_msgpack(&val).unwrap();
298        let json_str = msgpack_to_json_string(&mp).unwrap();
299        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
300        assert_eq!(parsed["msg"], "hello \"world\"\nnewline");
301    }
302
303    #[test]
304    fn empty() {
305        assert_eq!(msgpack_to_json_string(&[]).unwrap(), "");
306    }
307
308    #[test]
309    fn nested() {
310        let val = serde_json::json!({"a": {"b": [1, 2, {"c": 3}]}});
311        let mp = json_to_msgpack(&val).unwrap();
312        let json_str = msgpack_to_json_string(&mp).unwrap();
313        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
314        assert_eq!(parsed, val);
315    }
316}