Skip to main content

yo_doc/
text.rs

1//! JSON text into a [`Builder`] and back out of a [`Value`].
2//!
3//! The typed API never comes through here: a struct is serialized straight into
4//! the encoding and read straight back out, and text would be two conversions
5//! nobody asked for. This is for the other door. `JSON.SET` arrives with a
6//! bulk string that is JSON text and `JSON.GET` has to hand one back, so the
7//! whole `JSON.*` surface stands on these two functions and neither of them can
8//! be a dependency, because a JSON parser is where a compatibility claim goes to
9//! die and this one has to agree with RedisJSON down to the byte.
10//!
11//! ```
12//! use yo_doc::{Builder, Value};
13//!
14//! let mut b = Builder::new();
15//! b.json(br#"{"name": "a wrench", "price": 12.5, "tags": ["hand", "steel"]}"#)?;
16//! let doc = b.finish()?.to_vec();
17//!
18//! let v = Value::new(&doc).expect("readable");
19//! assert_eq!(v.get(b"price").unwrap().as_float(), Some(12.5));
20//! // Key order, not the order the text had them in. See below.
21//! assert_eq!(v.to_json()?, br#"{"name":"a wrench","tags":["hand","steel"],"price":12.5}"#);
22//! # Ok::<(), yo_common::Error>(())
23//! ```
24//!
25//! # What the parser accepts
26//!
27//! RFC 8259 and nothing else. No trailing commas, no comments, no unquoted
28//! keys, no single quoted strings, no leading plus, no leading zero, no bare
29//! `NaN` or `Infinity`. Every one of those is something some parser somewhere
30//! allows, and accepting one of them means a document that loads here and is
31//! refused by a real Redis, which is a divergence that nobody would think to
32//! look for. Being strict is the only setting that can be checked.
33//!
34//! A number without a fraction or an exponent that fits in an `i64` is stored as
35//! an integer and everything else is stored as a float, which is the same split
36//! RedisJSON makes and the reason `1` comes back as `1` rather than as `1.0`.
37//! An integer literal too big for an `i64` becomes a float, and loses precision
38//! the way it would anywhere else. So does `-0`, which is a number an integer
39//! cannot hold and a double can.
40//!
41//! # Two things the writer does that are worth knowing
42//!
43//! **An object comes out in key order**, which is by length first and then by
44//! bytes, so `name` comes before `price` and not after it. Members are stored
45//! sorted because that is what makes a lookup a binary search, so the order a
46//! client wrote them in is not kept anywhere and cannot be handed back.
47//! RedisJSON keeps it. That shows up on any document with more than one key and
48//! it belongs in the divergence register rather than in a footnote.
49//!
50//! **A float is printed as the shortest text that reads back as the same
51//! double**, with a `.0` added when it would otherwise look like an integer.
52//! Without that, a document that went out and came back would change type on
53//! every round trip, which is the sort of thing that only shows up three
54//! services downstream.
55
56use core::fmt::Write as _;
57
58use yo_common::{Code, Error, Result};
59
60use crate::build::Builder;
61use crate::head::{DEPTH_MAX, Kind};
62use crate::read::Value;
63
64/// One JSON document, as the bytes it encodes to.
65///
66/// A caller with more than one document to read should keep a [`Builder`] and
67/// call [`Builder::json`] on it instead, since that is the whole reason a
68/// builder can be cleared and reused.
69pub fn from_json(text: &[u8]) -> Result<Vec<u8>> {
70    let mut b = Builder::new();
71    b.json(text)?;
72    Ok(b.finish()?.to_vec())
73}
74
75impl Builder {
76    /// Write the one JSON value in `text`.
77    ///
78    /// This writes a value where a value goes, so it works on an empty builder
79    /// and it works just as well after a [`Builder::key`] inside an open
80    /// object, which is what a path update needs: the parts of the document
81    /// that are not changing are copied with [`Builder::embed`] and the part
82    /// that is arrives as text.
83    ///
84    /// `text` holds exactly one value. Anything after it, other than
85    /// whitespace, is an error rather than something quietly ignored, because a
86    /// client that sent two values meant something and it was not this.
87    pub fn json(&mut self, text: &[u8]) -> Result<()> {
88        // Checked once here so that every string literal inside can be taken as
89        // UTF-8 without checking it again. Slicing at escape boundaries cannot
90        // break that, since every character an escape is made of is ASCII.
91        if core::str::from_utf8(text).is_err() {
92            return Err(Error::new(Code::Invalid, "the JSON text is not UTF-8"));
93        }
94        let mut r = Reader {
95            text,
96            at: 0,
97            scratch: Vec::new(),
98        };
99        r.space();
100        r.value(self)?;
101        r.space();
102        if r.at < text.len() {
103            return Err(r.bad("more text after the value the document is"));
104        }
105        Ok(())
106    }
107}
108
109/// How a document is laid out when it is written as text.
110///
111/// All three are empty by default, which is one line and no spaces, and that is
112/// what `JSON.GET` answers when the client did not ask for anything else. The
113/// three are Redis's `INDENT`, `NEWLINE` and `SPACE`, and they are strings
114/// rather than counts there, so they are strings here.
115#[derive(Debug, Clone, Copy, Default)]
116pub struct Format<'a> {
117    /// Written once per level of nesting at the start of a line.
118    pub indent: &'a [u8],
119    /// Written at the end of a line.
120    pub newline: &'a [u8],
121    /// Written after the colon between a key and its value.
122    pub space: &'a [u8],
123}
124
125impl Format<'_> {
126    /// Whether this asks for anything at all.
127    ///
128    /// A container with nothing in it is written as `{}` either way, so the
129    /// laid out form only differs from the compact one where there is something
130    /// to lay out. `JSON.GET` builds its own wrapper around what a path matched
131    /// and has to make the same decision about it, which is why this is public.
132    #[must_use]
133    pub fn is_plain(&self) -> bool {
134        self.indent.is_empty() && self.newline.is_empty() && self.space.is_empty()
135    }
136}
137
138impl Value<'_> {
139    /// This value as JSON text, on one line.
140    pub fn to_json(&self) -> Result<Vec<u8>> {
141        let mut out = Vec::new();
142        self.write_json(&mut out)?;
143        Ok(out)
144    }
145
146    /// This value as JSON text, appended to a buffer the caller owns.
147    ///
148    /// The reply path has one of those per connection, so a `JSON.GET` over a
149    /// thousand keys is one buffer rather than a thousand.
150    pub fn write_json(&self, out: &mut Vec<u8>) -> Result<()> {
151        write_value(self, &Format::default(), out, 0)
152    }
153
154    /// The same, laid out the way `f` asks for.
155    pub fn write_json_with(&self, f: &Format<'_>, out: &mut Vec<u8>) -> Result<()> {
156        write_value(self, f, out, 0)
157    }
158
159    /// The same again, as if this value were already `depth` levels down.
160    ///
161    /// `JSON.GET` wraps what a JSONPath matched in an array and wraps several
162    /// paths in an object keyed by the paths, and it lays those wrappers out
163    /// too, so the values inside them start one or two levels in rather than at
164    /// the margin. The wrapper is built by the caller, which is the only thing
165    /// that knows how deep it went.
166    pub fn write_json_at(&self, f: &Format<'_>, out: &mut Vec<u8>, depth: usize) -> Result<()> {
167        write_value(self, f, out, depth)
168    }
169}
170
171// ------------------------------------------------------------------- the text
172
173/// A cursor over JSON text.
174struct Reader<'a> {
175    text: &'a [u8],
176    at: usize,
177    /// Where a string with escapes in it is put back together. Kept on the
178    /// reader rather than made per string, so a document full of escaped
179    /// strings allocates once.
180    scratch: Vec<u8>,
181}
182
183/// Where the bytes of a string literal ended up.
184///
185/// Most strings have no escapes in them, and those are a range of the text and
186/// no copy at all. The rest are built in [`Reader::scratch`], one at a time,
187/// which is why this is a marker rather than a slice: the slice would borrow
188/// the reader for as long as the caller held it.
189enum Str {
190    Plain(usize, usize),
191    Escaped,
192}
193
194impl<'a> Reader<'a> {
195    /// Step over whitespace, which JSON says is these four bytes and no others.
196    fn space(&mut self) {
197        while let Some(&c) = self.text.get(self.at) {
198            if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' {
199                self.at += 1;
200            } else {
201                break;
202            }
203        }
204    }
205
206    fn peek(&self) -> Option<u8> {
207        self.text.get(self.at).copied()
208    }
209
210    /// Step over `word` if that is what is here.
211    fn word(&mut self, word: &[u8]) -> bool {
212        if self.text[self.at..].starts_with(word) {
213            self.at += word.len();
214            true
215        } else {
216            false
217        }
218    }
219
220    /// One value, and whatever is under it.
221    ///
222    /// The recursion is bounded by the builder rather than here: opening the
223    /// hundred and twenty ninth container is an error, and the error comes
224    /// straight back up, so there is no depth to count in this function.
225    fn value(&mut self, b: &mut Builder) -> Result<()> {
226        match self.peek() {
227            None => Err(self.bad("the text ends where a value should be")),
228            Some(b'n') if self.word(b"null") => b.null(),
229            Some(b't') if self.word(b"true") => b.bool(true),
230            Some(b'f') if self.word(b"false") => b.bool(false),
231            Some(b'"') => {
232                let s = self.string()?;
233                let bytes = self.bytes_of(&s);
234                b.text_bytes(bytes)
235            }
236            Some(b'[') => self.array(b),
237            Some(b'{') => self.object(b),
238            Some(c) if c == b'-' || c.is_ascii_digit() => self.number(b),
239            Some(_) => Err(self.bad("this is not the start of a value")),
240        }
241    }
242
243    fn array(&mut self, b: &mut Builder) -> Result<()> {
244        self.at += 1;
245        b.begin_array()?;
246        self.space();
247        if self.peek() == Some(b']') {
248            self.at += 1;
249            return b.end_array();
250        }
251        loop {
252            self.space();
253            self.value(b)?;
254            self.space();
255            match self.peek() {
256                Some(b',') => self.at += 1,
257                Some(b']') => {
258                    self.at += 1;
259                    return b.end_array();
260                }
261                _ => return Err(self.bad("an array element is followed by `,` or by `]`")),
262            }
263        }
264    }
265
266    fn object(&mut self, b: &mut Builder) -> Result<()> {
267        self.at += 1;
268        b.begin_object()?;
269        self.space();
270        if self.peek() == Some(b'}') {
271            self.at += 1;
272            return b.end_object();
273        }
274        loop {
275            self.space();
276            if self.peek() != Some(b'"') {
277                return Err(self.bad("an object key is a string"));
278            }
279            let s = self.string()?;
280            b.key(self.bytes_of(&s))?;
281            self.space();
282            if self.peek() != Some(b':') {
283                return Err(self.bad("an object key is followed by `:`"));
284            }
285            self.at += 1;
286            self.space();
287            self.value(b)?;
288            self.space();
289            match self.peek() {
290                Some(b',') => self.at += 1,
291                Some(b'}') => {
292                    self.at += 1;
293                    return b.end_object();
294                }
295                _ => return Err(self.bad("an object member is followed by `,` or by `}`")),
296            }
297        }
298    }
299
300    /// The bytes a [`Str`] named, whichever of the two places it is in.
301    fn bytes_of(&self, s: &Str) -> &[u8] {
302        match *s {
303            Str::Plain(from, to) => &self.text[from..to],
304            Str::Escaped => &self.scratch,
305        }
306    }
307
308    /// A string literal, with the opening quote still under the cursor.
309    fn string(&mut self) -> Result<Str> {
310        self.at += 1;
311        let from = self.at;
312        // The common case walked first and on its own, so a string with no
313        // escapes never touches the scratch buffer and never copies a byte.
314        while let Some(c) = self.peek() {
315            match c {
316                b'"' => {
317                    let to = self.at;
318                    self.at += 1;
319                    return Ok(Str::Plain(from, to));
320                }
321                b'\\' => break,
322                // A raw control byte inside a string is what RFC 8259 forbids
323                // and what every other parser also refuses, so accepting it
324                // would be a divergence with nothing to gain.
325                0..=0x1f => return Err(self.bad("a control byte inside a string")),
326                _ => self.at += 1,
327            }
328        }
329
330        self.scratch.clear();
331        self.scratch.extend_from_slice(&self.text[from..self.at]);
332        loop {
333            let Some(c) = self.peek() else {
334                return Err(self.bad("the text ends inside a string"));
335            };
336            self.at += 1;
337            match c {
338                b'"' => return Ok(Str::Escaped),
339                0..=0x1f => return Err(self.bad("a control byte inside a string")),
340                b'\\' => self.escape()?,
341                _ => self.scratch.push(c),
342            }
343        }
344    }
345
346    /// One escape, with the backslash already stepped over.
347    fn escape(&mut self) -> Result<()> {
348        let Some(c) = self.peek() else {
349            return Err(self.bad("the text ends inside an escape"));
350        };
351        self.at += 1;
352        let plain = match c {
353            b'"' => b'"',
354            b'\\' => b'\\',
355            b'/' => b'/',
356            b'b' => 0x08,
357            b'f' => 0x0c,
358            b'n' => b'\n',
359            b'r' => b'\r',
360            b't' => b'\t',
361            b'u' => return self.unicode(),
362            _ => return Err(self.bad("this is not an escape JSON has")),
363        };
364        self.scratch.push(plain);
365        Ok(())
366    }
367
368    /// A `\u` escape and, if it opened a surrogate pair, the one that closes it.
369    fn unicode(&mut self) -> Result<()> {
370        let first = self.hex4()?;
371        let ch = if (0xd800..0xdc00).contains(&first) {
372            // A high surrogate on its own is not a character, so the low one
373            // has to be right here. Anything else is text that claims to be
374            // UTF-16 and is not, and guessing a replacement character for it
375            // would store something the client never sent.
376            if !(self.peek() == Some(b'\\') && self.text.get(self.at + 1) == Some(&b'u')) {
377                return Err(self.bad("a high surrogate with no low surrogate after it"));
378            }
379            self.at += 2;
380            let second = self.hex4()?;
381            if !(0xdc00..0xe000).contains(&second) {
382                return Err(
383                    self.bad("a high surrogate followed by something that is not a low one")
384                );
385            }
386            0x10000 + ((first - 0xd800) << 10) + (second - 0xdc00)
387        } else if (0xdc00..0xe000).contains(&first) {
388            return Err(self.bad("a low surrogate with no high surrogate before it"));
389        } else {
390            first
391        };
392        let ch = char::from_u32(ch).ok_or_else(|| self.bad("an escape that is not a character"))?;
393        let mut buf = [0u8; 4];
394        self.scratch
395            .extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
396        Ok(())
397    }
398
399    /// The four hex digits of a `\u` escape.
400    fn hex4(&mut self) -> Result<u32> {
401        let Some(digits) = self.text.get(self.at..self.at + 4) else {
402            return Err(self.bad("an escape with fewer than four hex digits"));
403        };
404        let mut v = 0u32;
405        for &d in digits {
406            let n = match d {
407                b'0'..=b'9' => u32::from(d - b'0'),
408                b'a'..=b'f' => u32::from(d - b'a') + 10,
409                b'A'..=b'F' => u32::from(d - b'A') + 10,
410                _ => return Err(self.bad("an escape with something that is not a hex digit")),
411            };
412            v = v * 16 + n;
413        }
414        self.at += 4;
415        Ok(v)
416    }
417
418    /// A number, written out by the grammar rather than handed to a parser and
419    /// checked afterwards, because the shapes Rust's own `parse` accepts and the
420    /// shapes JSON allows are not the same list.
421    fn number(&mut self, b: &mut Builder) -> Result<()> {
422        let from = self.at;
423        if self.peek() == Some(b'-') {
424            self.at += 1;
425        }
426        match self.peek() {
427            // A leading zero is a whole number on its own, which is what stops
428            // `0123` from being read as `123` here and as an octal somewhere
429            // else.
430            Some(b'0') => self.at += 1,
431            Some(c) if c.is_ascii_digit() => self.digits(),
432            _ => return Err(self.bad("a number with no digits in it")),
433        }
434        let mut whole = true;
435        if self.peek() == Some(b'.') {
436            self.at += 1;
437            if !self.peek().is_some_and(|c| c.is_ascii_digit()) {
438                return Err(self.bad("a decimal point with no digits after it"));
439            }
440            self.digits();
441            whole = false;
442        }
443        if matches!(self.peek(), Some(b'e' | b'E')) {
444            self.at += 1;
445            if matches!(self.peek(), Some(b'+' | b'-')) {
446                self.at += 1;
447            }
448            if !self.peek().is_some_and(|c| c.is_ascii_digit()) {
449                return Err(self.bad("an exponent with no digits in it"));
450            }
451            self.digits();
452            whole = false;
453        }
454
455        let text = core::str::from_utf8(&self.text[from..self.at])
456            .expect("a number is the ASCII this function just walked over");
457        // An integer that does not fit falls through to the float, which is the
458        // only thing that can be done with it and is what everyone else does.
459        // So does a negative zero, which is a number an integer cannot hold and
460        // a double can, and which every other JSON parser reads as a double for
461        // that reason. It matters because a document that went through here
462        // would otherwise come back out with the sign gone.
463        if whole
464            && text != "-0"
465            && let Ok(i) = text.parse::<i64>()
466        {
467            return b.int(i);
468        }
469        let f: f64 = text
470            .parse()
471            .map_err(|_| self.bad("a number that does not fit in a double"))?;
472        b.float(f)
473    }
474
475    fn digits(&mut self) {
476        while self.peek().is_some_and(|c| c.is_ascii_digit()) {
477            self.at += 1;
478        }
479    }
480
481    /// An error that says where it happened, because a client that sent four
482    /// kilobytes of JSON needs the offset more than it needs the adjective.
483    fn bad(&self, what: &str) -> Error {
484        Error::fmt(
485            Code::Invalid,
486            format_args!("{what}, at byte {} of the JSON text", self.at),
487        )
488    }
489}
490
491// ----------------------------------------------------------------- the writing
492
493fn write_value(v: &Value<'_>, f: &Format<'_>, out: &mut Vec<u8>, depth: usize) -> Result<()> {
494    match v.kind() {
495        Kind::Null => out.extend_from_slice(b"null"),
496        Kind::Bool => out.extend_from_slice(if v.as_bool() == Some(true) {
497            b"true".as_slice()
498        } else {
499            b"false".as_slice()
500        }),
501        Kind::Int => {
502            let i = v.as_int().ok_or_else(unreadable)?;
503            write!(Sink(out), "{i}").expect("a Vec never fails a write");
504        }
505        Kind::Float => write_float(v.as_float().ok_or_else(unreadable)?, out)?,
506        Kind::Text => write_string(v.text_bytes().ok_or_else(unreadable)?, out),
507        Kind::Array => {
508            deeper(depth)?;
509            let laid_out = !f.is_plain() && !v.is_empty();
510            out.push(b'[');
511            for (i, e) in v.iter().enumerate() {
512                if i > 0 {
513                    out.push(b',');
514                }
515                if laid_out {
516                    line(f, out, depth + 1);
517                }
518                write_value(&e, f, out, depth + 1)?;
519            }
520            if laid_out {
521                line(f, out, depth);
522            }
523            out.push(b']');
524        }
525        Kind::Object => {
526            deeper(depth)?;
527            if v.is_interned() {
528                return Err(Error::new(
529                    Code::Invalid,
530                    "an object whose keys are interned needs the collection's key table to be written as text",
531                ));
532            }
533            let laid_out = !f.is_plain() && !v.is_empty();
534            out.push(b'{');
535            for (i, (key, e)) in v.members().enumerate() {
536                if i > 0 {
537                    out.push(b',');
538                }
539                if laid_out {
540                    line(f, out, depth + 1);
541                }
542                write_string(key, out);
543                out.push(b':');
544                out.extend_from_slice(f.space);
545                write_value(&e, f, out, depth + 1)?;
546            }
547            if laid_out {
548                line(f, out, depth);
549            }
550            out.push(b'}');
551        }
552    }
553    Ok(())
554}
555
556/// End the line and indent the next one to `depth`.
557fn line(f: &Format<'_>, out: &mut Vec<u8>, depth: usize) {
558    out.extend_from_slice(f.newline);
559    for _ in 0..depth {
560        out.extend_from_slice(f.indent);
561    }
562}
563
564/// A document this crate wrote never nests past [`DEPTH_MAX`], so this only
565/// fires on one that arrived damaged, and it fires before the recursion does
566/// rather than after the stack has gone.
567fn deeper(depth: usize) -> Result<()> {
568    if depth >= DEPTH_MAX {
569        return Err(Error::fmt(
570            Code::Corrupt,
571            format_args!("the document nests past {DEPTH_MAX} levels"),
572        ));
573    }
574    Ok(())
575}
576
577pub(crate) fn write_float(f: f64, out: &mut Vec<u8>) -> Result<()> {
578    if !f.is_finite() {
579        return Err(Error::new(
580            Code::Invalid,
581            "JSON has no way to write an infinity or a NaN",
582        ));
583    }
584    // A number far from one is written the way every JSON writer worth using
585    // writes it, which is `1e16` rather than sixteen zeroes and `1e-7` rather
586    // than six. The switch is at `1e-5` below and `1e16` above, which is where
587    // RedisJSON's writer puts it and where JavaScript's does.
588    let mag = f.abs();
589    if mag != 0.0 && !(1e-5..1e16).contains(&mag) {
590        write!(Sink(out), "{f:e}").expect("a Vec never fails a write");
591        return Ok(());
592    }
593    let from = out.len();
594    write!(Sink(out), "{f}").expect("a Vec never fails a write");
595    // Rust prints a whole double as `1` rather than as `1.0`, and reading that
596    // back gives an integer, so a document left alone would change type once
597    // per round trip.
598    if !out[from..].iter().any(|&c| matches!(c, b'.' | b'e' | b'E')) {
599        out.extend_from_slice(b".0");
600    }
601    Ok(())
602}
603
604/// A whole number, as JSON text.
605pub(crate) fn write_int(i: i64, out: &mut Vec<u8>) {
606    write!(Sink(out), "{i}").expect("a Vec never fails a write");
607}
608
609/// A double, the way `JSON.RESP` writes one, which is not the way JSON does.
610///
611/// Redis has one routine for turning a double into text outside of JSON and
612/// `JSON.RESP` goes through it rather than through the JSON writer, so the
613/// digits are not the digits `JSON.GET` hands back for the same number. Three
614/// things differ. A whole number a long long can hold comes back as that long
615/// long, so `1.0` is `1` and `1e16` is `10000000000000000`. The switch to
616/// exponent form is at `1e-7` below and depends on the digit count above rather
617/// than sitting at `1e16`. And a positive exponent carries a `+`, so `1e19` is
618/// `1e+19` while `1e-7` stays `1e-7`.
619///
620/// The shape rules below are grisu2's as Redis links it. Redis will not always
621/// answer the shortest digits there and Rust always will, which is a difference
622/// of one digit on roughly one number in a thousand and is not worth carrying a
623/// second digit generator for.
624pub fn write_resp_float(f: f64, out: &mut Vec<u8>) {
625    if f.is_nan() {
626        out.extend_from_slice(b"nan");
627        return;
628    }
629    if f.is_infinite() {
630        out.extend_from_slice(if f < 0.0 { b"-inf" } else { b"inf" });
631        return;
632    }
633    if f == 0.0 {
634        if f.is_sign_negative() {
635            out.push(b'-');
636        }
637        out.push(b'0');
638        return;
639    }
640    // A whole number a long long can hold goes out as that long long. The half
641    // is not a typo: Redis will not trust the cast any nearer the edge than
642    // that, so the switch is at 4.6e18 and not at 9.2e18.
643    #[expect(clippy::cast_precision_loss, reason = "a bound and not a round trip")]
644    let half = (i64::MAX / 2) as f64;
645    if f.abs() <= half {
646        #[expect(clippy::cast_possible_truncation, reason = "bounded on the line above")]
647        let whole = f as i64;
648        #[expect(clippy::cast_precision_loss, reason = "the test is that it was exact")]
649        let exact = whole as f64 == f;
650        if exact {
651            write_int(whole, out);
652            return;
653        }
654    }
655    // The shortest digits and where the point sits, read back out of the form
656    // Rust writes them in. A double is at most seventeen digits, so the parse
657    // below cannot overrun.
658    let from = out.len();
659    write!(Sink(out), "{f:e}").expect("a Vec never fails a write");
660    let mut digits = [0u8; 17];
661    let mut count = 0;
662    let mut at = from;
663    let neg = out[at] == b'-';
664    if neg {
665        at += 1;
666    }
667    while at < out.len() && out[at] != b'e' {
668        if out[at] != b'.' {
669            digits[count] = out[at];
670            count += 1;
671        }
672        at += 1;
673    }
674    let exp: i32 = core::str::from_utf8(&out[at + 1..])
675        .expect("digits are UTF-8")
676        .parse()
677        .expect("Rust wrote the exponent");
678    out.truncate(from);
679    let digits = &digits[..count];
680    let len = i32::try_from(count).expect("at most seventeen");
681    // Where the point sits relative to the last digit. The value is the digits
682    // times ten to this.
683    let k = exp - (len - 1);
684    if neg {
685        out.push(b'-');
686    }
687    if k >= 0 && exp.abs() < len + 7 {
688        // A whole number that is short enough to write out in full.
689        out.extend_from_slice(digits);
690        out.resize(
691            out.len() + usize::try_from(k).expect("checked to be positive"),
692            b'0',
693        );
694    } else if k < 0 && (k > -7 || exp.abs() < 4) {
695        // A fraction near enough to one to write with a point in it.
696        let point = len + k;
697        if point <= 0 {
698            out.extend_from_slice(b"0.");
699            out.resize(
700                out.len() + usize::try_from(-point).expect("checked to be negative"),
701                b'0',
702            );
703            out.extend_from_slice(digits);
704        } else {
705            let point = usize::try_from(point).expect("checked to be positive");
706            out.extend_from_slice(&digits[..point]);
707            out.push(b'.');
708            out.extend_from_slice(&digits[point..]);
709        }
710    } else {
711        out.push(digits[0]);
712        if count > 1 {
713            out.push(b'.');
714            out.extend_from_slice(&digits[1..]);
715        }
716        out.push(b'e');
717        out.push(if exp < 0 { b'-' } else { b'+' });
718        write_int(i64::from(exp.abs()), out);
719    }
720}
721
722/// A string, quoted and escaped.
723///
724/// The bytes above `0x7f` are copied as they are, which keeps UTF-8 as UTF-8
725/// and is what every JSON writer worth using does. A string that was not UTF-8
726/// going in, which only [`Builder::text_bytes`] can produce, comes out the same
727/// way it went in and the result is not valid JSON. That is the caller's doing
728/// and re-encoding it would be inventing bytes.
729pub(crate) fn write_string(s: &[u8], out: &mut Vec<u8>) {
730    out.push(b'"');
731    for &c in s {
732        match c {
733            b'"' => out.extend_from_slice(b"\\\""),
734            b'\\' => out.extend_from_slice(b"\\\\"),
735            0x08 => out.extend_from_slice(b"\\b"),
736            0x0c => out.extend_from_slice(b"\\f"),
737            b'\n' => out.extend_from_slice(b"\\n"),
738            b'\r' => out.extend_from_slice(b"\\r"),
739            b'\t' => out.extend_from_slice(b"\\t"),
740            0..=0x1f => write!(Sink(out), "\\u{c:04x}").expect("a Vec never fails a write"),
741            _ => out.push(c),
742        }
743    }
744    out.push(b'"');
745}
746
747/// A `Vec<u8>` that `write!` can be pointed at, so that formatting a number
748/// lands in the caller's buffer instead of in a `String` on the way there.
749struct Sink<'a>(&'a mut Vec<u8>);
750
751impl core::fmt::Write for Sink<'_> {
752    fn write_str(&mut self, s: &str) -> core::fmt::Result {
753        self.0.extend_from_slice(s.as_bytes());
754        Ok(())
755    }
756}
757
758fn unreadable() -> Error {
759    Error::new(Code::Corrupt, "a value whose header and payload disagree")
760}
761
762#[cfg(test)]
763mod tests {
764    use super::*;
765    use yo_common::Rng;
766
767    /// The text, through the encoding, and back to text.
768    fn round(text: &str) -> String {
769        let bytes = from_json(text.as_bytes()).expect("the text parses");
770        let v = Value::new(&bytes).expect("readable");
771        assert!(
772            v.validate(),
773            "the encoding this produced does not check out"
774        );
775        String::from_utf8(v.to_json().expect("writable")).expect("UTF-8")
776    }
777
778    fn why(text: &str) -> String {
779        from_json(text.as_bytes())
780            .expect_err("this should not parse")
781            .message()
782            .to_string()
783    }
784
785    /// The text, through the encoding, and back out laid out the way `f` asks.
786    fn laid_out(text: &str, f: &Format<'_>) -> String {
787        let bytes = from_json(text.as_bytes()).expect("the text parses");
788        let v = Value::new(&bytes).expect("readable");
789        let mut out = Vec::new();
790        v.write_json_with(f, &mut out).expect("writable");
791        String::from_utf8(out).expect("UTF-8")
792    }
793
794    #[test]
795    fn a_document_is_laid_out_the_way_json_get_asks_for() {
796        let f = Format {
797            indent: b"  ",
798            newline: b"\n",
799            space: b" ",
800        };
801        assert_eq!(
802            laid_out(r#"{"a":1,"bb":[2,3]}"#, &f),
803            "{\n  \"a\": 1,\n  \"bb\": [\n    2,\n    3\n  ]\n}"
804        );
805        // A container with nothing in it has nothing to lay out, so it stays on
806        // the one line either way.
807        assert_eq!(
808            laid_out(r#"{"a":{},"bb":[]}"#, &f),
809            "{\n  \"a\": {},\n  \"bb\": []\n}"
810        );
811        // A scalar is a scalar whatever was asked for.
812        assert_eq!(laid_out("1.5", &f), "1.5");
813        // Asking for nothing is the compact form, byte for byte.
814        assert_eq!(
815            laid_out(r#"{"a":1,"bb":[2,3]}"#, &Format::default()),
816            round(r#"{"a":1,"bb":[2,3]}"#)
817        );
818        // The three are separate, so a client that asked for one gets one.
819        let only_space = Format {
820            space: b" ",
821            ..Format::default()
822        };
823        assert_eq!(
824            laid_out(r#"{"a":1,"bb":2}"#, &only_space),
825            r#"{"a": 1,"bb": 2}"#
826        );
827    }
828
829    #[test]
830    fn a_document_comes_back_as_the_text_it_went_in_as() {
831        assert_eq!(round("null"), "null");
832        assert_eq!(round("true"), "true");
833        assert_eq!(round("false"), "false");
834        assert_eq!(round("0"), "0");
835        assert_eq!(round("-17"), "-17");
836        assert_eq!(round(r#""hello""#), r#""hello""#);
837        assert_eq!(round("[]"), "[]");
838        assert_eq!(round("{}"), "{}");
839        assert_eq!(round(r#"[1,[2,[3]]]"#), "[1,[2,[3]]]");
840        assert_eq!(round(r#"{"a":{"b":[1,2,3]}}"#), r#"{"a":{"b":[1,2,3]}}"#);
841    }
842
843    #[test]
844    fn whitespace_is_allowed_where_json_allows_it_and_is_not_kept() {
845        assert_eq!(round("  \t\r\n [ 1 , 2 ]  \n"), "[1,2]");
846        assert_eq!(round("{ \"a\" : 1 , \"b\" : 2 }"), r#"{"a":1,"b":2}"#);
847    }
848
849    #[test]
850    fn a_whole_number_stays_whole_and_the_rest_do_not() {
851        assert_eq!(round("1"), "1");
852        assert_eq!(round("1.0"), "1.0");
853        assert_eq!(round("1e2"), "100.0");
854        assert_eq!(round("-0.5"), "-0.5");
855        assert_eq!(round("9223372036854775807"), "9223372036854775807");
856        // One past an i64, which is the point the split has to happen at. It
857        // comes back in exponent form because a double that big is written the
858        // way doubles that big are written.
859        assert_eq!(round("9223372036854775808"), "9.223372036854776e18");
860        assert_eq!(round("1e15"), "1000000000000000.0");
861        assert_eq!(round("1e16"), "1e16");
862        assert_eq!(round("1e-5"), "0.00001");
863        assert_eq!(round("1e-6"), "1e-6");
864        assert_eq!(round("-1e17"), "-1e17");
865        assert_eq!(round("5e-324"), "5e-324");
866        let bytes = from_json(b"1").expect("parses");
867        assert_eq!(Value::new(&bytes).expect("readable").kind(), Kind::Int);
868        let bytes = from_json(b"1.0").expect("parses");
869        assert_eq!(Value::new(&bytes).expect("readable").kind(), Kind::Float);
870        // A negative zero is the one whole number that is a double, because an
871        // integer cannot hold the sign and losing it would change the document.
872        assert_eq!(round("-0"), "-0.0");
873        assert_eq!(round("0"), "0");
874        let bytes = from_json(b"-0").expect("parses");
875        assert_eq!(Value::new(&bytes).expect("readable").kind(), Kind::Float);
876    }
877
878    #[test]
879    fn json_resp_writes_a_double_the_way_redis_writes_one_and_not_the_way_json_does() {
880        fn resp(f: f64) -> String {
881            let mut out = Vec::new();
882            write_resp_float(f, &mut out);
883            String::from_utf8(out).expect("digits are UTF-8")
884        }
885
886        // A whole number a long long can hold loses the `.0` the JSON writer
887        // puts on it, and the edge is at half a long long rather than at the
888        // whole of one.
889        assert_eq!(resp(1.0), "1");
890        assert_eq!(resp(1e16), "10000000000000000");
891        assert_eq!(resp(4e18), "4000000000000000000");
892        assert_eq!(resp(-4e18), "-4000000000000000000");
893        assert_eq!(resp(5e18), "5e+18");
894        // A whole number too big for that is still written out in full while it
895        // is short enough, which is what the digit count rule below is for.
896        assert_eq!(resp(1.2345678901234567e19), "12345678901234567000");
897        assert_eq!(resp(1e19), "1e+19");
898        assert_eq!(resp(1.5e19), "1.5e+19");
899        assert_eq!(resp(1e300), "1e+300");
900        assert_eq!(resp(f64::MAX), "1.7976931348623157e+308");
901        // A fraction keeps the point down to `1e-6` and goes to exponent form
902        // at `1e-7`, which is not where the JSON writer switches.
903        assert_eq!(resp(2.5), "2.5");
904        assert_eq!(resp(0.1), "0.1");
905        assert_eq!(resp(123.456), "123.456");
906        assert_eq!(resp(1e-6), "0.000001");
907        assert_eq!(resp(1e-7), "1e-7");
908        assert_eq!(resp(5e-324), "5e-324");
909        assert_eq!(resp(-1.5), "-1.5");
910        // A zero keeps its sign, since that is the one thing about a zero worth
911        // keeping.
912        assert_eq!(resp(0.0), "0");
913        assert_eq!(resp(-0.0), "-0");
914    }
915
916    #[test]
917    fn an_escape_is_read_and_only_written_back_when_it_has_to_be() {
918        assert_eq!(round(r#""a\"b""#), r#""a\"b""#);
919        assert_eq!(round(r#""a\\b""#), r#""a\\b""#);
920        assert_eq!(round(r#""a\nb""#), r#""a\nb""#);
921        assert_eq!(round(r#""a\tb""#), r#""a\tb""#);
922        assert_eq!(round(r#""a b""#), r#""a b""#);
923        // A solidus may be escaped and does not have to be, so it goes in one
924        // way and comes out the other.
925        assert_eq!(round(r#""a\/b""#), r#""a/b""#);
926        // And anything that is already a character comes back as that
927        // character rather than as an escape.
928        assert_eq!(round(r#""é""#), "\"\u{e9}\"");
929        assert_eq!(round(r#""😀""#), "\"\u{1f600}\"");
930        assert_eq!(round("\"caf\u{e9}\""), "\"caf\u{e9}\"");
931    }
932
933    #[test]
934    fn a_string_with_no_escapes_in_it_is_not_copied_through_the_scratch() {
935        let mut r = Reader {
936            text: br#""plain" "esc\n""#,
937            at: 0,
938            scratch: Vec::new(),
939        };
940        assert!(matches!(r.string().expect("parses"), Str::Plain(1, 6)));
941        r.space();
942        assert!(matches!(r.string().expect("parses"), Str::Escaped));
943        assert_eq!(r.scratch, b"esc\n");
944    }
945
946    #[test]
947    fn an_object_comes_back_in_key_order_and_the_last_of_a_repeated_key_wins() {
948        assert_eq!(round(r#"{"b":1,"a":2}"#), r#"{"a":2,"b":1}"#);
949        assert_eq!(round(r#"{"a":1,"a":2}"#), r#"{"a":2}"#);
950    }
951
952    #[test]
953    fn the_parser_refuses_what_is_not_json() {
954        assert!(why("").contains("ends where a value should be"));
955        assert!(why("[1,]").contains("not the start of a value"));
956        assert!(why("[1 2]").contains("`,` or by `]`"));
957        assert!(why("{a:1}").contains("key is a string"));
958        assert!(why(r#"{"a" 1}"#).contains("followed by `:`"));
959        assert!(why(r#"{"a":1,}"#).contains("key is a string"));
960        assert!(why("'a'").contains("not the start of a value"));
961        assert!(why("01").contains("more text after the value"));
962        assert!(why("+1").contains("not the start of a value"));
963        assert!(why("1.").contains("no digits after it"));
964        assert!(why(".5").contains("not the start of a value"));
965        assert!(why("1e").contains("exponent with no digits"));
966        assert!(why("NaN").contains("not the start of a value"));
967        assert!(why("Infinity").contains("not the start of a value"));
968        assert!(why("nul").contains("not the start of a value"));
969        assert!(why("1 2").contains("more text after the value"));
970        assert!(why("// a comment\n1").contains("not the start of a value"));
971        assert!(why("\"a\nb\"").contains("control byte inside a string"));
972        assert!(why(r#""a"#).contains("ends inside a string"));
973        assert!(why(r#""\x""#).contains("not an escape JSON has"));
974        assert!(why(r#""\u00"#).contains("fewer than four hex digits"));
975        assert!(why(r#""\uzzzz""#).contains("not a hex digit"));
976        assert!(why(r#""\ud83d""#).contains("no low surrogate after it"));
977        assert!(why(r#""\ude00""#).contains("no high surrogate before it"));
978        assert!(why(r#""\ud83da""#).contains("no low surrogate after it"));
979        assert!(why(r#""\ud83d\u0041""#).contains("something that is not a low one"));
980        assert!(why("[").contains("ends where a value should be"));
981        assert!(why("{").contains("key is a string"));
982    }
983
984    #[test]
985    fn an_error_says_where_it_was() {
986        assert!(why("[1, 2, x]").contains("at byte 7"));
987    }
988
989    /// The two halves against each other over documents nobody chose.
990    ///
991    /// A hand written table of cases tests the cases somebody thought of, and
992    /// the way a parser and a writer disagree is almost always over something
993    /// neither author thought of. So this builds documents at random, writes
994    /// them out and reads them back, and asks for the encoding to be the same
995    /// bytes both times. That is a stronger claim than the text matching:
996    /// identical bytes means every type, every key and every number survived,
997    /// and it catches a writer that loses a distinction the encoding was
998    /// keeping.
999    #[test]
1000    fn a_document_survives_being_written_out_and_read_back() {
1001        let mut rng = Rng::new(0x0d0c);
1002        // Fewer documents under Miri. Each one is grown at random and the
1003        // shapes repeat long before the count runs out, so this is a budget
1004        // rather than a size.
1005        let rounds = if cfg!(miri) { 60 } else { 500 };
1006        for _ in 0..rounds {
1007            let mut b = Builder::new();
1008            grow(&mut b, &mut rng, 0);
1009            let first = b.finish().expect("finished").to_vec();
1010
1011            let v = Value::new(&first).expect("readable");
1012            let text = v.to_json().expect("writable");
1013            let again = from_json(&text)
1014                .unwrap_or_else(|e| panic!("{}: {}", String::from_utf8_lossy(&text), e.message()));
1015            assert_eq!(
1016                first,
1017                again,
1018                "{} did not come back as itself",
1019                String::from_utf8_lossy(&text)
1020            );
1021        }
1022    }
1023
1024    /// One random value, and whatever it decides to nest under itself.
1025    ///
1026    /// The scalars are the ones JSON can carry, so no infinity, no NaN and no
1027    /// string that is not UTF-8, since those are cases the writer refuses on
1028    /// purpose and they have their own tests. The strings are drawn from bytes
1029    /// that need escaping and bytes that do not, in both planes, because the
1030    /// escaping is the half of this most likely to be wrong.
1031    fn grow(b: &mut Builder, rng: &mut Rng, depth: usize) {
1032        const CHARS: [char; 12] = [
1033            'a',
1034            'z',
1035            '"',
1036            '\\',
1037            '\n',
1038            '\t',
1039            '\u{0}',
1040            '\u{1f}',
1041            '/',
1042            '\u{e9}',
1043            '\u{4e2d}',
1044            '\u{1f600}',
1045        ];
1046        let pick = rng.next_u64() % if depth >= 4 { 6 } else { 8 };
1047        match pick {
1048            0 => b.null().expect("value"),
1049            1 => b.bool(rng.next_u64() & 1 == 0).expect("value"),
1050            2 => b.int(rng.next_u64() as i64).expect("value"),
1051            3 => b
1052                .float(f64::from_bits(rng.next_u64()).clamp(-1e300, 1e300))
1053                .expect("value"),
1054            4 => b.int(i64::from(rng.next_u64() as u8) - 128).expect("value"),
1055            5 => {
1056                let n = rng.next_u64() as usize % 8;
1057                let s: String = (0..n)
1058                    .map(|_| CHARS[rng.next_u64() as usize % CHARS.len()])
1059                    .collect();
1060                b.text(&s).expect("value");
1061            }
1062            6 => {
1063                b.begin_array().expect("open");
1064                for _ in 0..rng.next_u64() % 4 {
1065                    grow(b, rng, depth + 1);
1066                }
1067                b.end_array().expect("close");
1068            }
1069            _ => {
1070                b.begin_object().expect("open");
1071                for i in 0..rng.next_u64() % 4 {
1072                    // Keys of more than one length, so that the length first
1073                    // ordering is exercised and not only the byte ordering.
1074                    let key = "k".repeat(1 + i as usize % 3) + &i.to_string();
1075                    b.key(key.as_bytes()).expect("key");
1076                    grow(b, rng, depth + 1);
1077                }
1078                b.end_object().expect("close");
1079            }
1080        }
1081    }
1082
1083    #[test]
1084    fn text_that_is_not_utf8_is_refused_before_anything_is_parsed() {
1085        let e = from_json(&[b'"', 0xff, b'"']).expect_err("not UTF-8");
1086        assert!(e.message().contains("not UTF-8"));
1087    }
1088
1089    /// Not shrunk, for the same reason as the builder side of it: the second
1090    /// half of this test parses a document that sits exactly on
1091    /// [`DEPTH_MAX`] and asks for it back unchanged, so the refusal is the
1092    /// limit rather than something short of it. Any smaller number tests a
1093    /// different limit than the one the parser has.
1094    #[test]
1095    #[cfg_attr(miri, ignore = "the depth limit is the claim and it is 128 levels")]
1096    fn a_document_deeper_than_the_limit_is_refused_rather_than_recursed_into() {
1097        let deep = format!("{}1{}", "[".repeat(200), "]".repeat(200));
1098        assert!(why(&deep).contains("nests at most"));
1099        // And one exactly at the limit is fine, so the refusal is the limit and
1100        // not something one short of it.
1101        let ok = format!("{}1{}", "[".repeat(DEPTH_MAX), "]".repeat(DEPTH_MAX));
1102        assert_eq!(round(&ok), ok);
1103    }
1104
1105    #[test]
1106    fn json_writes_a_value_where_a_value_goes_and_not_only_at_the_root() {
1107        let mut b = Builder::new();
1108        b.begin_object().expect("open");
1109        b.key(b"meta").expect("key");
1110        b.json(br#"{"seen":2,"tags":["a"]}"#).expect("parses");
1111        b.key(b"id").expect("key");
1112        b.int(7).expect("value");
1113        b.end_object().expect("close");
1114        let bytes = b.finish().expect("finished").to_vec();
1115        let v = Value::new(&bytes).expect("readable");
1116        assert_eq!(
1117            v.to_json().expect("writable"),
1118            br#"{"id":7,"meta":{"seen":2,"tags":["a"]}}"#
1119        );
1120    }
1121
1122    #[test]
1123    fn a_float_that_json_cannot_write_says_so_rather_than_writing_something_else() {
1124        let mut b = Builder::new();
1125        b.float(f64::INFINITY).expect("value");
1126        let bytes = b.finish().expect("finished").to_vec();
1127        let v = Value::new(&bytes).expect("readable");
1128        let e = v.to_json().expect_err("infinity is not JSON");
1129        assert!(e.message().contains("infinity or a NaN"));
1130    }
1131
1132    #[test]
1133    fn an_interned_object_needs_the_key_table_and_says_so() {
1134        let mut b = Builder::new();
1135        b.begin_object_interned().expect("open");
1136        b.key_id(3).expect("key");
1137        b.int(1).expect("value");
1138        b.end_object().expect("close");
1139        let bytes = b.finish().expect("finished").to_vec();
1140        let v = Value::new(&bytes).expect("readable");
1141        let e = v.to_json().expect_err("there is no table here");
1142        assert!(e.message().contains("key table"));
1143    }
1144
1145    #[test]
1146    fn the_writer_appends_rather_than_replacing_what_is_in_the_buffer() {
1147        let bytes = from_json(b"[1,2]").expect("parses");
1148        let mut out = b"before ".to_vec();
1149        Value::new(&bytes)
1150            .expect("readable")
1151            .write_json(&mut out)
1152            .expect("writable");
1153        assert_eq!(out, b"before [1,2]");
1154    }
1155}