jupiter_rs/infograph/
docs.rs

1//! Contains the core elements to read / query an information graph.
2//!
3//! Each information graph is represented by a `Doc` which provides access to its nodes and
4//! leaves via `Element`. A `Doc` is built using [DocBuilder](crate::infograph::builder::DocBuilder)
5//! and can then be further optimized for read access using [Table](crate::infograph::table:Table).
6use crate::infograph::node::Node;
7use crate::infograph::symbols::{Symbol, SymbolTable};
8use std::borrow::Cow;
9use std::fmt::{Debug, Display, Formatter};
10
11/// Provides the root node of an information graph.
12///
13/// This essentially bundles the [SymbolTable](crate::infograph::symbols::SymbolTable) and the
14/// root node of the graph.
15///
16/// Note that a `Doc` cannot be modified once it has been [built](crate::infograph::builder) which
17/// permits to use a very efficient memory layout and also permits to cache queries, lookup indices
18/// and query results as provided by [Table](crate::infograph::table::Table).
19pub struct Doc {
20    root: Node,
21    symbols: SymbolTable,
22}
23
24/// Provides a node or leaf within the information graph.
25///
26/// Note that this is mainly a pointer into a `Doc` therefore it can be
27/// copied quite cheaply.
28#[derive(Copy, Clone)]
29pub struct Element<'a> {
30    doc: &'a Doc,
31    node: &'a Node,
32}
33
34/// Represents an `Iterator` over all child elements of a list.
35pub struct ElementIter<'a> {
36    doc: &'a Doc,
37    iter: Option<std::slice::Iter<'a, Node>>,
38}
39
40/// Represents an `Iterator` over all key/value pairs of an inner object.
41pub struct EntryIter<'a> {
42    doc: &'a Doc,
43    iter: Option<std::slice::Iter<'a, (Symbol, Node)>>,
44}
45
46/// Represents a pre-compiled query.
47///
48/// A query can be obtained using [Doc::compile](Doc::compile). And then repeatedly being
49/// executed using [Query::execute]{Query::execute}.
50///
51/// Compiling a query essentially splits the given query string at "." and then resolves each
52/// part of the query into a `Symbol`. Therefore a query compiled for one `Doc` cannot be used
53/// for another as its result would be entirely undefined. Also note that compiling queries is
54/// rather fast, therefore ad-hoc queries can be executed using [Element::query](Element::query).
55#[derive(Clone)]
56pub struct Query {
57    path: Vec<Symbol>,
58}
59
60impl Doc {
61    /// Creates an entirely empty doc which can be used as a fallback or placeholder.
62    pub fn empty() -> Self {
63        Doc {
64            symbols: SymbolTable::new(),
65            root: Node::Empty,
66        }
67    }
68
69    /// Creates a new document by combining the given `SymbolTable` and `Node`.
70    ///
71    /// Note that this is mainly an internal API as [DocBuilder](crate::infograph::builder::DocBuilder)
72    /// should be used to generate new docs.
73    pub fn new(symbols: SymbolTable, root: Node) -> Self {
74        Doc { symbols, root }
75    }
76
77    /// Returns the root node of this doc which is either a list or a map.
78    ///
79    /// # Example
80    /// ```
81    /// # use jupiter::infograph::builder::DocBuilder;
82    /// let mut builder = DocBuilder::new();
83    /// let mut list_builder = builder.root_list_builder();
84    /// list_builder.append_int(1);
85    /// let doc = builder.build();
86    ///
87    /// let root = doc.root();
88    ///
89    /// assert_eq!(root.len(), 1)
90    /// ```
91    pub fn root(&self) -> Element {
92        Element {
93            doc: self,
94            node: &self.root,
95        }
96    }
97
98    /// Compiles the given query.
99    ///
100    /// A query is composed of a chain of keys separated by ".". For nested objects,
101    /// we apply one key after another to obtain the resulting target element.
102    ///
103    /// # Example
104    /// ```
105    /// # use jupiter::infograph::builder::DocBuilder;
106    /// let mut builder = DocBuilder::new();
107    /// let mut object_builder = builder.root_object_builder();
108    /// object_builder.put_object("Foo").unwrap().put_int("Bar", 42).unwrap();
109    /// let doc = builder.build();
110    ///
111    /// let query = doc.compile("Foo.Bar");
112    /// assert_eq!(query.execute(doc.root()).as_int().unwrap(), 42)
113    /// ```
114    ///
115    /// # Performance
116    /// Compiling queries is rather fast, therefore ad-hoc queries can be executed using
117    /// [Doc::query](crate::infograph::docs::Doc::query). Therefore pre-compiled queries
118    /// are only feasible if a query is known to be executed several times (e.g. when iterating
119    /// over a list of objects and executing the same query each time).
120    ///
121    /// Also note that if we encounter an unknown symbol here, we know that the query cannot be
122    /// fullfilled and therefore return an empty query here which always yields and empty
123    /// result without any actual work being done.
124    pub fn compile(&self, query: impl AsRef<str>) -> Query {
125        let mut path = Vec::new();
126
127        for part in query.as_ref().split('.') {
128            if let Some(symbol) = self.symbols.resolve(part) {
129                path.push(symbol);
130            } else {
131                return Query { path: Vec::new() };
132            }
133        }
134
135        Query { path }
136    }
137
138    /// Estimates the size (in bytes) occupied by this doc.
139    ///
140    /// This accounts for both, the `SymbolTable` and the actual information graph. Note that this
141    /// is an approximation as not all data structures reveal their true size.
142    ///
143    /// # Example
144    /// ```
145    /// # use jupiter::infograph::builder::DocBuilder;
146    /// let mut builder = DocBuilder::new();
147    /// let mut object_builder = builder.root_object_builder();
148    /// object_builder.put_object("Foo").unwrap().put_int("Bar", 42).unwrap();
149    /// let doc = builder.build();
150    ///
151    /// println!("{}", doc.allocated_size());
152    /// ```
153    pub fn allocated_size(&self) -> usize {
154        self.root.allocated_size() + self.symbols.allocated_size()
155    }
156}
157
158/// Represents a node or leaf of the information graph.
159///
160/// This could be either an inner object, a list, an int, a bool or a string. Note that
161/// the element itself is just a pointer into the graph and can therefore be copied. However,
162/// its lifetime depends of the `Doc` it references.
163impl<'a> Element<'a> {
164    /// Executes an ad-hoc query on this element.
165    ///
166    /// See [Doc::compile](Doc::compile) for a description of the query syntax.
167    ///
168    /// Note that a query can be executed on any element but will only ever yield a non
169    /// empty result if it is executed on an object.
170    ///
171    /// # Example
172    ///
173    /// ```
174    /// # use jupiter::infograph::builder::DocBuilder;
175    /// let mut builder = DocBuilder::new();
176    /// let mut object_builder = builder.root_object_builder();
177    /// object_builder.put_object("Foo").unwrap().put_int("Bar", 42).unwrap();
178    /// let doc = builder.build();
179    ///
180    /// let result = doc.root().query("Foo.Bar");
181    ///
182    /// assert_eq!(result.as_int().unwrap(), 42)
183    /// ```
184    pub fn query(self, query: impl AsRef<str>) -> Element<'a> {
185        self.doc.compile(query).execute(self)
186    }
187
188    /// Determines if this element is empty.
189    ///
190    /// Such elements are e.g. returned if a query doesn't match or if an out of range
191    /// index is used when accessing a list.
192    ///
193    /// # Example
194    ///
195    /// ```
196    /// # use jupiter::infograph::docs::Doc;
197    /// let doc = Doc::empty();
198    ///
199    /// assert_eq!(doc.root().is_empty(), true);
200    /// assert_eq!(doc.root().query("unknown").is_empty(), true);
201    /// assert_eq!(doc.root().at(100).is_empty(), true);
202    /// ```
203    pub fn is_empty(&self) -> bool {
204        if let Node::Empty = self.node {
205            true
206        } else {
207            false
208        }
209    }
210
211    /// Returns the string represented by this element.
212    ///
213    /// Note that this will only return a string if the underlying data is one.
214    /// No other element will be converted into a string, as this is handled by
215    /// `to_string'.
216    ///
217    /// # Example
218    ///
219    /// ```
220    /// # use jupiter::infograph::builder::DocBuilder;
221    /// let mut builder = DocBuilder::new();
222    /// let mut list_builder = builder.root_list_builder();
223    /// list_builder.append_string("Foo");
224    /// let doc = builder.build();
225    ///
226    /// assert_eq!(doc.root().at(0).as_str().unwrap(), "Foo");
227    /// ```
228    pub fn as_str(&self) -> Option<&str> {
229        match self.node {
230            Node::InlineString(str) => Some(str.as_ref()),
231            Node::BoxedString(str) => Some(str.as_ref()),
232            _ => None,
233        }
234    }
235
236    /// Returns a string representation of all scalar values.
237    ///
238    /// Returns a string for string, int or bool values. Everything else is represented
239    /// as "". Note that `Element` implements `Debug` which will also render a representation
240    /// for lists and objects, but its representation is only for debugging purposes and should
241    /// not being relied up on.
242    ///
243    /// # Example
244    ///
245    /// ```
246    /// # use jupiter::infograph::builder::DocBuilder;
247    /// let mut builder = DocBuilder::new();
248    /// let mut list_builder = builder.root_list_builder();
249    /// list_builder.append_string("Foo");
250    /// list_builder.append_bool(true);
251    /// list_builder.append_int(42);
252    /// let doc = builder.build();
253    ///
254    /// assert_eq!(doc.root().at(0).to_string().as_ref(), "Foo");
255    /// assert_eq!(doc.root().at(1).to_string().as_ref(), "true");
256    /// assert_eq!(doc.root().at(2).to_string().as_ref(), "42");
257    /// ```
258    pub fn to_string(&self) -> Cow<str> {
259        match self.node {
260            Node::InlineString(str) => Cow::Borrowed(str.as_ref()),
261            Node::BoxedString(str) => Cow::Borrowed(str.as_ref()),
262            Node::Integer(value) => Cow::Owned(format!("{}", value)),
263            Node::Boolean(true) => Cow::Borrowed("true"),
264            Node::Boolean(false) => Cow::Borrowed("false"),
265            _ => Cow::Borrowed(""),
266        }
267    }
268
269    /// Returns the int value represented by this element.
270    ///
271    /// # Example
272    ///
273    /// ```
274    /// # use jupiter::infograph::builder::DocBuilder;
275    /// let mut builder = DocBuilder::new();
276    /// let mut list_builder = builder.root_list_builder();
277    /// list_builder.append_int(42);
278    /// let doc = builder.build();
279    ///
280    /// let value = doc.root().at(0).as_int().unwrap();
281    ///
282    /// assert_eq!(value, 42);
283    /// ```
284    pub fn as_int(&self) -> Option<i64> {
285        if let Node::Integer(value) = self.node {
286            Some(*value)
287        } else {
288            None
289        }
290    }
291
292    /// Returns the bool value represented by this element.
293    ///
294    /// Note that this actually represents a tri-state logic by returning an `Option<bool>`.
295    /// This helps to distinguish missing values from `false`. If this isn't necessarry,
296    /// [as_bool()](Element::as_bool) can be used which treats both cases as `false`.
297    ///
298    /// # Example
299    ///
300    /// ```
301    /// # use jupiter::infograph::builder::DocBuilder;
302    /// let mut builder = DocBuilder::new();
303    /// let mut list_builder = builder.root_list_builder();
304    /// list_builder.append_bool(true);
305    /// list_builder.append_bool(false);
306    /// list_builder.append_string("true");
307    /// list_builder.append_int(1);
308    /// let doc = builder.build();
309    ///
310    /// assert_eq!(doc.root().at(0).try_as_bool().unwrap(), true);
311    /// assert_eq!(doc.root().at(1).try_as_bool().unwrap(), false);
312    /// assert_eq!(doc.root().at(2).try_as_bool().is_none(), true);
313    /// assert_eq!(doc.root().at(3).try_as_bool().is_none(), true);
314    /// assert_eq!(doc.root().at(4).try_as_bool().is_none(), true);
315    /// ```
316    pub fn try_as_bool(&self) -> Option<bool> {
317        if let Node::Boolean(value) = self.node {
318            Some(*value)
319        } else {
320            None
321        }
322    }
323
324    /// Returns `true` if this element wraps an actual `bool` `true` or `false` in all other cases.
325    ///
326    /// Note that [try_as_bool()](Element::try_as_bool) can be used if `false` needs to be
327    /// distinguished from missing or non-boolean elements.
328    ///
329    /// # Example
330    ///
331    /// ```
332    /// # use jupiter::infograph::builder::DocBuilder;
333    /// let mut builder = DocBuilder::new();
334    /// let mut list_builder = builder.root_list_builder();
335    /// list_builder.append_bool(true);
336    /// list_builder.append_bool(false);
337    /// list_builder.append_string("true");
338    /// list_builder.append_int(1);
339    /// let doc = builder.build();
340    ///
341    /// assert_eq!(doc.root().at(0).as_bool(), true);
342    /// assert_eq!(doc.root().at(1).as_bool(), false);
343    /// assert_eq!(doc.root().at(2).as_bool(), false);
344    /// assert_eq!(doc.root().at(3).as_bool(), false);
345    /// assert_eq!(doc.root().at(4).as_bool(), false);
346    /// ```
347    pub fn as_bool(&self) -> bool {
348        if let Node::Boolean(value) = self.node {
349            *value
350        } else {
351            false
352        }
353    }
354
355    /// Determines if this element is a list.
356    ///
357    /// # Example
358    ///
359    /// ```
360    /// # use jupiter::infograph::builder::DocBuilder;
361    /// let mut builder = DocBuilder::new();
362    /// let mut list_builder = builder.root_list_builder();
363    /// list_builder.append_int(1);
364    /// let doc = builder.build();
365    ///
366    /// assert_eq!(doc.root().is_list(), true);
367    /// assert_eq!(doc.root().at(1).is_list(), false);
368    /// ```
369    pub fn is_list(&self) -> bool {
370        if let Node::List(_) = self.node {
371            true
372        } else {
373            false
374        }
375    }
376
377    /// Returns the number of elements in the underlying list or number of entries in the
378    /// underlying map.
379    ///
380    /// If this element is neither a list nor a map, 0 is returned.
381    ///
382    /// # Example
383    ///
384    /// ```
385    /// # use jupiter::infograph::builder::DocBuilder;
386    /// let mut builder = DocBuilder::new();
387    /// let mut list_builder = builder.root_list_builder();
388    /// list_builder.append_object().put_int("Foo", 42);
389    /// let doc = builder.build();
390    ///
391    /// assert_eq!(doc.root().len(), 1);
392    /// assert_eq!(doc.root().at(0).len(), 1);
393    ///
394    /// assert_eq!(doc.root().at(0).query("Foo").len(), 0);
395    /// assert_eq!(doc.root().at(1).len(), 0);
396    /// ```
397    pub fn len(&self) -> usize {
398        match self.node {
399            Node::List(list) => list.len(),
400            Node::Object(map) => map.len(),
401            _ => 0,
402        }
403    }
404
405    /// Returns the n-th element of the underlying list.
406    ///
407    /// If the underlying element isn't a list or if the given index is outside of the range
408    /// of the list, an `empty` element is returned.
409    ///
410    /// # Example
411    ///
412    /// ```
413    /// # use jupiter::infograph::builder::DocBuilder;
414    /// let mut builder = DocBuilder::new();
415    /// let mut list_builder = builder.root_list_builder();
416    /// list_builder.append_string("Foo");
417    /// let doc = builder.build();
418    ///
419    /// assert_eq!(doc.root().at(0).as_str().unwrap(), "Foo");
420    /// assert_eq!(doc.root().at(1).is_empty(), true);
421    /// assert_eq!(doc.root().at(0).at(1).is_empty(), true);
422    /// ```
423    pub fn at(self, index: usize) -> Element<'a> {
424        if let Node::List(list) = self.node {
425            if let Some(node) = list.get(index) {
426                return Element {
427                    doc: self.doc,
428                    node,
429                };
430            }
431        }
432
433        Element {
434            doc: self.doc,
435            node: &Node::Empty,
436        }
437    }
438
439    /// Returns an iterator for all elements of the underlying list.
440    ///
441    /// If the list is empty or if the underlying element isn't a list, an
442    /// empty iterator will be returned.
443    ///
444    /// # Example
445    ///
446    /// ```
447    /// # use jupiter::infograph::builder::DocBuilder;
448    /// # use itertools::Itertools;
449    /// # use jupiter::infograph::docs::Doc;
450    /// let mut builder = DocBuilder::new();
451    /// let mut list_builder = builder.root_list_builder();
452    /// list_builder.append_int(1);
453    /// list_builder.append_int(2);
454    /// list_builder.append_int(3);
455    /// let doc = builder.build();
456    ///
457    /// assert_eq!(doc.root().iter().map(|e| format!("{}", e.to_string())).join(", "), "1, 2, 3");
458    ///
459    /// assert_eq!(Doc::empty().root().iter().next().is_none(), true);
460    /// ```
461    pub fn iter(self) -> ElementIter<'a> {
462        if let Node::List(list) = self.node {
463            ElementIter {
464                doc: self.doc,
465                iter: Some(list.iter()),
466            }
467        } else {
468            ElementIter {
469                doc: self.doc,
470                iter: None,
471            }
472        }
473    }
474
475    /// Returns an iterator over all entries of the underlying map.
476    ///
477    /// If the underlying element isn't a map, an empty iterator is returned.
478    ///
479    /// # Example
480    ///
481    /// ```
482    /// # use jupiter::infograph::builder::DocBuilder;
483    /// # use itertools::Itertools;
484    /// let mut builder = DocBuilder::new();
485    /// let mut obj_builder = builder.root_object_builder();
486    /// obj_builder.put_int("Foo", 42);
487    /// obj_builder.put_int("Bar", 24);
488    /// let doc = builder.build();
489    ///
490    /// let entry_string = doc
491    ///                     .root()
492    ///                     .entries()
493    ///                     .map(|(k, v)| format!("{}: {}", k, v.to_string()))
494    ///                     .join(", ");
495    /// assert_eq!(entry_string, "Foo: 42, Bar: 24");
496    /// ```
497    pub fn entries(self) -> EntryIter<'a> {
498        if let Node::Object(map) = self.node {
499            EntryIter {
500                doc: self.doc,
501                iter: Some(map.entries()),
502            }
503        } else {
504            EntryIter {
505                doc: self.doc,
506                iter: None,
507            }
508        }
509    }
510}
511
512impl Query {
513    /// Executes the query against the given element.
514    ///
515    /// Note that the element must be part of the same doc for which the query has been compiled
516    /// otherwise the return value is undefined.
517    ///
518    /// # Example
519    /// ```
520    /// # use jupiter::infograph::builder::DocBuilder;
521    /// let mut builder = DocBuilder::new();
522    /// let mut object_builder = builder.root_object_builder();
523    /// object_builder.put_object("Foo").unwrap().put_int("Bar", 42).unwrap();
524    /// let doc = builder.build();
525    ///
526    /// let query = doc.compile("Foo.Bar");
527    /// assert_eq!(query.execute(doc.root()).as_int().unwrap(), 42)
528    /// ```
529    pub fn execute<'a>(&self, element: Element<'a>) -> Element<'a> {
530        let mut current_node = if self.path.is_empty() {
531            &Node::Empty
532        } else {
533            element.node
534        };
535
536        for key in self.path.iter() {
537            if let Node::Object(map) = current_node {
538                current_node = map.get(*key).unwrap_or(&Node::Empty);
539            } else {
540                current_node = &Node::Empty;
541            }
542        }
543
544        Element {
545            doc: element.doc,
546            node: current_node,
547        }
548    }
549}
550
551impl Debug for Element<'_> {
552    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
553        match self.node {
554            Node::Empty => write!(f, "(Empty)"),
555            Node::Integer(value) => write!(f, "{}", value),
556            Node::Boolean(value) => write!(f, "{}", value),
557            Node::InlineString(value) => write!(f, "{}", value.as_ref()),
558            Node::BoxedString(value) => write!(f, "{}", value.as_ref()),
559            Node::List(_) => f.debug_list().entries(self.iter()).finish(),
560            Node::Object(_) => {
561                let mut helper = f.debug_map();
562                for (name, value) in self.entries() {
563                    helper.key(&name).value(&value);
564                }
565                helper.finish()
566            }
567        }
568    }
569}
570
571impl<'a> Iterator for ElementIter<'a> {
572    type Item = Element<'a>;
573
574    fn next(&mut self) -> Option<Self::Item> {
575        if let Some(ref mut iter) = self.iter {
576            if let Some(node) = iter.next() {
577                Some(Element {
578                    doc: self.doc,
579                    node,
580                })
581            } else {
582                None
583            }
584        } else {
585            None
586        }
587    }
588}
589
590impl<'a> Iterator for EntryIter<'a> {
591    type Item = (&'a str, Element<'a>);
592
593    fn next(&mut self) -> Option<Self::Item> {
594        if let Some(ref mut iter) = self.iter {
595            if let Some((symbol, node)) = iter.next() {
596                Some((
597                    self.doc.symbols.lookup(*symbol),
598                    Element {
599                        doc: self.doc,
600                        node,
601                    },
602                ))
603            } else {
604                None
605            }
606        } else {
607            None
608        }
609    }
610}