Skip to main content

datui_lib/inspector/
inspector_drill.rs

1//! Drilling into a nested value from the row inspector: a struct's fields, a
2//! list's items, or the objects and arrays of JSON stored as text.
3//!
4//! A level holds where it is, never a copy of what is there: a column's value is
5//! a one-row slice of the buffer's series, and a JSON value is a path into a
6//! document parsed once. Only the items on screen are ever turned into rows, so a
7//! list of a million items costs as much to show as a list of ten.
8
9use polars::prelude::*;
10use serde_json::Value;
11use std::sync::Arc;
12
13/// Text up to this long is parsed as JSON on the key that asks; longer text is
14/// parsed off the event thread.
15pub const JSON_INLINE_BYTES: usize = 64 * 1024;
16/// Text longer than this is not parsed: its tree would take several times its size.
17pub const JSON_MAX_BYTES: usize = 4 * 1024 * 1024;
18/// Keys whose widths size a column of names: an object's keys past this are fitted
19/// to the width found, not measured.
20const MEASURED: usize = 1000;
21
22/// One place in a value: what a level of the drill shows, or one of its items.
23#[derive(Debug, Clone)]
24pub enum Node {
25    /// One value of a column, as a one-row slice: drilling copies nothing.
26    Native(Series),
27    /// A value in a JSON document parsed from text: the steps from its root.
28    Json { root: Arc<Value>, path: Vec<Step> },
29}
30
31/// One step into a JSON document. An object's step is its key, not its position:
32/// serde_json's map has no lookup by position, and walking to the 500,000th key of
33/// a large object for every item drawn, on every frame, is what a key avoids.
34#[derive(Debug, Clone)]
35pub enum Step {
36    Key(Arc<str>),
37    Index(usize),
38}
39
40/// What a node holds, as the drill sees it.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum Shape {
43    Struct,
44    List,
45    Object,
46    Array,
47    /// A scalar, text, bytes or a null: nothing to drill into.
48    Leaf,
49}
50
51impl Shape {
52    /// What the rule over a level's items calls them.
53    pub fn items_title(self) -> &'static str {
54        match self {
55            Shape::Struct => "Fields",
56            Shape::Object => "Keys",
57            _ => "Items",
58        }
59    }
60}
61
62impl Node {
63    /// The JSON value at this node's path.
64    pub fn json(&self) -> Option<&Value> {
65        let Node::Json { root, path } = self else {
66            return None;
67        };
68        let mut at: &Value = root;
69        for step in path {
70            at = match (at, step) {
71                (Value::Object(map), Step::Key(key)) => map.get(&**key)?,
72                (Value::Array(items), Step::Index(i)) => items.get(*i)?,
73                _ => return None,
74            };
75        }
76        Some(at)
77    }
78
79    fn is_null(&self) -> bool {
80        match self {
81            Node::Native(s) => s.null_count() == s.len(),
82            Node::Json { .. } => matches!(self.json(), None | Some(Value::Null)),
83        }
84    }
85
86    pub fn shape(&self) -> Shape {
87        if self.is_null() {
88            return Shape::Leaf;
89        }
90        match self {
91            Node::Native(s) => match s.dtype() {
92                DataType::Struct(_) => Shape::Struct,
93                DataType::List(_) | DataType::Array(..) => Shape::List,
94                _ => Shape::Leaf,
95            },
96            Node::Json { .. } => match self.json() {
97                Some(Value::Object(_)) => Shape::Object,
98                Some(Value::Array(_)) => Shape::Array,
99                _ => Shape::Leaf,
100            },
101        }
102    }
103
104    /// A list's items, as one series.
105    fn items(&self) -> Option<Series> {
106        let Node::Native(s) = self else {
107            return None;
108        };
109        match s.get(0).ok()? {
110            AnyValue::List(inner) | AnyValue::Array(inner, _) => Some(inner),
111            _ => None,
112        }
113    }
114
115    /// A struct's fields, each a one-row series named for its field.
116    fn fields(&self) -> Option<Vec<Series>> {
117        let Node::Native(s) = self else {
118            return None;
119        };
120        Some(s.struct_().ok()?.fields_as_series())
121    }
122
123    /// How many fields, items or keys a level of this node lists.
124    pub fn len(&self) -> usize {
125        match self.shape() {
126            Shape::Struct => self.fields().map_or(0, |f| f.len()),
127            Shape::List => self.items().map_or(0, |s| s.len()),
128            Shape::Object | Shape::Array => match self.json() {
129                Some(Value::Object(map)) => map.len(),
130                Some(Value::Array(items)) => items.len(),
131                _ => 0,
132            },
133            Shape::Leaf => 0,
134        }
135    }
136
137    pub fn is_empty(&self) -> bool {
138        self.len() == 0
139    }
140
141    fn json_child(&self, step: Step) -> Node {
142        let Node::Json { root, path } = self else {
143            unreachable!("only a JSON node has JSON children")
144        };
145        let mut path = path.clone();
146        path.push(step);
147        Node::Json {
148            root: Arc::clone(root),
149            path,
150        }
151    }
152
153    /// `count` items from `start`, each with its label: a field's or key's name, or
154    /// `[i]`. Only these are made, whatever the length.
155    pub fn children(&self, start: usize, count: usize) -> Vec<(String, Node)> {
156        match self.shape() {
157            Shape::Struct => self
158                .fields()
159                .unwrap_or_default()
160                .into_iter()
161                .skip(start)
162                .take(count)
163                .map(|s| (s.name().to_string(), Node::Native(s)))
164                .collect(),
165            Shape::List => {
166                let Some(items) = self.items() else {
167                    return Vec::new();
168                };
169                let end = items.len().min(start.saturating_add(count));
170                (start..end)
171                    .map(|i| (format!("[{i}]"), Node::Native(items.slice(i as i64, 1))))
172                    .collect()
173            }
174            Shape::Object => match self.json() {
175                Some(Value::Object(map)) => page(map.keys(), start, count)
176                    .into_iter()
177                    .map(|key| (key.clone(), self.json_child(Step::Key(key.as_str().into()))))
178                    .collect(),
179                _ => Vec::new(),
180            },
181            Shape::Array => {
182                let end = self.len().min(start.saturating_add(count));
183                (start..end)
184                    .map(|i| (format!("[{i}]"), self.json_child(Step::Index(i))))
185                    .collect()
186            }
187            Shape::Leaf => Vec::new(),
188        }
189    }
190
191    pub fn child(&self, i: usize) -> Option<(String, Node)> {
192        self.children(i, 1).pop()
193    }
194
195    /// The widest label a level of this node lists, without walking a long list:
196    /// a list's last index is its widest, and only the first keys are measured.
197    pub fn label_width(&self) -> usize {
198        match self.shape() {
199            Shape::List | Shape::Array => format!("[{}]", self.len().saturating_sub(1)).len(),
200            Shape::Struct => self
201                .fields()
202                .unwrap_or_default()
203                .iter()
204                .map(|s| crate::glyphs::cell_width(s.name()))
205                .max()
206                .unwrap_or(0),
207            Shape::Object => match self.json() {
208                Some(Value::Object(map)) => map
209                    .keys()
210                    .take(MEASURED)
211                    .map(|k| crate::glyphs::cell_width(k))
212                    .max()
213                    .unwrap_or(0),
214                _ => 0,
215            },
216            Shape::Leaf => 0,
217        }
218    }
219
220    /// The text of a text value, for `f`; None for any other value.
221    pub fn with_text<R>(&self, f: impl FnOnce(&str) -> R) -> Option<R> {
222        match self {
223            Node::Native(s) => match s.get(0).ok()? {
224                AnyValue::String(text) => Some(f(text)),
225                AnyValue::StringOwned(text) => Some(f(text.as_str())),
226                _ => None,
227            },
228            Node::Json { .. } => match self.json()? {
229                Value::String(text) => Some(f(text)),
230                _ => None,
231            },
232        }
233    }
234
235    /// Whether Enter opens this node as a level: a struct, a list, an object or an
236    /// array, or text that reads as a JSON object or array.
237    pub fn opens(&self) -> bool {
238        self.shape() != Shape::Leaf || self.with_text(opens_as_json).unwrap_or(false)
239    }
240
241    /// The type as the item list names it.
242    pub fn type_label(&self) -> String {
243        match self {
244            Node::Native(s) => crate::formats::column_types::dtype_label(s.dtype()),
245            Node::Json { .. } => json_kind(self.json().unwrap_or(&Value::Null)).to_string(),
246        }
247    }
248
249    /// The type the item list colors this node's name by.
250    pub fn color_dtype(&self) -> DataType {
251        match self {
252            Node::Native(s) => s.dtype().clone(),
253            Node::Json { .. } => json_dtype(self.json().unwrap_or(&Value::Null)),
254        }
255    }
256
257    /// The columns a list of structs, or an array of objects, shows its items in,
258    /// with the type that colors each name: the struct's fields, or the first
259    /// object's keys. None for any other level.
260    pub fn table_columns(&self) -> Option<Vec<(String, DataType)>> {
261        match self.shape() {
262            Shape::List => match self.items()?.dtype() {
263                DataType::Struct(fields) if !fields.is_empty() => Some(
264                    fields
265                        .iter()
266                        .map(|f| (f.name().to_string(), f.dtype().clone()))
267                        .collect(),
268                ),
269                _ => None,
270            },
271            Shape::Array => match self.json()? {
272                Value::Array(items) => match items.first()? {
273                    Value::Object(first) if !first.is_empty() => Some(
274                        first
275                            .iter()
276                            .map(|(k, v)| (k.clone(), json_dtype(v)))
277                            .collect(),
278                    ),
279                    _ => None,
280                },
281                _ => None,
282            },
283            _ => None,
284        }
285    }
286
287    /// One item's value under `column` in a level shown as a table; None when the
288    /// item has no such field or key.
289    pub fn cell(&self, column: &str) -> Option<Node> {
290        match self.shape() {
291            // One field made, not all of them for each cell of a row.
292            Shape::Struct => match self {
293                Node::Native(s) => s
294                    .struct_()
295                    .ok()?
296                    .field_by_name(column)
297                    .ok()
298                    .map(Node::Native),
299                Node::Json { .. } => None,
300            },
301            Shape::Object => match self.json()? {
302                Value::Object(map) if map.contains_key(column) => {
303                    Some(self.json_child(Step::Key(column.into())))
304                }
305                _ => None,
306            },
307            _ => None,
308        }
309    }
310}
311
312/// Items `start..start + count` of `items`, walked to from the nearer end: an
313/// object's keys can only be stepped through, and `End` on a large one is then as
314/// quick as `Home`.
315fn page<T>(
316    items: impl DoubleEndedIterator<Item = T> + ExactSizeIterator,
317    start: usize,
318    count: usize,
319) -> Vec<T> {
320    let len = items.len();
321    let end = len.min(start.saturating_add(count));
322    if start >= end {
323        return Vec::new();
324    }
325    if start <= len - end {
326        return items.skip(start).take(end - start).collect();
327    }
328    let mut back: Vec<T> = items.rev().skip(len - end).take(end - start).collect();
329    back.reverse();
330    back
331}
332
333/// The type a JSON value's name is colored by, as a column of that type is.
334fn json_dtype(value: &Value) -> DataType {
335    match value {
336        Value::String(_) => DataType::String,
337        Value::Bool(_) => DataType::Boolean,
338        Value::Number(n) if n.is_f64() => DataType::Float64,
339        Value::Number(_) => DataType::Int64,
340        Value::Array(_) => DataType::List(Box::new(DataType::Null)),
341        Value::Object(_) => DataType::Struct(Vec::new()),
342        Value::Null => DataType::Null,
343    }
344}
345
346/// A JSON value's kind, as the item list names it.
347pub fn json_kind(value: &Value) -> &'static str {
348    match value {
349        Value::Null => "null",
350        Value::Bool(_) => "bool",
351        Value::Number(_) => "number",
352        Value::String(_) => "str",
353        Value::Array(_) => "array",
354        Value::Object(_) => "object",
355    }
356}
357
358/// Whether `text` reads as a JSON object or array: it starts and ends with the
359/// brackets of one. Whether it parses is learned only by parsing it.
360pub fn looks_like_json(text: &str) -> bool {
361    let t = text.trim();
362    matches!(
363        (t.as_bytes().first(), t.as_bytes().last()),
364        (Some(b'{'), Some(b'}')) | (Some(b'['), Some(b']'))
365    )
366}
367
368/// Whether Enter offers to open `text` as JSON: it reads as an object or array and
369/// is not over [`JSON_MAX_BYTES`], past which Enter shows more of it instead.
370pub fn opens_as_json(text: &str) -> bool {
371    text.len() <= JSON_MAX_BYTES && looks_like_json(text)
372}
373
374/// Parse `text` as JSON, refusing text over [`JSON_MAX_BYTES`]. serde_json stops at
375/// 128 levels of nesting, so a deep document is an error, not a stack overflow.
376pub fn parse_json(text: &str) -> Result<Value, String> {
377    if text.len() > JSON_MAX_BYTES {
378        return Err(format!(
379            "{} MiB of text is over the {} MiB that opens as JSON",
380            text.len().div_ceil(1024 * 1024),
381            JSON_MAX_BYTES / (1024 * 1024)
382        ));
383    }
384    serde_json::from_str(text).map_err(|e| format!("not JSON: {e}"))
385}
386
387/// Writes up to `cap` bytes, then fails, so serializing a huge value stops there.
388struct Capped {
389    out: Vec<u8>,
390    cap: usize,
391    cut: bool,
392}
393
394impl std::io::Write for Capped {
395    fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
396        let room = self.cap.saturating_sub(self.out.len());
397        if bytes.len() > room {
398            self.out.extend_from_slice(&bytes[..room]);
399            self.cut = true;
400            return Err(std::io::Error::other("cap reached"));
401        }
402        self.out.extend_from_slice(bytes);
403        Ok(bytes.len())
404    }
405
406    fn flush(&mut self) -> std::io::Result<()> {
407        Ok(())
408    }
409}
410
411/// JSON on one line with a space after each comma and colon, as datui writes a
412/// list or struct on one line.
413struct Spaced;
414
415impl serde_json::ser::Formatter for Spaced {
416    fn begin_array_value<W: ?Sized + std::io::Write>(
417        &mut self,
418        writer: &mut W,
419        first: bool,
420    ) -> std::io::Result<()> {
421        if first {
422            Ok(())
423        } else {
424            writer.write_all(b", ")
425        }
426    }
427
428    fn begin_object_key<W: ?Sized + std::io::Write>(
429        &mut self,
430        writer: &mut W,
431        first: bool,
432    ) -> std::io::Result<()> {
433        if first {
434            Ok(())
435        } else {
436            writer.write_all(b", ")
437        }
438    }
439
440    fn begin_object_value<W: ?Sized + std::io::Write>(
441        &mut self,
442        writer: &mut W,
443    ) -> std::io::Result<()> {
444        writer.write_all(b": ")
445    }
446}
447
448/// `value` as JSON text, indented or on one line, stopping at `cap` bytes. True
449/// when it was cut there.
450pub fn json_text(value: &Value, pretty: bool, cap: usize) -> (String, bool) {
451    use serde::Serialize;
452    let mut w = Capped {
453        out: Vec::new(),
454        cap,
455        cut: false,
456    };
457    // An error here is only the cap: serializing a parsed value cannot fail otherwise.
458    let _ = if pretty {
459        serde_json::to_writer_pretty(&mut w, value)
460    } else {
461        value.serialize(&mut serde_json::Serializer::with_formatter(&mut w, Spaced))
462    };
463    let text = match String::from_utf8(w.out) {
464        Ok(text) => text,
465        // Cut inside a character: keep what is whole.
466        Err(e) => {
467            let valid = e.utf8_error().valid_up_to();
468            let mut bytes = e.into_bytes();
469            bytes.truncate(valid);
470            String::from_utf8(bytes).unwrap_or_default()
471        }
472    };
473    (text, w.cut)
474}
475
476/// A JSON value as copy text: text as itself, a null as empty, anything else as
477/// JSON, indented when it is an object or array. None when it is over `cap` bytes.
478pub fn json_copy_text(value: &Value, cap: usize) -> Option<String> {
479    match value {
480        Value::String(s) => (s.len() <= cap).then(|| s.clone()),
481        Value::Null => Some(String::new()),
482        v => {
483            let (text, cut) = json_text(v, true, cap);
484            (!cut).then_some(text)
485        }
486    }
487}
488
489/// One level of a drill: the value it lists, and the item focused in it.
490#[derive(Debug, Clone)]
491pub struct Level {
492    /// Its step in the breadcrumb: a field's or key's name, or `[i]`.
493    pub label: String,
494    pub node: Node,
495    pub selected: usize,
496}
497
498impl Level {
499    /// The focused item, and its label.
500    pub fn focused(&self) -> Option<(String, Node)> {
501        self.node.child(self.selected)
502    }
503}
504
505/// The levels opened under one field of one row, outermost first.
506#[derive(Debug, Clone)]
507pub struct Drill {
508    pub frame: u64,
509    pub row: usize,
510    pub levels: Vec<Level>,
511}
512
513impl Drill {
514    pub fn level(&self) -> &Level {
515        self.levels.last().expect("a drill has a level")
516    }
517
518    pub fn level_mut(&mut self) -> &mut Level {
519        self.levels.last_mut().expect("a drill has a level")
520    }
521}
522
523/// Text being parsed as JSON off the event thread, and where its level opens.
524#[derive(Debug, Clone)]
525pub struct JsonWait {
526    pub token: u64,
527    pub frame: u64,
528    pub row: usize,
529    pub label: String,
530    /// The text's place, as [`path_key`] names it.
531    pub path: String,
532}
533
534/// A place in a row, for telling one item from another: the field, then each step
535/// opened and the item. Unit separators cannot be typed into a name, so two places
536/// never meet.
537pub fn path_key<'a>(steps: impl IntoIterator<Item = &'a str>) -> String {
538    steps.into_iter().collect::<Vec<_>>().join("\u{1f}")
539}
540
541impl Drill {
542    /// The place of the item `label` in the level shown.
543    pub fn item_key(&self, label: &str) -> String {
544        path_key(
545            self.levels
546                .iter()
547                .map(|l| l.label.as_str())
548                .chain(std::iter::once(label)),
549        )
550    }
551}
552
553#[cfg(test)]
554mod tests;