Skip to main content

datavalue_rs/
emit.rs

1//! Native JSON emitter for [`DataValue`] and [`OwnedDataValue`].
2//!
3//! Bypasses the `serde_json::to_string` path. The serde route pays trait
4//! dispatch per node and a per-byte string-escape loop; emitting directly
5//! into a buffer with `ryu` / `itoa` and a SWAR-driven escape scan lands
6//! closer to the bespoke emitters in `json-rust` / `simd_json`.
7//!
8//! The same writers feed three sinks — `Vec<u8>` for [`DataValue::write_json_into`],
9//! `fmt::Formatter` for the [`fmt::Display`] impls, and an indenting wrapper
10//! for [`DataValue::pretty`] — through the [`JsonSink`] trait below.
11//!
12//! The `Serialize` impl in [`crate::ser`] is still the right entry point
13//! when feeding non-JSON serde sinks (msgpack, flexbuffers, etc.).
14
15use core::fmt;
16
17use crate::number::NumberValue;
18use crate::owned::OwnedDataValue;
19use crate::value::DataValue;
20
21/// Sink abstraction over `Vec<u8>` and `fmt::Formatter`. Bytes pushed are
22/// always valid UTF-8 (numbers are ASCII; strings are passed through from
23/// `&str` sources; escapes are ASCII), so the str adapter is sound.
24///
25/// Contract: every `write_bytes` call passes a *complete* valid-UTF-8 chunk
26/// (string runs are sliced at escape hits, which are ASCII, so run boundaries
27/// are char boundaries; everything else written is ASCII). `FormatterSink`'s
28/// staging buffer relies on this — it never splits a chunk, so the buffer
29/// content is always a concatenation of whole chunks and stays valid UTF-8.
30pub(crate) trait JsonSink {
31    type Error;
32    fn write_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error>;
33    fn write_byte(&mut self, b: u8) -> Result<(), Self::Error>;
34}
35
36impl JsonSink for Vec<u8> {
37    type Error = core::convert::Infallible;
38    #[inline]
39    fn write_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error> {
40        self.extend_from_slice(b);
41        Ok(())
42    }
43    #[inline]
44    fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
45        self.push(b);
46        Ok(())
47    }
48}
49
50impl JsonSink for bumpalo::collections::Vec<'_, u8> {
51    type Error = core::convert::Infallible;
52    #[inline]
53    fn write_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error> {
54        // Not extend_from_slice: bumpalo's Vec is a pre-specialization std
55        // fork, so its extend_from_slice copies element-by-element through a
56        // cloned iterator (measured ~2x slower on string-heavy emit). Reserve
57        // once and memcpy.
58        self.reserve(b.len());
59        let len = self.len();
60        // SAFETY: `reserve` guarantees capacity for `len + b.len()`; the
61        // source and destination don't overlap (`b` borrows input strings or
62        // static tables, never this vec's buffer).
63        unsafe {
64            core::ptr::copy_nonoverlapping(b.as_ptr(), self.as_mut_ptr().add(len), b.len());
65            self.set_len(len + b.len());
66        }
67        Ok(())
68    }
69    #[inline]
70    fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
71        self.push(b);
72        Ok(())
73    }
74}
75
76/// Finish an arena emit: reclaim unused BumpVec capacity, then reinterpret
77/// the bytes as `&str`.
78///
79/// SAFETY (of the contained `from_utf8_unchecked`): the emitters only write
80/// valid UTF-8 — ASCII structural bytes/escapes/numbers and `&str` payload
81/// runs (see the `JsonSink` contract).
82#[inline]
83fn into_arena_str<'b>(mut out: bumpalo::collections::Vec<'b, u8>) -> &'b str {
84    // Trim growth slack. The vec is the arena's most recent allocation
85    // (emitting allocates nothing else), so bumpalo reclaims in place;
86    // measured cost is noise-level because it only copies when more than
87    // half the capacity would be reclaimed.
88    out.shrink_to_fit();
89    unsafe { core::str::from_utf8_unchecked(out.into_bump_slice()) }
90}
91
92/// Staging capacity for `FormatterSink`. Without it, every structural byte
93/// (`[ ] { } , : "`) would be its own virtual `fmt::Write::write_str` call;
94/// with it, output reaches the formatter in ~128-byte runs.
95const FMT_STAGING: usize = 128;
96
97struct FormatterSink<'a, 'b> {
98    f: &'a mut fmt::Formatter<'b>,
99    buf: [u8; FMT_STAGING],
100    len: usize,
101}
102
103impl<'a, 'b> FormatterSink<'a, 'b> {
104    fn new(f: &'a mut fmt::Formatter<'b>) -> Self {
105        FormatterSink {
106            f,
107            buf: [0; FMT_STAGING],
108            len: 0,
109        }
110    }
111
112    fn flush(&mut self) -> fmt::Result {
113        if self.len > 0 {
114            // SAFETY: the buffer holds a concatenation of complete chunks,
115            // each valid UTF-8 (see the JsonSink contract); chunks are never
116            // split across flushes.
117            let s = unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) };
118            self.f.write_str(s)?;
119            self.len = 0;
120        }
121        Ok(())
122    }
123}
124
125impl<'a, 'b> JsonSink for FormatterSink<'a, 'b> {
126    type Error = fmt::Error;
127    #[inline]
128    fn write_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error> {
129        if self.len + b.len() > FMT_STAGING {
130            self.flush()?;
131            if b.len() >= FMT_STAGING {
132                // SAFETY: chunks are complete valid UTF-8 (JsonSink contract).
133                let s = unsafe { core::str::from_utf8_unchecked(b) };
134                return self.f.write_str(s);
135            }
136        }
137        self.buf[self.len..self.len + b.len()].copy_from_slice(b);
138        self.len += b.len();
139        Ok(())
140    }
141    #[inline]
142    fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
143        debug_assert!(b.is_ascii());
144        if self.len == FMT_STAGING {
145            self.flush()?;
146        }
147        self.buf[self.len] = b;
148        self.len += 1;
149        Ok(())
150    }
151}
152
153#[inline]
154fn write_escaped_str<S: JsonSink>(out: &mut S, s: &str) -> Result<(), S::Error> {
155    out.write_byte(b'"')?;
156    let bytes = s.as_bytes();
157    let mut run_start = 0;
158
159    // Scan-and-copy runs between escapes. The scan is the shared
160    // `crate::simd` helper: SWAR for slices under 32 bytes (the typical
161    // short JSON string — its criteria and mask are identical to the old
162    // inlined loop), the 16-byte SIMD stride for longer ones.
163    while let Some(off) = crate::simd::find_string_terminator(&bytes[run_start..]) {
164        let hit = run_start + off;
165        if hit > run_start {
166            out.write_bytes(&bytes[run_start..hit])?;
167        }
168        write_escape_byte(out, bytes[hit])?;
169        run_start = hit + 1;
170    }
171    if run_start < bytes.len() {
172        out.write_bytes(&bytes[run_start..])?;
173    }
174    out.write_byte(b'"')
175}
176
177/// DateTime arm: the 20-byte ISO buffer is pure ASCII with no JSON-special
178/// bytes, so it goes to the sink as quote + raw bytes + quote — no heap
179/// `String`, no escape scan. Years outside 0..=9999 fall back to chrono's
180/// RFC3339 formatter (heap) through the escaped path.
181#[cfg(feature = "datetime")]
182fn write_datetime<S: JsonSink>(
183    out: &mut S,
184    d: &crate::datetime::DataDateTime,
185) -> Result<(), S::Error> {
186    match d.iso_secs_buf() {
187        Some(buf) => {
188            out.write_byte(b'"')?;
189            out.write_bytes(&buf)?;
190            out.write_byte(b'"')
191        }
192        None => write_escaped_str(out, &d.to_iso_string()),
193    }
194}
195
196/// Duration arm: `Xd:Xh:Xm:Xs` is digits, `-`, letters, and `:` — never
197/// escaped, so it streams via itoa without the intermediate `String`.
198#[cfg(feature = "datetime")]
199fn write_duration<S: JsonSink>(
200    out: &mut S,
201    d: &crate::datetime::DataDuration,
202) -> Result<(), S::Error> {
203    let (days, hours, minutes, seconds) = d.dhms();
204    let mut b = itoa::Buffer::new();
205    out.write_byte(b'"')?;
206    out.write_bytes(b.format(days).as_bytes())?;
207    out.write_bytes(b"d:")?;
208    out.write_bytes(b.format(hours).as_bytes())?;
209    out.write_bytes(b"h:")?;
210    out.write_bytes(b.format(minutes).as_bytes())?;
211    out.write_bytes(b"m:")?;
212    out.write_bytes(b.format(seconds).as_bytes())?;
213    out.write_bytes(b"s\"")
214}
215
216#[inline]
217fn write_escape_byte<S: JsonSink>(out: &mut S, b: u8) -> Result<(), S::Error> {
218    match b {
219        b'"' => out.write_bytes(b"\\\""),
220        b'\\' => out.write_bytes(b"\\\\"),
221        b'\n' => out.write_bytes(b"\\n"),
222        b'\r' => out.write_bytes(b"\\r"),
223        b'\t' => out.write_bytes(b"\\t"),
224        0x08 => out.write_bytes(b"\\b"),
225        0x0C => out.write_bytes(b"\\f"),
226        c => {
227            // Other control bytes (< 0x20 not named above) use \u00XX. The
228            // high byte is always 0 here.
229            const HEX: &[u8; 16] = b"0123456789abcdef";
230            out.write_bytes(b"\\u00")?;
231            out.write_byte(HEX[((c >> 4) & 0x0F) as usize])?;
232            out.write_byte(HEX[(c & 0x0F) as usize])
233        }
234    }
235}
236
237#[inline]
238fn write_number<S: JsonSink>(out: &mut S, n: NumberValue) -> Result<(), S::Error> {
239    match n {
240        NumberValue::Integer(i) => {
241            let mut buf = itoa::Buffer::new();
242            out.write_bytes(buf.format(i).as_bytes())
243        }
244        NumberValue::Float(f) => {
245            if !f.is_finite() {
246                // serde_json emits non-finite floats as `null` to keep
247                // output valid JSON. Match that.
248                return out.write_bytes(b"null");
249            }
250            let mut buf = ryu::Buffer::new();
251            out.write_bytes(buf.format_finite(f).as_bytes())
252        }
253    }
254}
255
256// ---- Compact emit (no whitespace) ------------------------------------------------
257
258fn write_data_value<S: JsonSink>(out: &mut S, v: &DataValue<'_>) -> Result<(), S::Error> {
259    match *v {
260        DataValue::Null => out.write_bytes(b"null"),
261        DataValue::Bool(true) => out.write_bytes(b"true"),
262        DataValue::Bool(false) => out.write_bytes(b"false"),
263        DataValue::Number(n) => write_number(out, n),
264        DataValue::String(s) => write_escaped_str(out, s),
265        DataValue::Array(items) => {
266            out.write_byte(b'[')?;
267            let mut first = true;
268            for item in items {
269                if !first {
270                    out.write_byte(b',')?;
271                }
272                first = false;
273                write_data_value(out, item)?;
274            }
275            out.write_byte(b']')
276        }
277        DataValue::Object(pairs) => {
278            out.write_byte(b'{')?;
279            let mut first = true;
280            for (k, v) in pairs {
281                if !first {
282                    out.write_byte(b',')?;
283                }
284                first = false;
285                write_escaped_str(out, k)?;
286                out.write_byte(b':')?;
287                write_data_value(out, v)?;
288            }
289            out.write_byte(b'}')
290        }
291        #[cfg(feature = "datetime")]
292        DataValue::DateTime(d) => write_datetime(out, &d),
293        #[cfg(feature = "datetime")]
294        DataValue::Duration(d) => write_duration(out, &d),
295    }
296}
297
298fn write_owned_value<S: JsonSink>(out: &mut S, v: &OwnedDataValue) -> Result<(), S::Error> {
299    match v {
300        OwnedDataValue::Null => out.write_bytes(b"null"),
301        OwnedDataValue::Bool(true) => out.write_bytes(b"true"),
302        OwnedDataValue::Bool(false) => out.write_bytes(b"false"),
303        OwnedDataValue::Number(n) => write_number(out, *n),
304        OwnedDataValue::String(s) => write_escaped_str(out, s),
305        OwnedDataValue::Array(items) => {
306            out.write_byte(b'[')?;
307            let mut first = true;
308            for item in items {
309                if !first {
310                    out.write_byte(b',')?;
311                }
312                first = false;
313                write_owned_value(out, item)?;
314            }
315            out.write_byte(b']')
316        }
317        OwnedDataValue::Object(pairs) => {
318            out.write_byte(b'{')?;
319            let mut first = true;
320            for (k, v) in pairs {
321                if !first {
322                    out.write_byte(b',')?;
323                }
324                first = false;
325                write_escaped_str(out, k)?;
326                out.write_byte(b':')?;
327                write_owned_value(out, v)?;
328            }
329            out.write_byte(b'}')
330        }
331        #[cfg(feature = "datetime")]
332        OwnedDataValue::DateTime(d) => write_datetime(out, d),
333        #[cfg(feature = "datetime")]
334        OwnedDataValue::Duration(d) => write_duration(out, d),
335    }
336}
337
338// ---- Pretty emit (two-space indent, matches serde_json::to_string_pretty) -------
339
340#[inline]
341fn write_indent<S: JsonSink>(out: &mut S, depth: usize) -> Result<(), S::Error> {
342    // Two spaces per level. Keep a reasonably long literal so most depths
343    // need a single write.
344    const SPACES: &[u8; 64] = b"                                                                ";
345    let mut remaining = depth * 2;
346    while remaining > 0 {
347        let chunk = remaining.min(SPACES.len());
348        out.write_bytes(&SPACES[..chunk])?;
349        remaining -= chunk;
350    }
351    Ok(())
352}
353
354fn write_data_value_pretty<S: JsonSink>(
355    out: &mut S,
356    v: &DataValue<'_>,
357    depth: usize,
358) -> Result<(), S::Error> {
359    match *v {
360        DataValue::Null => out.write_bytes(b"null"),
361        DataValue::Bool(true) => out.write_bytes(b"true"),
362        DataValue::Bool(false) => out.write_bytes(b"false"),
363        DataValue::Number(n) => write_number(out, n),
364        DataValue::String(s) => write_escaped_str(out, s),
365        DataValue::Array(items) => {
366            if items.is_empty() {
367                return out.write_bytes(b"[]");
368            }
369            out.write_byte(b'[')?;
370            for (i, item) in items.iter().enumerate() {
371                if i > 0 {
372                    out.write_byte(b',')?;
373                }
374                out.write_byte(b'\n')?;
375                write_indent(out, depth + 1)?;
376                write_data_value_pretty(out, item, depth + 1)?;
377            }
378            out.write_byte(b'\n')?;
379            write_indent(out, depth)?;
380            out.write_byte(b']')
381        }
382        DataValue::Object(pairs) => {
383            if pairs.is_empty() {
384                return out.write_bytes(b"{}");
385            }
386            out.write_byte(b'{')?;
387            for (i, (k, v)) in pairs.iter().enumerate() {
388                if i > 0 {
389                    out.write_byte(b',')?;
390                }
391                out.write_byte(b'\n')?;
392                write_indent(out, depth + 1)?;
393                write_escaped_str(out, k)?;
394                out.write_bytes(b": ")?;
395                write_data_value_pretty(out, v, depth + 1)?;
396            }
397            out.write_byte(b'\n')?;
398            write_indent(out, depth)?;
399            out.write_byte(b'}')
400        }
401        #[cfg(feature = "datetime")]
402        DataValue::DateTime(d) => write_datetime(out, &d),
403        #[cfg(feature = "datetime")]
404        DataValue::Duration(d) => write_duration(out, &d),
405    }
406}
407
408fn write_owned_value_pretty<S: JsonSink>(
409    out: &mut S,
410    v: &OwnedDataValue,
411    depth: usize,
412) -> Result<(), S::Error> {
413    match v {
414        OwnedDataValue::Null => out.write_bytes(b"null"),
415        OwnedDataValue::Bool(true) => out.write_bytes(b"true"),
416        OwnedDataValue::Bool(false) => out.write_bytes(b"false"),
417        OwnedDataValue::Number(n) => write_number(out, *n),
418        OwnedDataValue::String(s) => write_escaped_str(out, s),
419        OwnedDataValue::Array(items) => {
420            if items.is_empty() {
421                return out.write_bytes(b"[]");
422            }
423            out.write_byte(b'[')?;
424            for (i, item) in items.iter().enumerate() {
425                if i > 0 {
426                    out.write_byte(b',')?;
427                }
428                out.write_byte(b'\n')?;
429                write_indent(out, depth + 1)?;
430                write_owned_value_pretty(out, item, depth + 1)?;
431            }
432            out.write_byte(b'\n')?;
433            write_indent(out, depth)?;
434            out.write_byte(b']')
435        }
436        OwnedDataValue::Object(pairs) => {
437            if pairs.is_empty() {
438                return out.write_bytes(b"{}");
439            }
440            out.write_byte(b'{')?;
441            for (i, (k, v)) in pairs.iter().enumerate() {
442                if i > 0 {
443                    out.write_byte(b',')?;
444                }
445                out.write_byte(b'\n')?;
446                write_indent(out, depth + 1)?;
447                write_escaped_str(out, k)?;
448                out.write_bytes(b": ")?;
449                write_owned_value_pretty(out, v, depth + 1)?;
450            }
451            out.write_byte(b'\n')?;
452            write_indent(out, depth)?;
453            out.write_byte(b'}')
454        }
455        #[cfg(feature = "datetime")]
456        OwnedDataValue::DateTime(d) => write_datetime(out, d),
457        #[cfg(feature = "datetime")]
458        OwnedDataValue::Duration(d) => write_duration(out, d),
459    }
460}
461
462// ---- Public API on DataValue ---------------------------------------------------
463
464impl DataValue<'_> {
465    /// Append the compact JSON encoding of this value to `out`. Useful when
466    /// you want to amortize allocation across many values into a shared buffer.
467    /// For one-shot string conversion, use the [`fmt::Display`] impl
468    /// (`v.to_string()` / `format!("{v}")` / `println!("{v}")`).
469    pub fn write_json_into(&self, out: &mut Vec<u8>) {
470        let _ = write_data_value(out, self);
471    }
472
473    /// Pretty-print wrapper. `format!("{}", v.pretty())` produces the same
474    /// two-space-indented layout as `serde_json::to_string_pretty`.
475    ///
476    /// ```
477    /// use bumpalo::Bump;
478    /// use datavalue_rs::DataValue;
479    ///
480    /// let arena = Bump::new();
481    /// let v = DataValue::from_str(r#"{"a":1}"#, &arena).unwrap();
482    /// assert_eq!(v.pretty().to_string(), "{\n  \"a\": 1\n}");
483    /// ```
484    pub fn pretty(&self) -> Pretty<'_, DataValue<'_>> {
485        Pretty(self)
486    }
487
488    /// Append the pretty JSON encoding of this value to `out`.
489    pub fn write_json_pretty_into(&self, out: &mut Vec<u8>) {
490        let _ = write_data_value_pretty(out, self, 0);
491    }
492
493    /// Emit the compact JSON encoding directly into `arena` and return the
494    /// arena-resident string — no heap allocation. The single-copy path for
495    /// consumers that render values to text living alongside the values
496    /// (e.g. a `&str` result slot in the same evaluation arena).
497    ///
498    /// ```
499    /// use bumpalo::Bump;
500    /// use datavalue_rs::DataValue;
501    ///
502    /// let arena = Bump::new();
503    /// let v = DataValue::from_str(r#"{"a":[1,2.5,"hi"]}"#, &arena).unwrap();
504    /// let s: &str = v.to_json_str_in(&arena);
505    /// assert_eq!(s, r#"{"a":[1,2.5,"hi"]}"#);
506    /// ```
507    pub fn to_json_str_in<'b>(&self, arena: &'b bumpalo::Bump) -> &'b str {
508        let mut out = bumpalo::collections::Vec::new_in(arena);
509        let _ = write_data_value(&mut out, self);
510        into_arena_str(out)
511    }
512
513    /// Pretty sibling of [`DataValue::to_json_str_in`].
514    pub fn to_json_pretty_str_in<'b>(&self, arena: &'b bumpalo::Bump) -> &'b str {
515        let mut out = bumpalo::collections::Vec::new_in(arena);
516        let _ = write_data_value_pretty(&mut out, self, 0);
517        into_arena_str(out)
518    }
519}
520
521impl OwnedDataValue {
522    /// Append the compact JSON encoding of this value to `out`. See
523    /// [`DataValue::write_json_into`]; this is the owned-side mirror.
524    pub fn write_json_into(&self, out: &mut Vec<u8>) {
525        let _ = write_owned_value(out, self);
526    }
527
528    /// Pretty-print wrapper; see [`DataValue::pretty`].
529    ///
530    /// ```
531    /// use datavalue_rs::OwnedDataValue;
532    ///
533    /// let v: OwnedDataValue = r#"{"a":1}"#.parse().unwrap();
534    /// assert_eq!(v.pretty().to_string(), "{\n  \"a\": 1\n}");
535    /// ```
536    pub fn pretty(&self) -> Pretty<'_, OwnedDataValue> {
537        Pretty(self)
538    }
539
540    /// Append the pretty JSON encoding of this value to `out`.
541    pub fn write_json_pretty_into(&self, out: &mut Vec<u8>) {
542        let _ = write_owned_value_pretty(out, self, 0);
543    }
544
545    /// Emit the compact JSON encoding directly into `arena`; see
546    /// [`DataValue::to_json_str_in`]. This is the owned-side mirror.
547    pub fn to_json_str_in<'b>(&self, arena: &'b bumpalo::Bump) -> &'b str {
548        let mut out = bumpalo::collections::Vec::new_in(arena);
549        let _ = write_owned_value(&mut out, self);
550        into_arena_str(out)
551    }
552
553    /// Pretty sibling of [`OwnedDataValue::to_json_str_in`].
554    pub fn to_json_pretty_str_in<'b>(&self, arena: &'b bumpalo::Bump) -> &'b str {
555        let mut out = bumpalo::collections::Vec::new_in(arena);
556        let _ = write_owned_value_pretty(&mut out, self, 0);
557        into_arena_str(out)
558    }
559}
560
561// ---- Display + Pretty wrapper ---------------------------------------------------
562
563/// Wrapper produced by [`DataValue::pretty`] / [`OwnedDataValue::pretty`] that
564/// renders the value as indented JSON via `Display`.
565pub struct Pretty<'b, T: ?Sized>(&'b T);
566
567impl fmt::Display for DataValue<'_> {
568    /// Compact JSON. Same shape as `serde_json::to_string`.
569    ///
570    /// ```
571    /// use bumpalo::Bump;
572    /// use datavalue_rs::DataValue;
573    ///
574    /// let arena = Bump::new();
575    /// let v = DataValue::from_str(r#"{"a":[1,2.5,"hi"]}"#, &arena).unwrap();
576    /// assert_eq!(v.to_string(), r#"{"a":[1,2.5,"hi"]}"#);
577    /// ```
578    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579        let mut sink = FormatterSink::new(f);
580        write_data_value(&mut sink, self)?;
581        sink.flush()
582    }
583}
584
585impl fmt::Display for OwnedDataValue {
586    /// Compact JSON. Same shape as `serde_json::to_string`.
587    ///
588    /// ```
589    /// use datavalue_rs::OwnedDataValue;
590    ///
591    /// let v: OwnedDataValue = r#"{"a":[1,2.5,"hi"]}"#.parse().unwrap();
592    /// assert_eq!(v.to_string(), r#"{"a":[1,2.5,"hi"]}"#);
593    /// ```
594    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
595        let mut sink = FormatterSink::new(f);
596        write_owned_value(&mut sink, self)?;
597        sink.flush()
598    }
599}
600
601impl fmt::Display for Pretty<'_, DataValue<'_>> {
602    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603        let mut sink = FormatterSink::new(f);
604        write_data_value_pretty(&mut sink, self.0, 0)?;
605        sink.flush()
606    }
607}
608
609impl fmt::Display for Pretty<'_, OwnedDataValue> {
610    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611        let mut sink = FormatterSink::new(f);
612        write_owned_value_pretty(&mut sink, self.0, 0)?;
613        sink.flush()
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620    use bumpalo::Bump;
621
622    fn round_trip(s: &str) -> String {
623        let arena = Bump::new();
624        let v = DataValue::from_str(s, &arena).unwrap();
625        v.to_string()
626    }
627
628    #[test]
629    fn primitives() {
630        assert_eq!(round_trip("null"), "null");
631        assert_eq!(round_trip("true"), "true");
632        assert_eq!(round_trip("false"), "false");
633        assert_eq!(round_trip("42"), "42");
634        assert_eq!(round_trip("-7"), "-7");
635        assert_eq!(round_trip("3.5"), "3.5");
636    }
637
638    #[test]
639    fn strings_with_escapes() {
640        assert_eq!(round_trip(r#""hello""#), r#""hello""#);
641        assert_eq!(round_trip(r#""a\nb""#), r#""a\nb""#);
642        assert_eq!(round_trip(r#""a\\b""#), r#""a\\b""#);
643        assert_eq!(round_trip(r#""a\"b""#), r#""a\"b""#);
644        // Unicode passes through verbatim (we don't re-escape non-ASCII).
645        assert_eq!(round_trip(r#""café""#), r#""café""#);
646    }
647
648    #[test]
649    fn control_bytes_render_as_unicode_escapes() {
650        let arena = Bump::new();
651        let v = DataValue::from_str("\"\\u0001\"", &arena).unwrap();
652        assert_eq!(v.to_string(), "\"\\u0001\"");
653    }
654
655    #[test]
656    fn nested_round_trip_matches_serde_json() {
657        let input = r#"{"a":[1,2,{"b":"hi\n","c":null,"d":true}],"e":-3.5,"f":[],"g":{}}"#;
658        let arena = Bump::new();
659        let v = DataValue::from_str(input, &arena).unwrap();
660        let ours = v.to_string();
661        let serde: serde_json::Value = serde_json::from_str(input).unwrap();
662        let theirs = serde_json::to_string(&serde).unwrap();
663        assert_eq!(ours, theirs);
664    }
665
666    #[test]
667    fn long_string_swar_path() {
668        let arena = Bump::new();
669        let s = format!("\"{}\"", "x".repeat(200));
670        let v = DataValue::from_str(&s, &arena).unwrap();
671        assert_eq!(v.to_string(), s);
672    }
673
674    // Display goes through FormatterSink's staging buffer; write_json_into
675    // goes straight to the Vec. The two must be byte-identical for every
676    // buffering edge case: chunks that straddle the FMT_STAGING boundary,
677    // chunks larger than the buffer (direct-write path), multi-byte UTF-8
678    // near flush points, and escape-heavy strings (many tiny chunks).
679    fn assert_display_matches_vec(input: &str) {
680        let arena = Bump::new();
681        let v = DataValue::from_str(input, &arena).unwrap();
682        let mut buf = Vec::new();
683        v.write_json_into(&mut buf);
684        assert_eq!(v.to_string().into_bytes(), buf, "compact mismatch");
685
686        let mut pretty_buf = Vec::new();
687        v.write_json_pretty_into(&mut pretty_buf);
688        assert_eq!(
689            v.pretty().to_string().into_bytes(),
690            pretty_buf,
691            "pretty mismatch"
692        );
693    }
694
695    #[cfg(feature = "datetime")]
696    #[test]
697    fn datetime_emit_matches_display_wire_format() {
698        use crate::datetime::DataDateTime;
699
700        let dt = DataDateTime::parse("2024-01-15T12:30:45Z").unwrap();
701        let later = DataDateTime::parse("2024-01-18T16:35:51Z").unwrap();
702        let du = later.diff(&dt);
703
704        for v in [DataValue::DateTime(dt), DataValue::Duration(du)] {
705            let display = v.to_string();
706            let mut buf = Vec::new();
707            v.write_json_into(&mut buf);
708            assert_eq!(display.into_bytes(), buf);
709
710            let owned = v.to_owned();
711            assert_eq!(owned.to_string(), v.to_string());
712        }
713        assert_eq!(
714            DataValue::DateTime(dt).to_string(),
715            "\"2024-01-15T12:30:45Z\""
716        );
717        assert_eq!(DataValue::Duration(du).to_string(), "\"3d:4h:5m:6s\"");
718    }
719
720    #[test]
721    fn to_json_str_in_matches_to_string() {
722        let inputs = [
723            "null",
724            "[]",
725            r#"{"a":[1,2.5,"hi\n",null,true],"b":{"c":"é€"},"e":-0.125}"#,
726        ];
727        let arena = Bump::new();
728        // Emit into a *different* arena than the values live in.
729        let out_arena = Bump::new();
730        for input in inputs {
731            let v = DataValue::from_str(input, &arena).unwrap();
732            assert_eq!(v.to_json_str_in(&out_arena), v.to_string());
733            assert_eq!(v.to_json_pretty_str_in(&out_arena), v.pretty().to_string());
734
735            let owned = v.to_owned();
736            assert_eq!(owned.to_json_str_in(&out_arena), owned.to_string());
737            assert_eq!(
738                owned.to_json_pretty_str_in(&out_arena),
739                owned.pretty().to_string()
740            );
741        }
742        // Long string: forces BumpVec growth inside the emit.
743        let long = format!("\"{}\"", "x".repeat(5000));
744        let v = DataValue::from_str(&long, &arena).unwrap();
745        assert_eq!(v.to_json_str_in(&out_arena), long);
746    }
747
748    #[test]
749    fn display_matches_vec_across_staging_boundaries() {
750        // ASCII strings sized to land runs on every offset around the
751        // 128-byte staging capacity.
752        for n in [1, 7, 126, 127, 128, 129, 200, 255, 256, 257, 1000] {
753            assert_display_matches_vec(&format!("\"{}\"", "x".repeat(n)));
754        }
755        // Multi-byte UTF-8 (2- and 3-byte chars) filling past the boundary —
756        // a split inside a char would corrupt output or trip UTF-8 checks.
757        for n in [60, 63, 64, 65, 100] {
758            assert_display_matches_vec(&format!("\"{}\"", "é".repeat(n)));
759            assert_display_matches_vec(&format!("\"{}\"", "€".repeat(n)));
760        }
761        // Escape-heavy: alternating escapes chop the string into 1-byte runs.
762        assert_display_matches_vec(&format!("\"{}\"", r#"a\n"#.repeat(100)));
763        // Composite document with many small structural writes.
764        assert_display_matches_vec(
765            r#"{"a":[1,2.5,"hi\n",null,true],"b":{"c":"é€","d":[[],{}]},"e":-0.125}"#,
766        );
767    }
768
769    #[test]
770    fn non_finite_floats_render_as_null() {
771        let v = DataValue::from_f64(f64::NAN);
772        assert_eq!(v.to_string(), "null");
773        let v = DataValue::from_f64(f64::INFINITY);
774        assert_eq!(v.to_string(), "null");
775    }
776
777    #[test]
778    fn owned_round_trip() {
779        let v: OwnedDataValue = r#"{"name":"alice","age":30}"#.parse().unwrap();
780        let serde: serde_json::Value = serde_json::from_str(&v.to_string()).unwrap();
781        assert_eq!(serde["name"], "alice");
782        assert_eq!(serde["age"], 30);
783    }
784
785    #[test]
786    fn write_json_into_buffer() {
787        let arena = Bump::new();
788        let v = DataValue::from_str(r#"[1,2,3]"#, &arena).unwrap();
789        let mut buf = Vec::new();
790        v.write_json_into(&mut buf);
791        assert_eq!(buf, b"[1,2,3]");
792    }
793
794    #[test]
795    fn pretty_matches_serde_json_pretty() {
796        let input = r#"{"a":[1,2,{"b":"hi","c":null}],"e":-3.5,"f":[],"g":{}}"#;
797        let arena = Bump::new();
798        let v = DataValue::from_str(input, &arena).unwrap();
799        let ours = v.pretty().to_string();
800        let serde: serde_json::Value = serde_json::from_str(input).unwrap();
801        let theirs = serde_json::to_string_pretty(&serde).unwrap();
802        assert_eq!(ours, theirs);
803    }
804
805    #[test]
806    fn pretty_owned_matches_serde_json_pretty() {
807        let input = r#"{"a":[1,2,{"b":"hi","c":null}],"e":-3.5,"f":[],"g":{}}"#;
808        let v: OwnedDataValue = input.parse().unwrap();
809        let serde: serde_json::Value = serde_json::from_str(input).unwrap();
810        assert_eq!(
811            v.pretty().to_string(),
812            serde_json::to_string_pretty(&serde).unwrap()
813        );
814    }
815
816    #[test]
817    fn pretty_empty_collections_inline() {
818        let arena = Bump::new();
819        let v = DataValue::from_str(r#"{"a":[],"b":{}}"#, &arena).unwrap();
820        assert_eq!(v.pretty().to_string(), "{\n  \"a\": [],\n  \"b\": {}\n}");
821    }
822
823    #[test]
824    fn pretty_deep_indent_beyond_64_spaces() {
825        // 35 levels deep -> 70 spaces of indent on the leaf line. Exercises
826        // the chunked SPACES write loop.
827        let arena = Bump::new();
828        let mut s = String::new();
829        for _ in 0..35 {
830            s.push('[');
831        }
832        s.push('1');
833        for _ in 0..35 {
834            s.push(']');
835        }
836        let v = DataValue::from_str(&s, &arena).unwrap();
837        let ours = v.pretty().to_string();
838        let serde: serde_json::Value = serde_json::from_str(&s).unwrap();
839        assert_eq!(ours, serde_json::to_string_pretty(&serde).unwrap());
840    }
841}