Skip to main content

ite_cli/
json_tree.rs

1//! Transforms JSON input into source-neutral tree data.
2
3use std::fmt;
4use std::io::{self, Read, Write};
5
6use serde_json::Value;
7
8use crate::tree::{ActionValues, NodeId, Tree};
9
10/// Enough text to fill an unusually wide terminal without retaining an
11/// unbounded second representation of every object.
12const MAX_OBJECT_PREVIEW_BYTES: usize = 512;
13/// Bounds the search for previewable scalars when early members are containers.
14const MAX_OBJECT_PREVIEW_MEMBERS: usize = 32;
15
16#[derive(Debug)]
17pub struct Error(serde_json::Error);
18
19impl fmt::Display for Error {
20    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(formatter, "invalid JSON input: {}", self.0)
22    }
23}
24
25impl std::error::Error for Error {
26    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
27        Some(&self.0)
28    }
29}
30
31pub fn from_reader(reader: impl Read) -> Result<Tree, Error> {
32    let value = serde_json::from_reader(reader).map_err(Error)?;
33    Ok(transform(&value))
34}
35
36fn transform(value: &Value) -> Tree {
37    let mut tree = Tree::new();
38    match value {
39        Value::Object(members) if !members.is_empty() => {
40            for (key, value) in members {
41                push_value(&mut tree, None, key, &append_pointer("", key), value);
42            }
43        }
44        _ => {
45            push_value(&mut tree, None, "$", "", value);
46        }
47    }
48    tree
49}
50
51fn push_value(
52    tree: &mut Tree,
53    parent: Option<NodeId>,
54    prefix: &str,
55    pointer: &str,
56    value: &Value,
57) -> NodeId {
58    let (name, detail) = label(prefix, value);
59    let alternate_output =
60        serde_json::to_string(value).expect("serializing a JSON value cannot fail");
61    let action =
62        ActionValues::new(pointer, pointer, pointer).with_alternate_output(alternate_output);
63    let id = tree.push_with_detail(
64        parent,
65        name,
66        detail,
67        matches!(value, Value::Array(_) | Value::Object(_)),
68        action,
69    );
70
71    match value {
72        Value::Array(elements) => {
73            for (index, value) in elements.iter().enumerate() {
74                let index = index.to_string();
75                push_value(
76                    tree,
77                    Some(id),
78                    &format!("[{index}]"),
79                    &append_pointer(pointer, &index),
80                    value,
81                );
82            }
83        }
84        Value::Object(members) => {
85            for (key, value) in members {
86                push_value(tree, Some(id), key, &append_pointer(pointer, key), value);
87            }
88        }
89        _ => {}
90    }
91    id
92}
93
94fn label(prefix: &str, value: &Value) -> (String, Option<String>) {
95    match value {
96        Value::Array(elements) if elements.is_empty() => (format!("{prefix} []"), None),
97        Value::Array(elements) => (format!("{prefix} [{}]", elements.len()), None),
98        Value::Object(members) if members.is_empty() => (format!("{prefix} {{}}"), None),
99        Value::Object(members) => (
100            format!("{prefix} {{{}}}", members.len()),
101            object_preview(members),
102        ),
103        _ => (
104            format!(
105                "{prefix}: {}",
106                scalar(value).expect("non-container JSON values are scalar")
107            ),
108            None,
109        ),
110    }
111}
112
113fn object_preview(members: &serde_json::Map<String, Value>) -> Option<String> {
114    let mut preview = Preview::new(MAX_OBJECT_PREVIEW_BYTES);
115    for (key, value) in members.iter().take(MAX_OBJECT_PREVIEW_MEMBERS) {
116        if !matches!(
117            value,
118            Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)
119        ) {
120            continue;
121        }
122        if !preview.is_empty() && !preview.push_str(" 路 ") {
123            break;
124        }
125        if !preview.push_str(key) || !preview.push_str(": ") {
126            break;
127        }
128        preview.push_json(value);
129        if preview.is_exhausted() {
130            break;
131        }
132    }
133    preview.finish()
134}
135
136struct Preview {
137    text: String,
138    limit: usize,
139    exhausted: bool,
140}
141
142impl Preview {
143    fn new(limit: usize) -> Self {
144        Self {
145            text: String::with_capacity(limit),
146            limit,
147            exhausted: false,
148        }
149    }
150
151    fn is_empty(&self) -> bool {
152        self.text.is_empty()
153    }
154
155    fn is_exhausted(&self) -> bool {
156        self.exhausted
157    }
158
159    fn push_str(&mut self, text: &str) -> bool {
160        if self.exhausted {
161            return false;
162        }
163        let remaining = self.limit.saturating_sub(self.text.len());
164        if text.len() <= remaining {
165            self.text.push_str(text);
166            return true;
167        }
168
169        let mut end = remaining;
170        while !text.is_char_boundary(end) {
171            end -= 1;
172        }
173        self.text.push_str(&text[..end]);
174        self.exhausted = true;
175        false
176    }
177
178    fn push_json(&mut self, value: &Value) {
179        if serde_json::to_writer(&mut *self, value).is_err() {
180            self.exhausted = true;
181        }
182    }
183
184    fn finish(self) -> Option<String> {
185        (!self.text.is_empty()).then_some(self.text)
186    }
187}
188
189impl Write for Preview {
190    fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
191        if bytes.is_empty() {
192            return Ok(0);
193        }
194        if self.exhausted {
195            return Err(io::ErrorKind::WriteZero.into());
196        }
197
198        let remaining = self.limit.saturating_sub(self.text.len());
199        let candidate = &bytes[..bytes.len().min(remaining)];
200        let end = std::str::from_utf8(candidate).map_or_else(|error| error.valid_up_to(), str::len);
201        if end == 0 {
202            self.exhausted = true;
203            return Err(io::ErrorKind::WriteZero.into());
204        }
205
206        self.text.push_str(
207            std::str::from_utf8(&candidate[..end])
208                .expect("a prefix ending on a UTF-8 boundary is valid"),
209        );
210        self.exhausted = end < bytes.len();
211        Ok(end)
212    }
213
214    fn flush(&mut self) -> io::Result<()> {
215        Ok(())
216    }
217}
218
219fn scalar(value: &Value) -> Option<String> {
220    match value {
221        Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {
222            Some(serde_json::to_string(value).expect("serializing a JSON scalar cannot fail"))
223        }
224        Value::Array(_) | Value::Object(_) => None,
225    }
226}
227
228fn append_pointer(parent: &str, token: &str) -> String {
229    let token = token.replace('~', "~0").replace('/', "~1");
230    format!("{parent}/{token}")
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236    use std::ffi::OsStr;
237
238    const DEMO_JSON: &str = include_str!("../examples/sample.json");
239
240    fn parse(json: &str) -> Tree {
241        from_reader(json.as_bytes()).unwrap()
242    }
243
244    fn names<'a>(tree: &'a Tree, ids: &[usize]) -> Vec<&'a str> {
245        ids.iter().map(|&id| tree.node(id).name.as_str()).collect()
246    }
247
248    #[test]
249    fn object_members_become_ordered_roots_with_container_sizes_and_scalar_values() {
250        let tree = parse(
251            r#"{
252                "users": [{"id": 12, "name": "Ada"}, {"id": 27}, null],
253                "empty": [],
254                "settings": {},
255                "enabled": true
256            }"#,
257        );
258
259        assert_eq!(
260            names(&tree, tree.root_ids()),
261            ["users [3]", "empty []", "settings {}", "enabled: true"]
262        );
263        assert!(tree.node(tree.root_ids()[0]).is_container);
264        assert!(tree.node(tree.root_ids()[1]).is_container);
265        assert!(tree.is_leaf(tree.root_ids()[1]));
266    }
267
268    #[test]
269    fn array_elements_keep_their_order_and_include_object_previews() {
270        let tree = parse(r#"["rust", 7, {"id": 12, "name": "Ada"}, [null]]"#);
271        let root = tree.root_ids()[0];
272        let object = tree.node(root).children[2];
273
274        assert_eq!(tree.node(root).name, "$ [4]");
275        assert_eq!(
276            names(&tree, &tree.node(root).children),
277            ["[0]: \"rust\"", "[1]: 7", "[2] {2}", "[3] [1]"]
278        );
279        assert_eq!(
280            tree.node(object).detail.as_deref(),
281            Some(r#"id: 12 路 name: "Ada""#)
282        );
283    }
284
285    #[test]
286    fn object_previews_include_more_than_two_scalar_members() {
287        let tree = parse(r#"{"item":{"a":1,"b":2,"c":3}}"#);
288        let item = tree.root_ids()[0];
289
290        assert_eq!(
291            tree.node(item).detail.as_deref(),
292            Some("a: 1 路 b: 2 路 c: 3")
293        );
294    }
295
296    #[test]
297    fn object_previews_are_bounded_and_remain_valid_utf8() {
298        let json = format!(r#"{{"item":{{"huge":"{}"}}}}"#, "馃榾".repeat(1_000));
299        let tree = parse(&json);
300        let item = tree.root_ids()[0];
301        let preview = tree.node(item).detail.as_deref().unwrap();
302
303        assert!(preview.len() <= 512, "preview used {} bytes", preview.len());
304        assert!(preview.starts_with(r#"huge: ""#));
305    }
306
307    #[test]
308    fn object_previews_inspect_at_most_the_first_32_members() {
309        let mut object = serde_json::Map::new();
310        for index in 0..32 {
311            object.insert(format!("nested-{index}"), Value::Array(Vec::new()));
312        }
313        object.insert("too-late".to_owned(), Value::Bool(true));
314        let document = Value::Object(
315            [("item".to_owned(), Value::Object(object))]
316                .into_iter()
317                .collect(),
318        );
319        let tree = transform(&document);
320        let item = tree.root_ids()[0];
321
322        assert_eq!(tree.node(item).detail, None);
323    }
324
325    #[test]
326    fn every_node_outputs_its_canonical_json_pointer() {
327        let tree = parse(r#"["rust", {"a/b": {"~key": "value"}}]"#);
328        let root = tree.root_ids()[0];
329        let text = tree.node(root).children[0];
330        let object = tree.node(root).children[1];
331        let slash_key = tree.node(object).children[0];
332        let tilde_key = tree.node(slash_key).children[0];
333
334        assert_eq!(tree.node(root).action.output, OsStr::new(""));
335        assert_eq!(
336            tree.node(root).action.alternate_output,
337            OsStr::new(r#"["rust",{"a/b":{"~key":"value"}}]"#)
338        );
339        assert_eq!(tree.node(root).action.path, OsStr::new(""));
340        assert_eq!(tree.node(text).action.output, OsStr::new("/0"));
341        assert_eq!(
342            tree.node(text).action.alternate_output,
343            OsStr::new(r#""rust""#)
344        );
345        assert_eq!(tree.node(text).action.path, OsStr::new("/0"));
346        assert_eq!(tree.node(slash_key).action.output, OsStr::new("/1/a~1b"));
347        assert_eq!(tree.node(slash_key).action.path, OsStr::new("/1/a~1b"));
348        assert_eq!(
349            tree.node(tilde_key).action.path,
350            OsStr::new("/1/a~1b/~0key")
351        );
352        assert_eq!(
353            tree.node(tilde_key).action.relpath,
354            tree.node(tilde_key).action.path
355        );
356        assert_eq!(
357            tree.node(tilde_key).action.output,
358            OsStr::new("/1/a~1b/~0key")
359        );
360        assert_eq!(
361            tree.node(tilde_key).action.alternate_output,
362            OsStr::new(r#""value""#)
363        );
364    }
365
366    #[test]
367    fn scalar_and_empty_object_roots_remain_selectable() {
368        let scalar = parse("null");
369        assert_eq!(names(&scalar, scalar.root_ids()), ["$: null"]);
370        assert_eq!(
371            scalar.node(scalar.root_ids()[0]).action.output,
372            OsStr::new("")
373        );
374
375        let empty = parse("{}");
376        assert_eq!(names(&empty, empty.root_ids()), ["$ {}"]);
377        assert_eq!(
378            empty.node(empty.root_ids()[0]).action.output,
379            OsStr::new("")
380        );
381        assert!(empty.node(empty.root_ids()[0]).is_container);
382        assert!(empty.is_leaf(empty.root_ids()[0]));
383    }
384
385    #[test]
386    fn invalid_json_is_reported() {
387        let error = from_reader("{]".as_bytes()).unwrap_err();
388        assert!(error.to_string().starts_with("invalid JSON input:"));
389    }
390
391    #[test]
392    fn demo_sample_exercises_the_json_tree_shapes() {
393        let tree = parse(DEMO_JSON);
394
395        assert_eq!(
396            names(&tree, tree.root_ids()),
397            ["project {4}", "users [3]", "settings {}", "version: 1"]
398        );
399        assert_eq!(
400            tree.node(tree.root_ids()[0]).detail.as_deref(),
401            Some(r#"name: "ite" 路 status: "experimental""#)
402        );
403        let users = tree.root_ids()[1];
404        assert_eq!(tree.node(users).children.len(), 3);
405        assert_eq!(tree.node(tree.node(users).children[2]).name, "[2]: null");
406    }
407}