Skip to main content

nodedb_types/json_msgpack/
writer.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Msgpack serialization for `serde_json::Value` and `nodedb_types::Value`.
4
5use super::json_value::JsonValue;
6
7/// Serialize a `serde_json::Value` to MessagePack bytes.
8#[inline]
9pub fn json_to_msgpack(value: &serde_json::Value) -> zerompk::Result<Vec<u8>> {
10    zerompk::to_msgpack_vec(&JsonValue(value.clone()))
11}
12
13/// Serialize a `serde_json::Value` to MessagePack bytes, returning an empty
14/// msgpack map (`0x80`) on failure.
15///
16/// Suitable for filter evaluation where an empty map causes all field
17/// predicates to pass vacuously. Callers that need error propagation
18/// should use [`json_to_msgpack`] instead.
19#[inline]
20pub fn json_to_msgpack_or_empty(value: &serde_json::Value) -> Vec<u8> {
21    json_to_msgpack(value).unwrap_or_else(|_| vec![0x80])
22}
23
24/// Serialize a `nodedb_types::Value` to standard MessagePack bytes.
25///
26/// Writes standard msgpack format (fixmap 0x80-0x8F, fixstr 0xA0-0xBF, etc.)
27/// directly from `Value` — no zerompk tagged encoding.
28pub fn value_to_msgpack(value: &crate::Value) -> zerompk::Result<Vec<u8>> {
29    let mut buf = Vec::with_capacity(128);
30    write_native_value(&mut buf, value);
31    Ok(buf)
32}
33
34/// Write a `nodedb_types::Value` as standard msgpack bytes.
35fn write_native_value(buf: &mut Vec<u8>, value: &crate::Value) {
36    match value {
37        crate::Value::Null => buf.push(0xC0),
38        crate::Value::Bool(false) => buf.push(0xC2),
39        crate::Value::Bool(true) => buf.push(0xC3),
40        crate::Value::Integer(i) => write_native_int(buf, *i),
41        crate::Value::Float(f) => {
42            buf.push(0xCB);
43            buf.extend_from_slice(&f.to_be_bytes());
44        }
45        crate::Value::String(s)
46        | crate::Value::Uuid(s)
47        | crate::Value::Ulid(s)
48        | crate::Value::Regex(s) => write_native_str(buf, s),
49        crate::Value::Bytes(b) => write_native_bin(buf, b),
50        crate::Value::Array(arr) | crate::Value::Set(arr) => {
51            write_native_array_header(buf, arr.len());
52            for v in arr {
53                write_native_value(buf, v);
54            }
55        }
56        crate::Value::Object(map) => {
57            write_native_map_header(buf, map.len());
58            for (k, v) in map {
59                write_native_str(buf, k);
60                write_native_value(buf, v);
61            }
62        }
63        crate::Value::DateTime(dt) | crate::Value::NaiveDateTime(dt) => {
64            write_native_str(buf, &dt.to_string())
65        }
66        crate::Value::Duration(d) => write_native_str(buf, &d.to_string()),
67        crate::Value::Decimal(d) => write_native_str(buf, &d.to_string()),
68        crate::Value::Geometry(g) => {
69            if let Ok(s) = sonic_rs::to_string(g) {
70                write_native_str(buf, &s);
71            } else {
72                buf.push(0xC0);
73            }
74        }
75        crate::Value::Range { .. } | crate::Value::Record { .. } => buf.push(0xC0),
76        crate::Value::Vector(v) => {
77            // Encode as a standard msgpack array of float64 values so that
78            // pgwire clients receive a plain JSON number array.
79            write_native_array_header(buf, v.len());
80            for f in v.iter() {
81                buf.push(0xCB);
82                buf.extend_from_slice(&(*f as f64).to_be_bytes());
83            }
84        }
85        // ArrayCell is encoded as a `{coords:[...], attrs:[...]}` map so the
86        // pgwire `msgpack_to_json_string` transcoder produces clean JSON for
87        // clients reading slice/project rows. On audit-log (AS OF SYSTEM TIME
88        // NULL) reads each version carries its system-time, surfaced as the
89        // `_ts_system` column (mirrors the document-engine audit-log shape).
90        crate::Value::ArrayCell(cell) => {
91            let map_len = if cell.system_time.is_some() { 3 } else { 2 };
92            write_native_map_header(buf, map_len);
93            write_native_str(buf, "coords");
94            write_native_array_header(buf, cell.coords.len());
95            for v in &cell.coords {
96                write_native_value(buf, v);
97            }
98            write_native_str(buf, "attrs");
99            write_native_array_header(buf, cell.attrs.len());
100            for v in &cell.attrs {
101                write_native_value(buf, v);
102            }
103            if let Some(ts) = cell.system_time {
104                write_native_str(buf, "_ts_system");
105                write_native_int(buf, ts);
106            }
107        }
108    }
109}
110
111fn write_native_int(buf: &mut Vec<u8>, i: i64) {
112    if (0..=0x7F).contains(&i) {
113        buf.push(i as u8);
114    } else if (-32..0).contains(&i) {
115        buf.push(i as u8); // negative fixint
116    } else if i >= i8::MIN as i64 && i <= i8::MAX as i64 {
117        buf.push(0xD0);
118        buf.push(i as i8 as u8);
119    } else if i >= i16::MIN as i64 && i <= i16::MAX as i64 {
120        buf.push(0xD1);
121        buf.extend_from_slice(&(i as i16).to_be_bytes());
122    } else if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
123        buf.push(0xD2);
124        buf.extend_from_slice(&(i as i32).to_be_bytes());
125    } else {
126        buf.push(0xD3);
127        buf.extend_from_slice(&i.to_be_bytes());
128    }
129}
130
131fn write_native_str(buf: &mut Vec<u8>, s: &str) {
132    let len = s.len();
133    if len < 32 {
134        buf.push(0xA0 | len as u8);
135    } else if len <= u8::MAX as usize {
136        buf.push(0xD9);
137        buf.push(len as u8);
138    } else if len <= u16::MAX as usize {
139        buf.push(0xDA);
140        buf.extend_from_slice(&(len as u16).to_be_bytes());
141    } else {
142        buf.push(0xDB);
143        buf.extend_from_slice(&(len as u32).to_be_bytes());
144    }
145    buf.extend_from_slice(s.as_bytes());
146}
147
148fn write_native_bin(buf: &mut Vec<u8>, b: &[u8]) {
149    let len = b.len();
150    if len <= u8::MAX as usize {
151        buf.push(0xC4);
152        buf.push(len as u8);
153    } else if len <= u16::MAX as usize {
154        buf.push(0xC5);
155        buf.extend_from_slice(&(len as u16).to_be_bytes());
156    } else {
157        buf.push(0xC6);
158        buf.extend_from_slice(&(len as u32).to_be_bytes());
159    }
160    buf.extend_from_slice(b);
161}
162
163fn write_native_array_header(buf: &mut Vec<u8>, len: usize) {
164    if len < 16 {
165        buf.push(0x90 | len as u8);
166    } else if len <= u16::MAX as usize {
167        buf.push(0xDC);
168        buf.extend_from_slice(&(len as u16).to_be_bytes());
169    } else {
170        buf.push(0xDD);
171        buf.extend_from_slice(&(len as u32).to_be_bytes());
172    }
173}
174
175fn write_native_map_header(buf: &mut Vec<u8>, len: usize) {
176    if len < 16 {
177        buf.push(0x80 | len as u8);
178    } else if len <= u16::MAX as usize {
179        buf.push(0xDE);
180        buf.extend_from_slice(&(len as u16).to_be_bytes());
181    } else {
182        buf.push(0xDF);
183        buf.extend_from_slice(&(len as u32).to_be_bytes());
184    }
185}