Skip to main content

reddb_server/
serde_json.rs

1use crate::utils::json::{parse_json, JsonValue};
2use std::collections::{BTreeMap, HashMap};
3use std::fmt;
4use std::ops::{Index, IndexMut};
5
6pub type Map<K, V> = BTreeMap<K, V>;
7
8#[derive(Debug, Clone, PartialEq)]
9pub enum Value {
10    Null,
11    Bool(bool),
12    Number(f64),
13    String(String),
14    Array(Vec<Value>),
15    Object(Map<String, Value>),
16}
17
18impl fmt::Display for Value {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        f.write_str(&self.to_string_compact())
21    }
22}
23
24impl Value {
25    pub fn as_str(&self) -> Option<&str> {
26        match self {
27            Value::String(s) => Some(s.as_str()),
28            _ => None,
29        }
30    }
31
32    pub fn as_f64(&self) -> Option<f64> {
33        match self {
34            Value::Number(n) => Some(*n),
35            _ => None,
36        }
37    }
38
39    pub fn as_i64(&self) -> Option<i64> {
40        match self {
41            Value::Number(n) => Some(*n as i64),
42            _ => None,
43        }
44    }
45
46    pub fn as_u64(&self) -> Option<u64> {
47        match self {
48            Value::Number(n) if *n >= 0.0 => Some(*n as u64),
49            _ => None,
50        }
51    }
52
53    pub fn as_bool(&self) -> Option<bool> {
54        match self {
55            Value::Bool(b) => Some(*b),
56            _ => None,
57        }
58    }
59
60    pub fn as_array(&self) -> Option<&[Value]> {
61        match self {
62            Value::Array(values) => Some(values.as_slice()),
63            _ => None,
64        }
65    }
66
67    pub fn as_object(&self) -> Option<&Map<String, Value>> {
68        match self {
69            Value::Object(map) => Some(map),
70            _ => None,
71        }
72    }
73
74    pub fn get(&self, key: &str) -> Option<&Value> {
75        if let Value::Object(map) = self {
76            map.get(key)
77        } else {
78            None
79        }
80    }
81
82    pub fn to_string_compact(&self) -> String {
83        let mut out = String::new();
84        self.write_compact(&mut out);
85        out
86    }
87
88    pub fn to_string_pretty(&self) -> String {
89        let mut out = String::new();
90        self.write_pretty(&mut out, 0);
91        out
92    }
93
94    fn write_compact(&self, out: &mut String) {
95        match self {
96            Value::Null => out.push_str("null"),
97            Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
98            Value::Number(n) => {
99                if n.fract() == 0.0 {
100                    out.push_str(&format!("{}", *n as i64));
101                } else {
102                    out.push_str(&format!("{}", n));
103                }
104            }
105            Value::String(s) => {
106                out.push('"');
107                out.push_str(&escape_string(s));
108                out.push('"');
109            }
110            Value::Array(values) => {
111                out.push('[');
112                for (idx, value) in values.iter().enumerate() {
113                    if idx > 0 {
114                        out.push(',');
115                    }
116                    value.write_compact(out);
117                }
118                out.push(']');
119            }
120            Value::Object(map) => {
121                out.push('{');
122                for (idx, (key, value)) in map.iter().enumerate() {
123                    if idx > 0 {
124                        out.push(',');
125                    }
126                    out.push('"');
127                    out.push_str(&escape_string(key));
128                    out.push('"');
129                    out.push(':');
130                    value.write_compact(out);
131                }
132                out.push('}');
133            }
134        }
135    }
136
137    fn write_pretty(&self, out: &mut String, indent: usize) {
138        match self {
139            Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {
140                out.push_str(&self.to_string_compact());
141            }
142            Value::Array(values) => {
143                out.push('[');
144                if !values.is_empty() {
145                    out.push('\n');
146                    for (idx, value) in values.iter().enumerate() {
147                        if idx > 0 {
148                            out.push_str(",\n");
149                        }
150                        out.push_str(&"  ".repeat(indent + 1));
151                        value.write_pretty(out, indent + 1);
152                    }
153                    out.push('\n');
154                    out.push_str(&"  ".repeat(indent));
155                }
156                out.push(']');
157            }
158            Value::Object(map) => {
159                out.push('{');
160                if !map.is_empty() {
161                    out.push('\n');
162                    for (idx, (key, value)) in map.iter().enumerate() {
163                        if idx > 0 {
164                            out.push_str(",\n");
165                        }
166                        out.push_str(&"  ".repeat(indent + 1));
167                        out.push('"');
168                        out.push_str(&escape_string(key));
169                        out.push_str("\": ");
170                        value.write_pretty(out, indent + 1);
171                    }
172                    out.push('\n');
173                    out.push_str(&"  ".repeat(indent));
174                }
175                out.push('}');
176            }
177        }
178    }
179}
180
181fn escape_string(input: &str) -> String {
182    // RFC 8259 §7: all control bytes (U+0000..U+001F), `"`, and `\` MUST be escaped.
183    // Previous version silently dropped control bytes other than \n \r \t — see
184    // F-01 in docs/security/serialization-boundary-audit-2026-05-06.md and
185    // ADR 0010 (serialization-boundary discipline).
186    use std::fmt::Write as _;
187    let mut out = String::with_capacity(input.len());
188    for ch in input.chars() {
189        match ch {
190            '"' => out.push_str("\\\""),
191            '\\' => out.push_str("\\\\"),
192            '\n' => out.push_str("\\n"),
193            '\r' => out.push_str("\\r"),
194            '\t' => out.push_str("\\t"),
195            '\u{08}' => out.push_str("\\b"),
196            '\u{0C}' => out.push_str("\\f"),
197            c if (c as u32) < 0x20 => {
198                let _ = write!(out, "\\u{:04x}", c as u32);
199            }
200            c => out.push(c),
201        }
202    }
203    out
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    fn encode(s: &str) -> String {
211        Value::String(s.to_string()).to_string_compact()
212    }
213
214    /// Every byte 0x00..0x20 must produce a valid JSON string that round-trips
215    /// through a real JSON parser preserving the original byte.
216    #[test]
217    fn escape_string_handles_every_control_byte() {
218        for byte in 0x00u8..0x20 {
219            let original: String = std::char::from_u32(byte as u32).unwrap().to_string();
220            let encoded = encode(&original);
221            // Must parse back to the exact same byte (NOT silently dropped).
222            let parsed: String = from_str(&encoded).unwrap_or_else(|err| {
223                panic!("byte 0x{byte:02x} encoded as {encoded:?} failed to parse: {err}")
224            });
225            assert_eq!(
226                parsed, original,
227                "byte 0x{byte:02x} did not round-trip (encoded={encoded:?})"
228            );
229        }
230    }
231
232    #[test]
233    fn escape_string_handles_standard_escapes() {
234        assert_eq!(encode("\""), "\"\\\"\"");
235        assert_eq!(encode("\\"), "\"\\\\\"");
236        assert_eq!(encode("\n"), "\"\\n\"");
237        assert_eq!(encode("\r"), "\"\\r\"");
238        assert_eq!(encode("\t"), "\"\\t\"");
239        assert_eq!(encode("\u{08}"), "\"\\b\"");
240        assert_eq!(encode("\u{0C}"), "\"\\f\"");
241    }
242
243    #[test]
244    fn escape_string_handles_mixed_payload() {
245        let input = "name=\"x\"\n\\path\t\x01end";
246        let encoded = encode(input);
247        let parsed: String = from_str(&encoded).expect("mixed payload must parse");
248        assert_eq!(parsed, input);
249    }
250
251    /// Regression test for F-01: the "self-disagreeing audit log" exploit.
252    /// An attacker writes audit data containing \x01. The old encoder
253    /// silently dropped \x01, so a downstream auditor that re-parses the
254    /// JSONL would see a different record than what was emitted. The fix
255    /// must encode \x01 as  so it survives the round trip.
256    #[test]
257    fn audit_log_preserves_low_control_bytes() {
258        let payload = "collection\x01name\x07with\x1fbells";
259        let encoded = encode(payload);
260
261        // Encoded form must contain explicit \u escapes — NOT raw control bytes,
262        // NOT silent drops.
263        assert!(
264            encoded.contains("\\u0001"),
265            "expected \\u0001 escape in {encoded:?}"
266        );
267        assert!(
268            encoded.contains("\\u0007"),
269            "expected \\u0007 escape in {encoded:?}"
270        );
271        assert!(
272            encoded.contains("\\u001f"),
273            "expected \\u001f escape in {encoded:?}"
274        );
275        assert!(
276            !encoded.contains('\x01'),
277            "raw \\x01 must not appear in encoded output"
278        );
279
280        // Round trip through the in-house parser must reproduce the original bytes.
281        let parsed: String = from_str(&encoded).expect("audit payload must parse");
282        assert_eq!(parsed, payload);
283    }
284}
285
286impl From<JsonValue> for Value {
287    fn from(value: JsonValue) -> Self {
288        match value {
289            JsonValue::Null => Value::Null,
290            JsonValue::Bool(b) => Value::Bool(b),
291            JsonValue::Number(n) => Value::Number(n),
292            JsonValue::String(s) => Value::String(s),
293            JsonValue::Array(values) => Value::Array(values.into_iter().map(Value::from).collect()),
294            JsonValue::Object(entries) => {
295                let mut map = Map::new();
296                for (k, v) in entries {
297                    map.insert(k, Value::from(v));
298                }
299                Value::Object(map)
300            }
301        }
302    }
303}
304
305impl Index<&str> for Value {
306    type Output = Value;
307
308    fn index(&self, key: &str) -> &Self::Output {
309        static NULL: Value = Value::Null;
310        match self {
311            Value::Object(map) => map.get(key).unwrap_or(&NULL),
312            _ => &NULL,
313        }
314    }
315}
316
317impl IndexMut<&str> for Value {
318    fn index_mut(&mut self, key: &str) -> &mut Self::Output {
319        match self {
320            Value::Object(map) => map.entry(key.to_string()).or_insert(Value::Null),
321            _ => {
322                *self = Value::Object(Map::new());
323                match self {
324                    Value::Object(map) => map.entry(key.to_string()).or_insert(Value::Null),
325                    _ => unreachable!(),
326                }
327            }
328        }
329    }
330}
331
332pub trait JsonEncode {
333    fn to_json_value(&self) -> Value;
334}
335
336impl<T: JsonEncode + ?Sized> JsonEncode for &T {
337    fn to_json_value(&self) -> Value {
338        (*self).to_json_value()
339    }
340}
341
342pub trait JsonDecode: Sized {
343    fn from_json_value(value: Value) -> Result<Self, String>;
344}
345
346impl JsonEncode for Value {
347    fn to_json_value(&self) -> Value {
348        self.clone()
349    }
350}
351
352impl JsonDecode for Value {
353    fn from_json_value(value: Value) -> Result<Self, String> {
354        Ok(value)
355    }
356}
357
358impl JsonEncode for bool {
359    fn to_json_value(&self) -> Value {
360        Value::Bool(*self)
361    }
362}
363
364impl JsonEncode for i64 {
365    fn to_json_value(&self) -> Value {
366        Value::Number(*self as f64)
367    }
368}
369
370impl JsonEncode for i32 {
371    fn to_json_value(&self) -> Value {
372        Value::Number(*self as f64)
373    }
374}
375
376impl JsonEncode for u8 {
377    fn to_json_value(&self) -> Value {
378        Value::Number(*self as f64)
379    }
380}
381
382impl JsonEncode for u16 {
383    fn to_json_value(&self) -> Value {
384        Value::Number(*self as f64)
385    }
386}
387
388impl JsonEncode for u32 {
389    fn to_json_value(&self) -> Value {
390        Value::Number(*self as f64)
391    }
392}
393
394impl JsonEncode for u64 {
395    fn to_json_value(&self) -> Value {
396        Value::Number(*self as f64)
397    }
398}
399
400impl JsonEncode for usize {
401    fn to_json_value(&self) -> Value {
402        Value::Number(*self as f64)
403    }
404}
405
406impl JsonEncode for f64 {
407    fn to_json_value(&self) -> Value {
408        Value::Number(*self)
409    }
410}
411
412impl JsonEncode for f32 {
413    fn to_json_value(&self) -> Value {
414        Value::Number(*self as f64)
415    }
416}
417
418impl JsonEncode for String {
419    fn to_json_value(&self) -> Value {
420        Value::String(self.clone())
421    }
422}
423
424impl JsonEncode for &str {
425    fn to_json_value(&self) -> Value {
426        Value::String(self.to_string())
427    }
428}
429
430impl<'a> JsonEncode for std::borrow::Cow<'a, str> {
431    fn to_json_value(&self) -> Value {
432        Value::String(self.to_string())
433    }
434}
435
436impl<T: JsonEncode> JsonEncode for Vec<T> {
437    fn to_json_value(&self) -> Value {
438        Value::Array(self.iter().map(|v| v.to_json_value()).collect())
439    }
440}
441
442impl<T: JsonEncode> JsonEncode for [T] {
443    fn to_json_value(&self) -> Value {
444        Value::Array(self.iter().map(|v| v.to_json_value()).collect())
445    }
446}
447
448impl<T: JsonEncode> JsonEncode for Option<T> {
449    fn to_json_value(&self) -> Value {
450        match self {
451            Some(value) => value.to_json_value(),
452            None => Value::Null,
453        }
454    }
455}
456
457impl<const N: usize> JsonEncode for [u8; N] {
458    fn to_json_value(&self) -> Value {
459        Value::Array(self.iter().map(|b| Value::Number(*b as f64)).collect())
460    }
461}
462
463impl<T: JsonEncode> JsonEncode for HashMap<String, T> {
464    fn to_json_value(&self) -> Value {
465        let mut map = Map::new();
466        for (k, v) in self {
467            map.insert(k.clone(), v.to_json_value());
468        }
469        Value::Object(map)
470    }
471}
472
473impl JsonDecode for String {
474    fn from_json_value(value: Value) -> Result<Self, String> {
475        match value {
476            Value::String(s) => Ok(s),
477            _ => Err("expected string".to_string()),
478        }
479    }
480}
481
482impl JsonDecode for bool {
483    fn from_json_value(value: Value) -> Result<Self, String> {
484        match value {
485            Value::Bool(b) => Ok(b),
486            _ => Err("expected bool".to_string()),
487        }
488    }
489}
490
491impl JsonDecode for u8 {
492    fn from_json_value(value: Value) -> Result<Self, String> {
493        match value {
494            Value::Number(n) => Ok(n as u8),
495            _ => Err("expected number".to_string()),
496        }
497    }
498}
499
500impl JsonDecode for u16 {
501    fn from_json_value(value: Value) -> Result<Self, String> {
502        match value {
503            Value::Number(n) => Ok(n as u16),
504            _ => Err("expected number".to_string()),
505        }
506    }
507}
508
509impl JsonDecode for u32 {
510    fn from_json_value(value: Value) -> Result<Self, String> {
511        match value {
512            Value::Number(n) => Ok(n as u32),
513            _ => Err("expected number".to_string()),
514        }
515    }
516}
517
518impl JsonDecode for u64 {
519    fn from_json_value(value: Value) -> Result<Self, String> {
520        match value {
521            Value::Number(n) => Ok(n as u64),
522            _ => Err("expected number".to_string()),
523        }
524    }
525}
526
527impl JsonDecode for usize {
528    fn from_json_value(value: Value) -> Result<Self, String> {
529        match value {
530            Value::Number(n) => Ok(n as usize),
531            _ => Err("expected number".to_string()),
532        }
533    }
534}
535
536impl JsonDecode for i64 {
537    fn from_json_value(value: Value) -> Result<Self, String> {
538        match value {
539            Value::Number(n) => Ok(n as i64),
540            _ => Err("expected number".to_string()),
541        }
542    }
543}
544
545impl JsonDecode for i32 {
546    fn from_json_value(value: Value) -> Result<Self, String> {
547        match value {
548            Value::Number(n) => Ok(n as i32),
549            _ => Err("expected number".to_string()),
550        }
551    }
552}
553
554impl JsonDecode for f32 {
555    fn from_json_value(value: Value) -> Result<Self, String> {
556        match value {
557            Value::Number(n) => Ok(n as f32),
558            _ => Err("expected number".to_string()),
559        }
560    }
561}
562
563impl<T: JsonDecode> JsonDecode for Vec<T> {
564    fn from_json_value(value: Value) -> Result<Self, String> {
565        match value {
566            Value::Array(values) => values.into_iter().map(T::from_json_value).collect(),
567            _ => Err("expected array".to_string()),
568        }
569    }
570}
571
572impl<T: JsonDecode> JsonDecode for HashMap<String, T> {
573    fn from_json_value(value: Value) -> Result<Self, String> {
574        match value {
575            Value::Object(map) => map
576                .into_iter()
577                .map(|(k, v)| Ok((k, T::from_json_value(v)?)))
578                .collect(),
579            _ => Err("expected object".to_string()),
580        }
581    }
582}
583
584impl<T: JsonDecode> JsonDecode for Option<T> {
585    fn from_json_value(value: Value) -> Result<Self, String> {
586        match value {
587            Value::Null => Ok(None),
588            other => Ok(Some(T::from_json_value(other)?)),
589        }
590    }
591}
592
593impl<const N: usize> JsonDecode for [u8; N] {
594    fn from_json_value(value: Value) -> Result<Self, String> {
595        match value {
596            Value::Array(values) => {
597                if values.len() != N {
598                    return Err("invalid array length".to_string());
599                }
600                let mut out = [0u8; N];
601                for (idx, val) in values.into_iter().enumerate() {
602                    out[idx] = u8::from_json_value(val)?;
603                }
604                Ok(out)
605            }
606            _ => Err("expected array".to_string()),
607        }
608    }
609}
610
611pub fn to_value<T: JsonEncode + ?Sized>(value: &T) -> Value {
612    value.to_json_value()
613}
614
615pub fn to_string<T: JsonEncode + ?Sized>(value: &T) -> Result<String, String> {
616    Ok(to_value(value).to_string_compact())
617}
618
619pub fn to_string_pretty<T: JsonEncode + ?Sized>(value: &T) -> Result<String, String> {
620    Ok(to_value(value).to_string_pretty())
621}
622
623pub fn to_vec<T: JsonEncode + ?Sized>(value: &T) -> Result<Vec<u8>, String> {
624    Ok(to_string(value)?.into_bytes())
625}
626
627pub fn from_str<T: JsonDecode>(input: &str) -> Result<T, String> {
628    let value = parse_json(input).map(Value::from)?;
629    T::from_json_value(value)
630}
631
632pub fn from_slice<T: JsonDecode>(input: &[u8]) -> Result<T, String> {
633    let s = std::str::from_utf8(input).map_err(|e| e.to_string())?;
634    from_str(s)
635}
636
637pub fn from_value<T: JsonDecode>(value: Value) -> Result<T, String> {
638    T::from_json_value(value)
639}
640
641#[macro_export]
642macro_rules! json {
643    (null) => {
644        $crate::serde_json::Value::Null
645    };
646    ([ $( $elem:expr ),* $(,)? ]) => {
647        $crate::serde_json::Value::Array(vec![ $( $crate::json!($elem) ),* ])
648    };
649    ({ $( $key:literal : $value:expr ),* $(,)? }) => {{
650        let mut map = $crate::serde_json::Map::new();
651        $( map.insert($key.to_string(), $crate::json!($value)); )*
652        $crate::serde_json::Value::Object(map)
653    }};
654    ($other:expr) => {
655        $crate::serde_json::to_value(&$other)
656    };
657}
658
659pub use crate::json;