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}