Skip to main content

yo_doc/
read.rs

1//! Reading a YOJB value without decoding it.
2//!
3//! Every accessor here is bounds checked and answers `None` rather than
4//! panicking, because these bytes come off a disk and a corrupt document is a
5//! thing that happens. Nothing here allocates and nothing here copies: a child
6//! is a slice of its parent and a string is a slice of the document.
7
8use core::cmp::Ordering;
9
10use crate::head::{self, ARRAY, COUNT_MAX, DEPTH_MAX, INTERNED, Kind, OFFSETS, SORTED, Tag};
11
12/// A value, borrowed from the bytes it is stored in.
13///
14/// The slice starts at the value's header and may run past its end, which is
15/// what makes a child free: it is the parent's slice from the child's offset,
16/// with no length to compute. Use [`Value::encoded_len`] when the exact end
17/// matters, which is when the value is being copied somewhere else.
18#[derive(Clone, Copy)]
19pub struct Value<'a> {
20    b: &'a [u8],
21}
22
23impl<'a> Value<'a> {
24    /// A value over `bytes`, if the header at the front is one this version
25    /// understands and its payload is there.
26    ///
27    /// This is a header check and not a walk. It is what a read does, because a
28    /// read touches one path and checking the whole document to answer one
29    /// field would cost more than the read. [`Value::validate`] is the walk,
30    /// for the caller that is about to trust the whole thing.
31    #[must_use]
32    pub fn new(bytes: &'a [u8]) -> Option<Value<'a>> {
33        let v = Value { b: bytes };
34        let h = head::read(bytes, 0)?;
35        let tag = Tag::of(h)?;
36        if matches!(tag, Tag::Container) {
37            if h & OFFSETS == 0 || head::count(h) > COUNT_MAX {
38                return None;
39            }
40            // The entry table has to be there before anything can be indexed.
41            // The value region is checked per element, on the way in.
42            let end = v.entries_end()?;
43            if bytes.len() < end {
44                return None;
45            }
46        } else if bytes.len() < 4 + head::count(h) {
47            return None;
48        }
49        Some(v)
50    }
51
52    /// What this value is.
53    #[must_use]
54    pub fn kind(&self) -> Kind {
55        match self.tag() {
56            Tag::Null => Kind::Null,
57            Tag::False | Tag::True => Kind::Bool,
58            Tag::Int => Kind::Int,
59            Tag::Float => Kind::Float,
60            Tag::Text => Kind::Text,
61            Tag::Container if self.head() & ARRAY == 0 => Kind::Object,
62            Tag::Container => Kind::Array,
63        }
64    }
65
66    /// Whether this is `null`.
67    #[must_use]
68    pub fn is_null(&self) -> bool {
69        matches!(self.tag(), Tag::Null)
70    }
71
72    /// The boolean this holds, if it holds one.
73    #[must_use]
74    pub fn as_bool(&self) -> Option<bool> {
75        match self.tag() {
76            Tag::False => Some(false),
77            Tag::True => Some(true),
78            _ => None,
79        }
80    }
81
82    /// The integer this holds, if it holds one.
83    ///
84    /// The payload is as narrow as the number allows, so a document full of
85    /// small numbers costs five bytes each rather than twelve, and reading one
86    /// back is a sign extending load of one, two, four or eight bytes.
87    #[must_use]
88    pub fn as_int(&self) -> Option<i64> {
89        if !matches!(self.tag(), Tag::Int) {
90            return None;
91        }
92        let raw = self.payload()?;
93        Some(match raw.len() {
94            1 => i64::from(raw[0] as i8),
95            2 => i64::from(i16::from_le_bytes(raw.try_into().ok()?)),
96            4 => i64::from(i32::from_le_bytes(raw.try_into().ok()?)),
97            8 => i64::from_le_bytes(raw.try_into().ok()?),
98            _ => return None,
99        })
100    }
101
102    /// The float this holds, if it holds one.
103    #[must_use]
104    pub fn as_float(&self) -> Option<f64> {
105        if !matches!(self.tag(), Tag::Float) {
106            return None;
107        }
108        let raw = self.payload()?;
109        Some(f64::from_le_bytes(raw.try_into().ok()?))
110    }
111
112    /// The string this holds, if it holds one and it is UTF-8.
113    #[must_use]
114    pub fn as_text(&self) -> Option<&'a str> {
115        core::str::from_utf8(self.text_bytes()?).ok()
116    }
117
118    /// The string this holds as it is stored, without the UTF-8 check.
119    ///
120    /// A string written through this crate is UTF-8 by construction, so the
121    /// check only ever catches a damaged file. A caller that is going to hand
122    /// the bytes straight back out over RESP does not need it.
123    #[must_use]
124    pub fn text_bytes(&self) -> Option<&'a [u8]> {
125        if !matches!(self.tag(), Tag::Text) {
126            return None;
127        }
128        self.payload()
129    }
130
131    /// How many elements a container holds. Zero for anything else.
132    #[must_use]
133    pub fn len(&self) -> usize {
134        if matches!(self.tag(), Tag::Container) {
135            head::count(self.head())
136        } else {
137            0
138        }
139    }
140
141    /// Whether this is a container with nothing in it.
142    ///
143    /// A scalar is not empty, it is not a container, so this is false for one.
144    #[must_use]
145    pub fn is_empty(&self) -> bool {
146        matches!(self.tag(), Tag::Container) && self.len() == 0
147    }
148
149    /// Whether this object's keys are ids from a collection's intern table
150    /// rather than bytes stored with the document.
151    ///
152    /// Nothing else about reading changes, except that a lookup is by id and
153    /// getting a key's name back needs the table.
154    #[must_use]
155    pub fn is_interned(&self) -> bool {
156        self.is_container() && self.head() & INTERNED != 0
157    }
158
159    /// The value of element `i`, counting in the container's own order.
160    ///
161    /// For an array that is the order the elements were written in. For an
162    /// object it is key order, which is not the order the document was written
163    /// in, and it is the order [`Value::members`] walks.
164    #[must_use]
165    pub fn at(&self, i: usize) -> Option<Value<'a>> {
166        let (_, off) = self.entry(i)?;
167        let child = self.b.get(off..)?;
168        Value::new(child)
169    }
170
171    /// The key of member `i` of an object, if the object stores its keys as
172    /// bytes.
173    #[must_use]
174    pub fn key_at(&self, i: usize) -> Option<&'a [u8]> {
175        if !self.is_object() || self.is_interned() {
176            return None;
177        }
178        let at = self.key_off(i)?;
179        let end = self.key_end(i)?;
180        self.b.get(at..end)
181    }
182
183    /// The intern table id of member `i` of an object, if the object stores its
184    /// keys as ids.
185    #[must_use]
186    pub fn key_id_at(&self, i: usize) -> Option<u16> {
187        if !self.is_interned() {
188            return None;
189        }
190        let at = 4 + i * 2;
191        let raw = self.b.get(at..at + 2)?;
192        Some(u16::from_le_bytes(raw.try_into().expect("two bytes")))
193    }
194
195    /// The value stored under `key`, by binary search over the entry table.
196    ///
197    /// Keys are ordered by length and then by bytes, so the search compares a
198    /// length before it compares anything else and most steps never touch the
199    /// key region at all. This is the lookup G15 is about: for a document whose
200    /// keys are interned it is not even this, it is [`Value::get_id`].
201    #[must_use]
202    pub fn get(&self, key: &[u8]) -> Option<Value<'a>> {
203        self.at(self.find(key)?)
204    }
205
206    /// The index of `key` among this object's members.
207    #[must_use]
208    pub fn find(&self, key: &[u8]) -> Option<usize> {
209        if !self.is_object() || self.is_interned() {
210            return None;
211        }
212        let n = self.len();
213        if self.head() & SORTED == 0 {
214            return (0..n).find(|&i| self.key_at(i) == Some(key));
215        }
216        let (mut lo, mut hi) = (0usize, n);
217        while lo < hi {
218            let mid = (lo + hi) / 2;
219            match key_order(self.key_at(mid)?, key) {
220                Ordering::Less => lo = mid + 1,
221                Ordering::Greater => hi = mid,
222                Ordering::Equal => return Some(mid),
223            }
224        }
225        None
226    }
227
228    /// The value stored under intern table id `id`.
229    #[must_use]
230    pub fn get_id(&self, id: u16) -> Option<Value<'a>> {
231        self.at(self.find_id(id)?)
232    }
233
234    /// The index of intern table id `id` among this object's members.
235    #[must_use]
236    pub fn find_id(&self, id: u16) -> Option<usize> {
237        if !self.is_interned() {
238            return None;
239        }
240        let n = self.len();
241        if self.head() & SORTED == 0 {
242            return (0..n).find(|&i| self.key_id_at(i) == Some(id));
243        }
244        let (mut lo, mut hi) = (0usize, n);
245        while lo < hi {
246            let mid = (lo + hi) / 2;
247            match self.key_id_at(mid)?.cmp(&id) {
248                Ordering::Less => lo = mid + 1,
249                Ordering::Greater => hi = mid,
250                Ordering::Equal => return Some(mid),
251            }
252        }
253        None
254    }
255
256    /// Every element of a container, in the container's own order.
257    #[must_use]
258    pub fn iter(&self) -> Elems<'a> {
259        Elems { v: *self, i: 0 }
260    }
261
262    /// Every member of an object, key first, in key order.
263    ///
264    /// An interned object yields nothing here, because the names are not in the
265    /// document. Walk it with [`Value::key_id_at`] and [`Value::at`].
266    #[must_use]
267    pub fn members(&self) -> Members<'a> {
268        Members { v: *self, i: 0 }
269    }
270
271    /// How many bytes this value occupies, header included.
272    ///
273    /// A container works this out from its last element, which recurses down
274    /// the right hand edge of the document and so costs one step per level
275    /// rather than one per element. That is the price of not spending four
276    /// bytes a container on a length nothing else needs.
277    #[must_use]
278    pub fn encoded_len(&self) -> Option<usize> {
279        self.encoded_len_at(0)
280    }
281
282    fn encoded_len_at(&self, depth: usize) -> Option<usize> {
283        if depth > DEPTH_MAX {
284            return None;
285        }
286        let h = self.head();
287        if !matches!(Tag::of(h)?, Tag::Container) {
288            return Some(4 + head::count(h));
289        }
290        let n = head::count(h);
291        if n == 0 {
292            return self.entries_end();
293        }
294        let (_, off) = self.entry(n - 1)?;
295        let last = Value::new(self.b.get(off..)?)?;
296        off.checked_add(last.encoded_len_at(depth + 1)?)
297    }
298
299    /// Where this value begins inside `root`, in bytes.
300    ///
301    /// A child is its parent's slice from the child's offset, so the offset is
302    /// still there to be read back off the slice itself and nothing has to be
303    /// carried alongside it. This is how a write identifies the places a path
304    /// matched: [`Path::select`](crate::Path::select) answers values, and a
305    /// value plus the document it came out of is an offset, which is what
306    /// [`edit`](crate::edit()) takes.
307    ///
308    /// `None` if this value did not come out of `root`.
309    #[must_use]
310    pub fn offset_in(&self, root: &Value<'_>) -> Option<usize> {
311        let here = self.b.as_ptr() as usize;
312        let base = root.b.as_ptr() as usize;
313        let off = here.checked_sub(base)?;
314        (off < root.b.len()).then_some(off)
315    }
316
317    /// This value's bytes and nothing after them.
318    #[must_use]
319    pub fn as_bytes(&self) -> Option<&'a [u8]> {
320        self.b.get(..self.encoded_len()?)
321    }
322
323    /// Walk the whole value and check that every part of it is there.
324    ///
325    /// This is what a caller runs over bytes it did not write: a record read
326    /// back from a file that failed its checksum in an interesting way, or a
327    /// document handed in over a socket. Everything it checks, the accessors
328    /// also check one at a time, so a document that fails here still cannot
329    /// make a read panic. It is O(the document).
330    #[must_use]
331    pub fn validate(&self) -> bool {
332        self.validate_at(0)
333    }
334
335    fn validate_at(&self, depth: usize) -> bool {
336        if depth > DEPTH_MAX {
337            return false;
338        }
339        let Some(h) = head::read(self.b, 0) else {
340            return false;
341        };
342        let Some(tag) = Tag::of(h) else {
343            return false;
344        };
345        if !matches!(tag, Tag::Container) {
346            let n = head::count(h);
347            if matches!(tag, Tag::Int) && !matches!(n, 1 | 2 | 4 | 8) {
348                return false;
349            }
350            if matches!(tag, Tag::Float) && n != 8 {
351                return false;
352            }
353            if matches!(tag, Tag::Null | Tag::False | Tag::True) && n != 0 {
354                return false;
355            }
356            return self.b.len() >= 4 + n;
357        }
358        if h & OFFSETS == 0 {
359            return false;
360        }
361        let n = head::count(h);
362        let Some(mut want) = self.entries_end() else {
363            return false;
364        };
365        if self.b.len() < want {
366            return false;
367        }
368        if self.is_object() && !self.is_interned() {
369            // The key region runs from the end of the entry table to the first
370            // value, and the keys inside it have to tile it in order.
371            for i in 0..n {
372                let (Some(at), Some(end)) = (self.key_off(i), self.key_end(i)) else {
373                    return false;
374                };
375                if at != want || end < at || self.b.len() < end {
376                    return false;
377                }
378                want = end;
379            }
380        }
381        for i in 0..n {
382            let Some((copy, off)) = self.entry(i) else {
383                return false;
384            };
385            // Values are stored in entry order and they tile the value region,
386            // which is what lets a length be a difference of two offsets.
387            if off != want {
388                return false;
389            }
390            let Some(child) = self.b.get(off..).and_then(Value::new) else {
391                return false;
392            };
393            if child.head() != copy || !child.validate_at(depth + 1) {
394                return false;
395            }
396            let Some(len) = child.encoded_len_at(depth + 1) else {
397                return false;
398            };
399            want = off + len;
400        }
401        if self.is_object() && h & SORTED != 0 && !self.keys_ascend(n) {
402            return false;
403        }
404        true
405    }
406
407    /// The header word.
408    fn head(&self) -> u32 {
409        head::read(self.b, 0).unwrap_or(0)
410    }
411
412    fn tag(&self) -> Tag {
413        Tag::of(self.head()).unwrap_or(Tag::Null)
414    }
415
416    fn is_container(&self) -> bool {
417        matches!(self.tag(), Tag::Container)
418    }
419
420    fn is_object(&self) -> bool {
421        self.is_container() && self.head() & ARRAY == 0
422    }
423
424    /// A scalar's bytes, after the header.
425    fn payload(&self) -> Option<&'a [u8]> {
426        let n = head::count(self.head());
427        self.b.get(4..4 + n)
428    }
429
430    /// Where the entry table starts, which is after the key entries.
431    fn entries_at(&self) -> usize {
432        4 + crate::layout::keys_area(self.head(), self.len())
433    }
434
435    /// Where the key region starts, which is after the entry table.
436    fn entries_end(&self) -> Option<usize> {
437        self.entries_at().checked_add(self.len().checked_mul(8)?)
438    }
439
440    /// Element `i`'s header copy and where its value starts.
441    fn entry(&self, i: usize) -> Option<(u32, usize)> {
442        if !self.is_container() || i >= self.len() {
443            return None;
444        }
445        let at = self.entries_at() + i * 8;
446        let copy = head::read(self.b, at)?;
447        let off = head::read(self.b, at + 4)? as usize;
448        // A child starts after its parent's entry table, always. Checking it
449        // here rather than only in [`Value::validate`] is what keeps a damaged
450        // offset from making a child that contains its own parent, and so keeps
451        // every walk over a document finite.
452        if off < self.entries_end()? {
453            return None;
454        }
455        Some((copy, off))
456    }
457
458    /// Where member `i`'s key starts.
459    fn key_off(&self, i: usize) -> Option<usize> {
460        if i >= self.len() {
461            return None;
462        }
463        Some(head::read(self.b, 4 + i * 4)? as usize)
464    }
465
466    /// Where member `i`'s key ends.
467    ///
468    /// Keys are stored in member order and tile the key region, so a key ends
469    /// where the next one starts, and the last one ends where the first value
470    /// starts. That is why the region costs four bytes a key rather than eight.
471    fn key_end(&self, i: usize) -> Option<usize> {
472        if i + 1 < self.len() {
473            self.key_off(i + 1)
474        } else {
475            self.entry(0).map(|(_, off)| off)
476        }
477    }
478
479    fn keys_ascend(&self, n: usize) -> bool {
480        for i in 1..n {
481            let ord = if self.is_interned() {
482                match (self.key_id_at(i - 1), self.key_id_at(i)) {
483                    (Some(a), Some(b)) => a.cmp(&b),
484                    _ => return false,
485                }
486            } else {
487                match (self.key_at(i - 1), self.key_at(i)) {
488                    (Some(a), Some(b)) => key_order(a, b),
489                    _ => return false,
490                }
491            };
492            if ord != Ordering::Less {
493                return false;
494            }
495        }
496        true
497    }
498}
499
500impl core::fmt::Debug for Value<'_> {
501    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
502        match self.kind() {
503            Kind::Null => f.write_str("null"),
504            Kind::Bool => write!(f, "{}", self.as_bool().unwrap_or(false)),
505            Kind::Int => write!(f, "{}", self.as_int().unwrap_or(0)),
506            Kind::Float => write!(f, "{}", self.as_float().unwrap_or(0.0)),
507            Kind::Text => write!(f, "{:?}", self.as_text().unwrap_or("")),
508            Kind::Array => f.debug_list().entries(self.iter()).finish(),
509            Kind::Object => {
510                let mut m = f.debug_map();
511                for (k, v) in self.members() {
512                    m.entry(&String::from_utf8_lossy(k), &v);
513                }
514                m.finish()
515            }
516        }
517    }
518}
519
520/// How two object keys compare: shorter first, then by bytes.
521///
522/// Length first is not arbitrary. It puts the cheapest comparison at the front
523/// of the search, so most steps of a lookup are an integer compare against a
524/// number the reader already has, and it keeps keys of one length together in
525/// the key region.
526#[must_use]
527pub fn key_order(a: &[u8], b: &[u8]) -> Ordering {
528    a.len().cmp(&b.len()).then_with(|| a.cmp(b))
529}
530
531/// Every element of a container, from [`Value::iter`].
532#[derive(Clone)]
533pub struct Elems<'a> {
534    v: Value<'a>,
535    i: usize,
536}
537
538impl<'a> Iterator for Elems<'a> {
539    type Item = Value<'a>;
540
541    fn next(&mut self) -> Option<Value<'a>> {
542        let out = self.v.at(self.i)?;
543        self.i += 1;
544        Some(out)
545    }
546
547    fn size_hint(&self) -> (usize, Option<usize>) {
548        let left = self.v.len().saturating_sub(self.i);
549        (left, Some(left))
550    }
551}
552
553/// Every member of an object, from [`Value::members`].
554#[derive(Clone)]
555pub struct Members<'a> {
556    v: Value<'a>,
557    i: usize,
558}
559
560impl<'a> Iterator for Members<'a> {
561    type Item = (&'a [u8], Value<'a>);
562
563    fn next(&mut self) -> Option<(&'a [u8], Value<'a>)> {
564        let key = self.v.key_at(self.i)?;
565        let val = self.v.at(self.i)?;
566        self.i += 1;
567        Some((key, val))
568    }
569
570    fn size_hint(&self) -> (usize, Option<usize>) {
571        let left = self.v.len().saturating_sub(self.i);
572        (left, Some(left))
573    }
574}
575
576#[cfg(test)]
577mod tests {
578    use super::*;
579    use crate::Builder;
580    use yo_common::Rng;
581
582    /// A document with one of everything in it.
583    fn sample() -> Vec<u8> {
584        let mut b = Builder::new();
585        b.begin_object().expect("open");
586        for (k, write) in [
587            ("nil", 0),
588            ("yes", 1),
589            ("n", 2),
590            ("big", 3),
591            ("f", 4),
592            ("s", 5),
593            ("arr", 6),
594            ("obj", 7),
595        ] {
596            b.key(k.as_bytes()).expect("key");
597            match write {
598                0 => b.null().expect("value"),
599                1 => b.bool(true).expect("value"),
600                2 => b.int(-3).expect("value"),
601                3 => b.int(i64::MAX).expect("value"),
602                4 => b.float(0.125).expect("value"),
603                5 => b.text("a string with some length to it").expect("value"),
604                6 => {
605                    b.begin_array().expect("open");
606                    b.int(1).expect("value");
607                    b.text("two").expect("value");
608                    b.end_array().expect("close");
609                }
610                _ => {
611                    b.begin_object().expect("open");
612                    b.key(b"deep").expect("key");
613                    b.int(9).expect("value");
614                    b.end_object().expect("close");
615                }
616            }
617        }
618        b.end_object().expect("close");
619        b.finish().expect("finished").to_vec()
620    }
621
622    /// Touch every accessor on every part of `bytes`, however damaged it is.
623    ///
624    /// The point is that nothing here panics and nothing here runs forever. A
625    /// corrupt count can claim sixteen million elements over four bytes, so the
626    /// walk stops after a few, and a corrupt offset cannot point backwards
627    /// because [`Value::entry`] refuses one that does.
628    fn walk(bytes: &[u8]) {
629        fn go(v: Value<'_>, depth: usize) {
630            if depth > 8 {
631                return;
632            }
633            let _ = v.kind();
634            let _ = v.is_null();
635            let _ = v.as_bool();
636            let _ = v.as_int();
637            let _ = v.as_float();
638            let _ = v.as_text();
639            let _ = v.text_bytes();
640            let _ = v.is_empty();
641            let _ = v.encoded_len();
642            let _ = v.as_bytes();
643            let _ = v.get(b"nil");
644            let _ = v.get_id(3);
645            let _ = v.path("$.a.b[0]");
646            let _ = format!("{v:?}");
647            for i in 0..v.len().min(16) {
648                let _ = v.key_at(i);
649                let _ = v.key_id_at(i);
650                if let Some(child) = v.at(i) {
651                    go(child, depth + 1);
652                }
653            }
654        }
655        if let Some(v) = Value::new(bytes) {
656            let _ = v.validate();
657            go(v, 0);
658        }
659    }
660
661    #[test]
662    fn a_document_cut_short_anywhere_is_refused_and_never_panics() {
663        let bytes = sample();
664        for n in 0..bytes.len() {
665            let cut = &bytes[..n];
666            walk(cut);
667            if let Some(v) = Value::new(cut) {
668                assert!(!v.validate(), "a document missing its tail is not sound");
669            }
670        }
671        assert!(Value::new(&bytes).expect("readable").validate());
672    }
673
674    #[test]
675    fn a_document_with_a_byte_changed_is_never_worse_than_wrong() {
676        let bytes = sample();
677        let mut rng = Rng::new(0x5eed_0d0c);
678        for _ in 0..20_000 {
679            let mut damaged = bytes.clone();
680            let at = rng.below(damaged.len());
681            damaged[at] ^= 1 << rng.below(8);
682            walk(&damaged);
683        }
684    }
685
686    #[test]
687    fn a_child_that_points_at_its_own_parent_is_refused() {
688        let mut bytes = sample();
689        let v = Value::new(&bytes).expect("readable");
690        assert!(v.validate());
691        // The first entry's offset lives right after the key entries and the
692        // header copy. Point it at the container itself.
693        let n = v.len();
694        let entry = 4 + n * 4 + 4;
695        bytes[entry..entry + 4].copy_from_slice(&0u32.to_le_bytes());
696        let v = Value::new(&bytes).expect("the header is still fine");
697        assert!(v.at(0).is_none(), "the child is not readable");
698        assert!(!v.validate());
699        walk(&bytes);
700    }
701
702    #[test]
703    fn a_container_that_claims_more_elements_than_it_has_is_refused() {
704        let bytes = sample();
705        let mut damaged = bytes.clone();
706        let h = u32::from_le_bytes(damaged[..4].try_into().expect("four bytes"));
707        let bigger = (h & 0xff) | ((1u32 << 20) << 8);
708        damaged[..4].copy_from_slice(&bigger.to_le_bytes());
709        assert!(
710            Value::new(&damaged).is_none(),
711            "the entry table would not fit, so the value is not readable at all"
712        );
713        walk(&damaged);
714    }
715
716    #[test]
717    fn keys_sort_by_length_and_then_by_bytes() {
718        let mut keys: Vec<&[u8]> = vec![b"bb", b"a", b"", b"ab", b"z", b"aaa"];
719        keys.sort_by(|a, b| key_order(a, b));
720        assert_eq!(keys, [&b""[..], b"a", b"z", b"ab", b"bb", b"aaa"]);
721    }
722
723    #[test]
724    fn a_document_prints_as_itself() {
725        let bytes = sample();
726        let v = Value::new(&bytes).expect("readable");
727        let text = format!("{v:?}");
728        assert!(text.contains("\"n\": -3"), "{text}");
729        assert!(text.contains("\"arr\": [1, \"two\"]"), "{text}");
730        assert!(text.contains("\"nil\": null"), "{text}");
731    }
732
733    #[test]
734    fn an_unsorted_object_is_still_readable() {
735        // Nothing this crate writes clears the sorted flag, but a later version
736        // might, so a reader that finds it clear falls back to a scan rather
737        // than refusing the document.
738        let mut bytes = sample();
739        let h = u32::from_le_bytes(bytes[..4].try_into().expect("four bytes"));
740        bytes[..4].copy_from_slice(&(h & !SORTED).to_le_bytes());
741        let v = Value::new(&bytes).expect("readable");
742        assert!(v.validate(), "clearing the claim does not make it unsound");
743        assert_eq!(v.get(b"n").expect("found by scan").as_int(), Some(-3));
744        assert!(v.get(b"missing").is_none());
745    }
746}