1use std::fmt;
15use std::io::{self, Read, Write};
16use std::ops::Range;
17use std::sync::Arc;
18
19use serde_json::Value;
20
21use crate::tree::{JsonKey, NodeId, Tree};
22
23const MAX_OBJECT_PREVIEW_BYTES: usize = 512;
26const MAX_OBJECT_PREVIEW_MEMBERS: usize = 32;
28const MAX_DEPTH: usize = 128;
30
31#[derive(Debug)]
32pub struct Error(String);
33
34impl fmt::Display for Error {
35 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36 write!(formatter, "invalid JSON input: {}", self.0)
37 }
38}
39
40impl std::error::Error for Error {}
41
42pub fn from_reader(mut reader: impl Read) -> Result<Tree, Error> {
45 let _span = crate::profile::span("json_tree::load");
46 let bytes = slurp(reader.by_ref())?;
47 if detect_jsonl(&bytes) {
48 jsonl_from_bytes(bytes)
49 } else {
50 json_from_bytes(bytes)
51 }
52}
53
54pub fn jsonl_from_reader(mut reader: impl Read) -> Result<Tree, Error> {
58 let _span = crate::profile::span("json_tree::load");
59 let bytes = slurp(reader.by_ref())?;
60 jsonl_from_bytes(bytes)
61}
62
63fn slurp(reader: &mut impl Read) -> Result<Vec<u8>, Error> {
64 let mut bytes = Vec::new();
65 reader
66 .read_to_end(&mut bytes)
67 .map_err(|error| Error(error.to_string()))?;
68 if u32::try_from(bytes.len()).is_err() {
69 return Err(Error(
70 "input exceeds the 4 GiB the span index addresses".to_owned(),
71 ));
72 }
73 if let Err(error) = std::str::from_utf8(&bytes) {
74 return Err(Error(error.to_string()));
75 }
76 Ok(bytes)
77}
78
79fn detect_jsonl(bytes: &[u8]) -> bool {
85 let Some(start) = bytes
86 .iter()
87 .position(|b| !matches!(b, b' ' | b'\t' | b'\n' | b'\r'))
88 else {
89 return false;
90 };
91 let content = &bytes[start..];
92 let Some(newline) = content.iter().position(|&b| b == b'\n') else {
93 return false;
94 };
95 let (first_line, rest) = content.split_at(newline);
96 if rest
97 .iter()
98 .all(|b| matches!(b, b' ' | b'\t' | b'\n' | b'\r'))
99 {
100 return false;
101 }
102 is_complete_value(first_line)
103}
104
105fn is_complete_value(bytes: &[u8]) -> bool {
107 let mut scanner = Scanner::new(bytes);
108 if scanner.skip_value().is_err() {
109 return false;
110 }
111 scanner.skip_ws();
112 scanner.pos == bytes.len()
113}
114
115fn json_from_bytes(bytes: Vec<u8>) -> Result<Tree, Error> {
116 let mut tree = Tree::new();
117 let mut scanner = Scanner::new(&bytes);
118 scanner.skip_ws();
119 let mut single_root = None;
120 let spread = scanner.peek() == Some(b'{') && {
124 let probe = scanner.pos;
125 scanner.pos += 1;
126 scanner.skip_ws();
127 let has_members = scanner.peek() != Some(b'}');
128 scanner.pos = probe;
129 has_members
130 };
131 if spread {
132 scanner.scan_members_shallow(&mut tree, None)?;
133 } else {
134 single_root = Some(scanner.scan_child(&mut tree, None, JsonKey::Root)?);
135 }
136 scanner.skip_ws();
137 if scanner.pos != bytes.len() {
138 return Err(scanner.syntax_error("trailing characters"));
139 }
140 tree.set_json_source(Arc::new(bytes));
141 if let Some(root) = single_root {
144 tree.ensure_children(root);
145 }
146 Ok(tree)
147}
148
149fn jsonl_from_bytes(bytes: Vec<u8>) -> Result<Tree, Error> {
156 let mut lines = non_blank_lines(&bytes);
157 if let Some(last) = lines.last()
158 && !is_complete_value(&bytes[last.clone()])
159 {
160 lines.pop();
161 }
162 let mut tree = Tree::new();
163 let root = tree.push_json(
164 None,
165 0..bytes.len() as u32,
166 JsonKey::JsonlRoot,
167 true,
168 Some(lines.len() as u32),
169 true,
170 );
171 for (ordinal, line) in lines.iter().enumerate() {
172 let span = trim_ws(&bytes, line.clone());
173 let is_container = matches!(bytes[span.start], b'{' | b'[');
174 tree.push_json(
175 Some(root),
176 span.start as u32..span.end as u32,
177 JsonKey::Index(ordinal as u32),
178 is_container,
179 None,
180 false,
181 );
182 }
183 tree.set_json_source(Arc::new(bytes));
184 Ok(tree)
185}
186
187pub(crate) fn materialize(tree: &mut Tree, id: NodeId) {
192 let (Some(bytes), Some(span)) = (tree.json_source_arc(), tree.json_span(id)) else {
193 tree.mark_children_loaded(id);
194 return;
195 };
196 let end = span.end as usize;
197 let mark = tree.len();
198 let mut scanner = Scanner::new(&bytes[..end]);
199 scanner.pos = span.start as usize;
200 match scan_span(&mut scanner, tree, id, end) {
201 Ok(()) => tree.mark_children_loaded(id),
202 Err(error) => {
203 tree.truncate(mark);
204 tree.set_json_error(id, error.to_string());
205 }
206 }
207}
208
209fn scan_span(scanner: &mut Scanner, tree: &mut Tree, id: NodeId, end: usize) -> Result<(), Error> {
210 scanner.skip_ws();
211 match scanner.peek() {
212 Some(b'{') => scanner.scan_members_shallow(tree, Some(id))?,
213 Some(b'[') => scanner.scan_elements_shallow(tree, id)?,
214 _ => {
215 scanner.scan_scalar()?;
216 }
217 }
218 scanner.skip_ws();
219 if scanner.pos != end {
220 return Err(scanner.syntax_error("trailing characters"));
221 }
222 Ok(())
223}
224
225fn non_blank_lines(bytes: &[u8]) -> Vec<Range<usize>> {
227 let mut lines = Vec::new();
228 let mut start = 0;
229 for (position, &byte) in bytes.iter().enumerate() {
230 if byte == b'\n' {
231 push_if_non_blank(bytes, start..position, &mut lines);
232 start = position + 1;
233 }
234 }
235 push_if_non_blank(bytes, start..bytes.len(), &mut lines);
236 lines
237}
238
239fn push_if_non_blank(bytes: &[u8], range: Range<usize>, lines: &mut Vec<Range<usize>>) {
240 if bytes[range.clone()]
241 .iter()
242 .any(|b| !matches!(b, b' ' | b'\t' | b'\r'))
243 {
244 lines.push(range);
245 }
246}
247
248fn trim_ws(bytes: &[u8], mut range: Range<usize>) -> Range<usize> {
249 while range.start < range.end && matches!(bytes[range.start], b' ' | b'\t' | b'\r') {
250 range.start += 1;
251 }
252 while range.end > range.start && matches!(bytes[range.end - 1], b' ' | b'\t' | b'\r') {
253 range.end -= 1;
254 }
255 range
256}
257
258struct Scanner<'a> {
263 bytes: &'a [u8],
264 pos: usize,
265 depth: usize,
266}
267
268impl<'a> Scanner<'a> {
269 fn new(bytes: &'a [u8]) -> Self {
270 Self {
271 bytes,
272 pos: 0,
273 depth: 0,
274 }
275 }
276
277 fn peek(&self) -> Option<u8> {
278 self.bytes.get(self.pos).copied()
279 }
280
281 fn skip_ws(&mut self) {
282 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
283 self.pos += 1;
284 }
285 }
286
287 fn expect(&mut self, byte: u8) -> Result<(), Error> {
288 if self.peek() == Some(byte) {
289 self.pos += 1;
290 Ok(())
291 } else {
292 Err(self.syntax_error(&format!("expected `{}`", byte as char)))
293 }
294 }
295
296 fn syntax_error(&self, message: &str) -> Error {
297 let consumed = &self.bytes[..self.pos.min(self.bytes.len())];
298 let line = 1 + consumed.iter().filter(|&&b| b == b'\n').count();
299 let line_start = consumed
300 .iter()
301 .rposition(|&b| b == b'\n')
302 .map_or(0, |i| i + 1);
303 let column = self.pos - line_start + 1;
304 Error(format!("{message} at line {line} column {column}"))
305 }
306
307 fn descend(&mut self) -> Result<(), Error> {
308 self.depth += 1;
309 if self.depth > MAX_DEPTH {
310 return Err(self.syntax_error("recursion limit exceeded"));
311 }
312 Ok(())
313 }
314
315 fn scan_child(
319 &mut self,
320 tree: &mut Tree,
321 parent: Option<NodeId>,
322 key: JsonKey,
323 ) -> Result<NodeId, Error> {
324 self.skip_ws();
325 let start = self.pos as u32;
326 let child_count = self.skip_value()?;
327 let span = start..self.pos as u32;
328 Ok(match child_count {
329 Some(count) => tree.push_json(parent, span, key, true, Some(count), count == 0),
330 None => tree.push_json(parent, span, key, false, None, true),
331 })
332 }
333
334 fn scan_members_shallow(
337 &mut self,
338 tree: &mut Tree,
339 parent: Option<NodeId>,
340 ) -> Result<(), Error> {
341 self.expect(b'{')?;
342 self.skip_ws();
343 if self.peek() == Some(b'}') {
344 self.pos += 1;
345 return Ok(());
346 }
347 loop {
348 self.skip_ws();
349 let key_start = self.pos as u32;
350 self.scan_string()?;
351 let key_span = key_start..self.pos as u32;
352 self.skip_ws();
353 self.expect(b':')?;
354 self.scan_child(tree, parent, JsonKey::Member { key_span })?;
355 self.skip_ws();
356 match self.peek() {
357 Some(b',') => self.pos += 1,
358 Some(b'}') => {
359 self.pos += 1;
360 return Ok(());
361 }
362 _ => return Err(self.syntax_error("expected `,` or `}`")),
363 }
364 }
365 }
366
367 fn scan_elements_shallow(&mut self, tree: &mut Tree, parent: NodeId) -> Result<(), Error> {
368 self.expect(b'[')?;
369 self.skip_ws();
370 if self.peek() == Some(b']') {
371 self.pos += 1;
372 return Ok(());
373 }
374 let mut index = 0;
375 loop {
376 self.scan_child(tree, Some(parent), JsonKey::Index(index))?;
377 index += 1;
378 self.skip_ws();
379 match self.peek() {
380 Some(b',') => self.pos += 1,
381 Some(b']') => {
382 self.pos += 1;
383 return Ok(());
384 }
385 _ => return Err(self.syntax_error("expected `,` or `]`")),
386 }
387 }
388 }
389
390 fn skip_value(&mut self) -> Result<Option<u32>, Error> {
393 self.skip_ws();
394 match self.peek() {
395 Some(b'{') => {
396 self.descend()?;
397 let count = self.skip_object()?;
398 self.depth -= 1;
399 Ok(Some(count))
400 }
401 Some(b'[') => {
402 self.descend()?;
403 let count = self.skip_array()?;
404 self.depth -= 1;
405 Ok(Some(count))
406 }
407 _ => {
408 self.scan_scalar()?;
409 Ok(None)
410 }
411 }
412 }
413
414 fn skip_object(&mut self) -> Result<u32, Error> {
415 self.expect(b'{')?;
416 self.skip_ws();
417 if self.peek() == Some(b'}') {
418 self.pos += 1;
419 return Ok(0);
420 }
421 let mut count = 0;
422 loop {
423 self.skip_ws();
424 self.scan_string()?;
425 self.skip_ws();
426 self.expect(b':')?;
427 self.skip_value()?;
428 count += 1;
429 self.skip_ws();
430 match self.peek() {
431 Some(b',') => self.pos += 1,
432 Some(b'}') => {
433 self.pos += 1;
434 return Ok(count);
435 }
436 _ => return Err(self.syntax_error("expected `,` or `}`")),
437 }
438 }
439 }
440
441 fn skip_array(&mut self) -> Result<u32, Error> {
442 self.expect(b'[')?;
443 self.skip_ws();
444 if self.peek() == Some(b']') {
445 self.pos += 1;
446 return Ok(0);
447 }
448 let mut count = 0;
449 loop {
450 self.skip_value()?;
451 count += 1;
452 self.skip_ws();
453 match self.peek() {
454 Some(b',') => self.pos += 1,
455 Some(b']') => {
456 self.pos += 1;
457 return Ok(count);
458 }
459 _ => return Err(self.syntax_error("expected `,` or `]`")),
460 }
461 }
462 }
463
464 fn scan_scalar(&mut self) -> Result<(), Error> {
465 match self.peek() {
466 Some(b'"') => self.scan_string(),
467 Some(b't') => self.scan_literal("true"),
468 Some(b'f') => self.scan_literal("false"),
469 Some(b'n') => self.scan_literal("null"),
470 Some(b'-' | b'0'..=b'9') => self.scan_number(),
471 Some(other) => {
472 Err(self.syntax_error(&format!("unexpected character `{}`", other as char)))
473 }
474 None => Err(self.syntax_error("unexpected end of input")),
475 }
476 }
477
478 fn scan_literal(&mut self, literal: &str) -> Result<(), Error> {
479 if self.bytes[self.pos..].starts_with(literal.as_bytes()) {
480 self.pos += literal.len();
481 Ok(())
482 } else {
483 Err(self.syntax_error(&format!("expected `{literal}`")))
484 }
485 }
486
487 fn scan_string(&mut self) -> Result<(), Error> {
488 self.expect(b'"')?;
489 loop {
490 match self.peek() {
491 None => return Err(self.syntax_error("unterminated string")),
492 Some(b'"') => {
493 self.pos += 1;
494 return Ok(());
495 }
496 Some(b'\\') => {
497 self.pos += 1;
498 match self.peek() {
499 Some(b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't') => {
500 self.pos += 1;
501 }
502 Some(b'u') => {
503 self.pos += 1;
504 for _ in 0..4 {
505 if !self.peek().is_some_and(|b| b.is_ascii_hexdigit()) {
506 return Err(self.syntax_error("invalid \\u escape"));
507 }
508 self.pos += 1;
509 }
510 }
511 _ => return Err(self.syntax_error("invalid escape")),
512 }
513 }
514 Some(byte) if byte < 0x20 => {
515 return Err(self.syntax_error("control character in string"));
516 }
517 Some(_) => self.pos += 1,
518 }
519 }
520 }
521
522 fn scan_number(&mut self) -> Result<(), Error> {
523 if self.peek() == Some(b'-') {
524 self.pos += 1;
525 }
526 match self.peek() {
527 Some(b'0') => self.pos += 1,
528 Some(b'1'..=b'9') => {
529 while self.peek().is_some_and(|b| b.is_ascii_digit()) {
530 self.pos += 1;
531 }
532 }
533 _ => return Err(self.syntax_error("invalid number")),
534 }
535 if self.peek() == Some(b'.') {
536 self.pos += 1;
537 if !self.peek().is_some_and(|b| b.is_ascii_digit()) {
538 return Err(self.syntax_error("invalid number"));
539 }
540 while self.peek().is_some_and(|b| b.is_ascii_digit()) {
541 self.pos += 1;
542 }
543 }
544 if matches!(self.peek(), Some(b'e' | b'E')) {
545 self.pos += 1;
546 if matches!(self.peek(), Some(b'+' | b'-')) {
547 self.pos += 1;
548 }
549 if !self.peek().is_some_and(|b| b.is_ascii_digit()) {
550 return Err(self.syntax_error("invalid number"));
551 }
552 while self.peek().is_some_and(|b| b.is_ascii_digit()) {
553 self.pos += 1;
554 }
555 }
556 Ok(())
557 }
558}
559
560fn slice<'a>(bytes: &'a [u8], span: &Range<u32>) -> &'a [u8] {
563 &bytes[span.start as usize..span.end as usize]
564}
565
566pub(crate) fn key_text(bytes: &[u8], span: &Range<u32>) -> String {
568 let raw = slice(bytes, span);
569 serde_json::from_slice(raw).unwrap_or_else(|_| String::from_utf8_lossy(raw).into_owned())
570}
571
572pub(crate) fn value_text(bytes: &[u8], span: &Range<u32>) -> String {
576 let raw = slice(bytes, span);
577 match serde_json::from_slice::<Value>(raw) {
578 Ok(value) => {
579 serde_json::to_string(&value).expect("serializing a parsed JSON value cannot fail")
580 }
581 Err(_) => String::from_utf8_lossy(raw).into_owned(),
582 }
583}
584
585pub(crate) fn raw_text(bytes: &[u8], span: &Range<u32>) -> String {
587 String::from_utf8_lossy(slice(bytes, span)).into_owned()
588}
589
590pub(crate) fn append_pointer(parent: &str, token: &str) -> String {
591 let token = token.replace('~', "~0").replace('/', "~1");
592 format!("{parent}/{token}")
593}
594
595pub(crate) fn object_preview(
599 bytes: &[u8],
600 members: impl Iterator<Item = Option<(Range<u32>, Range<u32>)>>,
601) -> Option<String> {
602 let mut preview = Preview::new(MAX_OBJECT_PREVIEW_BYTES);
603 for member in members.take(MAX_OBJECT_PREVIEW_MEMBERS) {
604 let Some((key_span, value_span)) = member else {
605 continue;
606 };
607 if !preview.is_empty() && !preview.push_str(" · ") {
608 break;
609 }
610 if !preview.push_str(&key_text(bytes, &key_span)) || !preview.push_str(": ") {
611 break;
612 }
613 preview.push_value(bytes, &value_span);
614 if preview.is_exhausted() {
615 break;
616 }
617 }
618 preview.finish()
619}
620
621struct Preview {
622 text: String,
623 limit: usize,
624 exhausted: bool,
625}
626
627impl Preview {
628 fn new(limit: usize) -> Self {
629 Self {
630 text: String::with_capacity(limit),
631 limit,
632 exhausted: false,
633 }
634 }
635
636 fn is_empty(&self) -> bool {
637 self.text.is_empty()
638 }
639
640 fn is_exhausted(&self) -> bool {
641 self.exhausted
642 }
643
644 fn push_str(&mut self, text: &str) -> bool {
645 if self.exhausted {
646 return false;
647 }
648 let remaining = self.limit.saturating_sub(self.text.len());
649 if text.len() <= remaining {
650 self.text.push_str(text);
651 return true;
652 }
653
654 let mut end = remaining;
655 while !text.is_char_boundary(end) {
656 end -= 1;
657 }
658 self.text.push_str(&text[..end]);
659 self.exhausted = true;
660 false
661 }
662
663 fn push_value(&mut self, bytes: &[u8], span: &Range<u32>) {
664 match serde_json::from_slice::<Value>(slice(bytes, span)) {
665 Ok(value) => {
666 if serde_json::to_writer(&mut *self, &value).is_err() {
667 self.exhausted = true;
668 }
669 }
670 Err(_) => {
671 self.push_str(&String::from_utf8_lossy(slice(bytes, span)));
672 }
673 }
674 }
675
676 fn finish(self) -> Option<String> {
677 (!self.text.is_empty()).then_some(self.text)
678 }
679}
680
681impl Write for Preview {
682 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
683 if bytes.is_empty() {
684 return Ok(0);
685 }
686 if self.exhausted {
687 return Err(io::ErrorKind::WriteZero.into());
688 }
689
690 let remaining = self.limit.saturating_sub(self.text.len());
691 let candidate = &bytes[..bytes.len().min(remaining)];
692 let end = std::str::from_utf8(candidate).map_or_else(|error| error.valid_up_to(), str::len);
693 if end == 0 {
694 self.exhausted = true;
695 return Err(io::ErrorKind::WriteZero.into());
696 }
697
698 self.text.push_str(
699 std::str::from_utf8(&candidate[..end])
700 .expect("a prefix ending on a UTF-8 boundary is valid"),
701 );
702 self.exhausted = end < bytes.len();
703 Ok(end)
704 }
705
706 fn flush(&mut self) -> io::Result<()> {
707 Ok(())
708 }
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use std::ffi::OsStr;
715
716 const DEMO_JSON: &str = include_str!("../examples/sample.json");
717
718 fn parse(json: &str) -> Tree {
721 let mut tree = from_reader(json.as_bytes()).unwrap();
722 tree.index_all();
723 tree
724 }
725
726 fn names(tree: &Tree, ids: &[usize]) -> Vec<String> {
727 ids.iter().map(|&id| tree.name(id)).collect()
728 }
729
730 #[test]
731 fn object_members_become_ordered_roots_with_container_sizes_and_scalar_values() {
732 let tree = parse(
733 r#"{
734 "users": [{"id": 12, "name": "Ada"}, {"id": 27}, null],
735 "empty": [],
736 "settings": {},
737 "enabled": true
738 }"#,
739 );
740
741 assert_eq!(
742 names(&tree, tree.root_ids()),
743 ["users [3]", "empty []", "settings {}", "enabled: true"]
744 );
745 assert!(tree.is_container(tree.root_ids()[0]));
746 assert!(tree.is_container(tree.root_ids()[1]));
747 assert!(!tree.is_leaf(tree.root_ids()[1]));
749 }
750
751 #[test]
752 fn json_containers_load_lazily_with_discovered_counts() {
753 let mut tree =
754 from_reader(r#"{"users": [{"id": 1}, {"id": 2}], "n": 3}"#.as_bytes()).unwrap();
755 let users = tree.root_ids()[0];
756
757 assert_eq!(tree.name(users), "users [2]");
760 assert!(tree.children_of(users).is_empty());
761 assert!(!tree.is_leaf(users));
762 assert!(!tree.fully_indexed());
763
764 assert!(tree.ensure_children(users));
765 assert_eq!(tree.children_of(users).len(), 2);
766 assert!(!tree.ensure_children(users), "loading is idempotent");
767
768 tree.index_all();
769 assert!(tree.fully_indexed());
770 assert!(tree.errors().is_empty());
771 }
772
773 #[test]
774 fn single_rooted_documents_materialize_level_one() {
775 let tree = from_reader("[1, [2], 3]".as_bytes()).unwrap();
776 let root = tree.root_ids()[0];
777 assert_eq!(tree.name(root), "$ [3]");
778 assert_eq!(
779 names(&tree, tree.children_of(root)),
780 ["[0]: 1", "[1] [1]", "[2]: 3"]
781 );
782 }
783
784 #[test]
785 fn array_elements_keep_their_order_and_include_object_previews() {
786 let tree = parse(r#"["rust", 7, {"id": 12, "name": "Ada"}, [null]]"#);
787 let root = tree.root_ids()[0];
788 let object = tree.children_of(root)[2];
789
790 assert_eq!(tree.name(root), "$ [4]");
791 assert_eq!(
792 names(&tree, tree.children_of(root)),
793 ["[0]: \"rust\"", "[1]: 7", "[2] {2}", "[3] [1]"]
794 );
795 assert_eq!(
796 tree.detail(object).as_deref(),
797 Some(r#"id: 12 · name: "Ada""#)
798 );
799 }
800
801 #[test]
802 fn object_previews_include_more_than_two_scalar_members() {
803 let tree = parse(r#"{"item":{"a":1,"b":2,"c":3}}"#);
804 let item = tree.root_ids()[0];
805
806 assert_eq!(tree.detail(item).as_deref(), Some("a: 1 · b: 2 · c: 3"));
807 }
808
809 #[test]
810 fn object_previews_are_bounded_and_remain_valid_utf8() {
811 let json = format!(r#"{{"item":{{"huge":"{}"}}}}"#, "😀".repeat(1_000));
812 let tree = parse(&json);
813 let item = tree.root_ids()[0];
814 let preview = tree.detail(item).unwrap();
815
816 assert!(preview.len() <= 512, "preview used {} bytes", preview.len());
817 assert!(preview.starts_with(r#"huge: ""#));
818 }
819
820 #[test]
821 fn object_previews_inspect_at_most_the_first_32_members() {
822 let mut object = serde_json::Map::new();
823 for index in 0..32 {
824 object.insert(format!("nested-{index}"), Value::Array(Vec::new()));
825 }
826 object.insert("too-late".to_owned(), Value::Bool(true));
827 let document = Value::Object(
828 [("item".to_owned(), Value::Object(object))]
829 .into_iter()
830 .collect(),
831 );
832 let tree = parse(&document.to_string());
833 let item = tree.root_ids()[0];
834
835 assert_eq!(tree.detail(item), None);
836 }
837
838 #[test]
839 fn every_node_outputs_its_canonical_json_pointer() {
840 let tree = parse(r#"["rust", {"a/b": {"~key": "value"}}]"#);
841 let root = tree.root_ids()[0];
842 let text = tree.children_of(root)[0];
843 let object = tree.children_of(root)[1];
844 let slash_key = tree.children_of(object)[0];
845 let tilde_key = tree.children_of(slash_key)[0];
846
847 assert_eq!(tree.output(root), OsStr::new(""));
848 assert_eq!(
849 tree.alternate_output(root),
850 OsStr::new(r#"["rust",{"a/b":{"~key":"value"}}]"#)
851 );
852 assert_eq!(tree.path(root), OsStr::new(""));
853 assert_eq!(tree.output(text), OsStr::new("/0"));
854 assert_eq!(tree.alternate_output(text), OsStr::new(r#""rust""#));
855 assert_eq!(tree.path(text), OsStr::new("/0"));
856 assert_eq!(tree.output(slash_key), OsStr::new("/1/a~1b"));
857 assert_eq!(tree.path(slash_key), OsStr::new("/1/a~1b"));
858 assert_eq!(tree.path(tilde_key), OsStr::new("/1/a~1b/~0key"));
859 assert_eq!(tree.relpath(tilde_key), tree.path(tilde_key));
860 assert_eq!(tree.output(tilde_key), OsStr::new("/1/a~1b/~0key"));
861 assert_eq!(tree.alternate_output(tilde_key), OsStr::new(r#""value""#));
862 }
863
864 #[test]
865 fn scalar_and_empty_object_roots_remain_selectable() {
866 let scalar = parse("null");
867 assert_eq!(names(&scalar, scalar.root_ids()), ["$: null"]);
868 assert_eq!(scalar.output(scalar.root_ids()[0]), OsStr::new(""));
869
870 let empty = parse("{}");
871 assert_eq!(names(&empty, empty.root_ids()), ["$ {}"]);
872 assert_eq!(empty.output(empty.root_ids()[0]), OsStr::new(""));
873 assert!(empty.is_container(empty.root_ids()[0]));
874 assert!(!empty.is_leaf(empty.root_ids()[0]));
875 }
876
877 #[test]
878 fn invalid_json_is_reported() {
879 let error = from_reader("{]".as_bytes()).unwrap_err();
880 assert!(error.to_string().starts_with("invalid JSON input:"));
881 }
882
883 #[test]
884 fn trailing_content_after_the_document_is_an_error() {
885 assert!(from_reader(r#"{"a": 1} extra"#.as_bytes()).is_err());
886 assert!(from_reader("[1, 2] [3]".as_bytes()).is_err());
887 assert!(from_reader("null null".as_bytes()).is_err());
888 }
889
890 #[test]
891 fn truncated_documents_are_an_error() {
892 assert!(from_reader(r#"{"a": "#.as_bytes()).is_err());
893 assert!(from_reader(r#"["unterminated"#.as_bytes()).is_err());
894 assert!(from_reader("".as_bytes()).is_err());
895 }
896
897 #[test]
898 fn escaped_keys_and_strings_render_unescaped_labels() {
899 let tree = parse("{\"k\\u0065y\": \"va\\u0041lue\"}");
900 assert_eq!(names(&tree, tree.root_ids()), [r#"key: "vaAlue""#]);
901 }
902
903 #[test]
904 fn numbers_render_through_json_value_normalization() {
905 let tree = parse(r#"[1e3, 0.5, -0]"#);
906 let root = tree.root_ids()[0];
907 assert_eq!(
908 names(&tree, tree.children_of(root)),
909 ["[0]: 1000.0", "[1]: 0.5", "[2]: -0.0"]
910 );
911 }
912
913 #[test]
914 fn jsonl_content_is_detected_and_presents_as_a_virtual_array() {
915 let tree = parse("{\"a\": 1}\n{\"a\": 2}\n");
916 let root = tree.root_ids()[0];
917
918 assert_eq!(names(&tree, tree.root_ids()), ["$ [2]"]);
919 assert!(tree.is_container(root));
920 assert_eq!(names(&tree, tree.children_of(root)), ["[0] {1}", "[1] {1}"]);
921 }
922
923 #[test]
924 fn jsonl_records_start_unvalidated_and_show_pending_counts() {
925 let tree = from_reader("{\"a\": 1}\n{\"a\": 2}\n".as_bytes()).unwrap();
926 let root = tree.root_ids()[0];
927
928 assert_eq!(tree.name(root), "$ [2]");
929 assert!(!tree.fully_indexed());
930 assert_eq!(names(&tree, tree.children_of(root)), ["[0] {…}", "[1] {…}"]);
931 }
932
933 #[test]
934 fn jsonl_detection_accepts_scalar_records() {
935 let tree = parse("1\n2\n");
937 let root = tree.root_ids()[0];
938 assert_eq!(names(&tree, tree.root_ids()), ["$ [2]"]);
939 assert_eq!(names(&tree, tree.children_of(root)), ["[0]: 1", "[1]: 2"]);
940 }
941
942 #[test]
943 fn a_single_record_with_trailing_newline_stays_json() {
944 let tree = parse("{\"a\": 1}\n");
947 assert_eq!(names(&tree, tree.root_ids()), ["a: 1"]);
948 }
949
950 #[test]
951 fn pretty_printed_json_is_not_mistaken_for_jsonl() {
952 let tree = parse("{\n \"a\": 1,\n \"b\": 2\n}\n");
953 assert_eq!(names(&tree, tree.root_ids()), ["a: 1", "b: 2"]);
954 }
955
956 #[test]
957 fn jsonl_skips_blank_lines_and_indices_are_record_ordinals() {
958 let tree = parse("1\n\n \n2\n");
959 let root = tree.root_ids()[0];
960 assert_eq!(names(&tree, tree.root_ids()), ["$ [2]"]);
961 assert_eq!(names(&tree, tree.children_of(root)), ["[0]: 1", "[1]: 2"]);
962 }
963
964 #[test]
965 fn jsonl_drops_a_truncated_final_record() {
966 let tree = parse("{\"a\": 1}\n{\"b\": tru");
967 let root = tree.root_ids()[0];
968 assert_eq!(names(&tree, tree.root_ids()), ["$ [1]"]);
969 assert_eq!(names(&tree, tree.children_of(root)), ["[0] {1}"]);
970 }
971
972 #[test]
973 fn jsonl_surfaces_a_malformed_middle_record_as_an_error_node() {
974 let mut tree = from_reader("{\"a\": 1}\nxxx\n{\"b\": 2}\n".as_bytes()).unwrap();
978 tree.index_all();
979 let root = tree.root_ids()[0];
980 let records = tree.children_of(root).to_vec();
981
982 assert_eq!(records.len(), 3);
983 assert_eq!(tree.name(records[0]), "[0] {1}");
984 assert_eq!(tree.name(records[1]), "[1] ⚠");
985 assert_eq!(tree.detail(records[1]).as_deref(), Some("xxx"));
986 assert_eq!(tree.name(records[2]), "[2] {1}");
987 assert!(tree.is_leaf(records[1]));
988
989 assert_eq!(tree.errors().len(), 1);
990 assert!(
991 tree.errors()[0].contains("line 2"),
992 "error should carry the line number: {:?}",
993 tree.errors()[0]
994 );
995
996 assert_eq!(tree.path(records[1]), OsStr::new("/1"));
998 assert_eq!(tree.relpath(records[1]), OsStr::new(""));
999 assert_eq!(tree.jump_key(records[1]), "/1");
1000 assert_eq!(tree.alternate_output(records[1]), OsStr::new("xxx"));
1001 }
1002
1003 #[test]
1004 fn jsonl_nodes_carry_global_and_record_relative_addresses() {
1005 let tree = parse("{\"user\": {\"name\": \"Ada\"}}\n{\"user\": {\"name\": \"Bo\"}}\n");
1006 let root = tree.root_ids()[0];
1007 let record = tree.children_of(root)[1];
1008 let user = tree.children_of(record)[0];
1009 let name = tree.children_of(user)[0];
1010
1011 assert_eq!(tree.path(root), OsStr::new(""));
1012 assert_eq!(tree.path(record), OsStr::new("/1"));
1013 assert_eq!(tree.relpath(record), OsStr::new(""));
1014 assert_eq!(tree.output(record), OsStr::new("/1"));
1015 assert_eq!(
1016 tree.alternate_output(record),
1017 OsStr::new(r#"{"user":{"name":"Bo"}}"#)
1018 );
1019 assert_eq!(tree.path(name), OsStr::new("/1/user/name"));
1020 assert_eq!(tree.relpath(name), OsStr::new("/user/name"));
1021 assert_eq!(tree.jump_key(name), "/1/user/name");
1022 assert_eq!(tree.jump_key(record), "/1");
1023 }
1024
1025 #[test]
1026 fn forced_jsonl_reads_a_single_record_as_one_element() {
1027 let mut tree = jsonl_from_reader("{\"a\": 1}\n".as_bytes()).unwrap();
1028 tree.index_all();
1029 let root = tree.root_ids()[0];
1030 assert_eq!(names(&tree, tree.root_ids()), ["$ [1]"]);
1031 assert_eq!(names(&tree, tree.children_of(root)), ["[0] {1}"]);
1032 }
1033
1034 #[test]
1035 fn jsonl_startup_builds_only_the_record_level() {
1036 let mut input = String::new();
1037 for i in 0..10_000 {
1038 input.push_str(&format!("{{\"id\": {i}, \"tags\": [1, 2, 3]}}\n"));
1039 }
1040 let mut tree = from_reader(input.as_bytes()).unwrap();
1041
1042 assert_eq!(tree.len(), 1 + 10_000);
1044 assert!(!tree.fully_indexed());
1045
1046 tree.index_all();
1047 assert!(tree.fully_indexed());
1048 assert!(tree.errors().is_empty());
1049 assert_eq!(tree.len(), 1 + 10_000 + 10_000 * 2 + 10_000 * 3);
1051 }
1052
1053 #[test]
1054 fn demo_sample_exercises_the_json_tree_shapes() {
1055 let tree = parse(DEMO_JSON);
1056
1057 assert_eq!(
1058 names(&tree, tree.root_ids()),
1059 ["project {4}", "users [3]", "settings {}", "version: 1"]
1060 );
1061 assert_eq!(
1062 tree.detail(tree.root_ids()[0]).as_deref(),
1063 Some(r#"name: "ite" · status: "experimental""#)
1064 );
1065 let users = tree.root_ids()[1];
1066 assert_eq!(tree.children_of(users).len(), 3);
1067 assert_eq!(tree.name(tree.children_of(users)[2]), "[2]: null");
1068 }
1069}