toml_query/resolver/
non_mut_resolver.rs

1/// The query resolver that operates on the AST and the TOML object
2use std::ops::Index;
3
4use crate::error::{Error, Result};
5use crate::tokenizer::Token;
6use toml::Value;
7
8/// Resolves the path in the passed document recursively
9///
10/// # Guarantees
11///
12/// If error_if_not_found is set to true, this function does not return Ok(None) in any case.
13///
14pub fn resolve<'doc>(
15    toml: &'doc Value,
16    tokens: &Token,
17    error_if_not_found: bool,
18) -> Result<Option<&'doc Value>> {
19    match toml {
20        Value::Table(ref t) => match tokens {
21            Token::Identifier { ref ident, .. } => match t.get(ident) {
22                None => {
23                    if error_if_not_found {
24                        Err(Error::IdentifierNotFoundInDocument(ident.to_owned()))
25                    } else {
26                        Ok(None)
27                    }
28                }
29                Some(sub_document) => match tokens.next() {
30                    Some(next) => resolve(sub_document, next, error_if_not_found),
31                    None => Ok(Some(sub_document)),
32                },
33            },
34
35            Token::Index { idx, .. } => Err(Error::NoIndexInTable(*idx)),
36        },
37
38        Value::Array(ref ary) => match tokens {
39            Token::Index { idx, .. } => match tokens.next() {
40                Some(next) => resolve(ary.get(*idx).unwrap(), next, error_if_not_found),
41                None => {
42                    if ary.get(*idx).is_none() {
43                        Err(Error::IndexOutOfBounds(*idx, ary.len()))
44                    } else {
45                        Ok(Some(ary.index(*idx)))
46                    }
47                }
48            },
49            Token::Identifier { ref ident, .. } => Err(Error::NoIdentifierInArray(ident.clone())),
50        },
51
52        _ => match tokens {
53            Token::Identifier { ref ident, .. } => Err(Error::QueryingValueAsTable(ident.clone())),
54
55            Token::Index { idx, .. } => Err(Error::QueryingValueAsArray(*idx)),
56        },
57    }
58}
59
60#[cfg(test)]
61mod test {
62    use super::resolve;
63    use crate::error::*;
64    use crate::tokenizer::*;
65    use toml::from_str as toml_from_str;
66    use toml::Value;
67
68    macro_rules! do_resolve {
69        ( $toml:ident => $query:expr ) => {
70            resolve(
71                &$toml,
72                &tokenize_with_seperator(&String::from($query), '.').unwrap(),
73                true,
74            )
75        };
76    }
77
78    #[test]
79    fn test_resolve_empty_toml_simple_query() {
80        let toml = toml_from_str("").unwrap();
81        let result = do_resolve!(toml => "example");
82
83        assert!(result.is_err());
84        let result = result.unwrap_err();
85
86        assert!(is_match!(result, Error::IdentifierNotFoundInDocument { .. }));
87    }
88
89    #[test]
90    fn test_resolve_present_bool() {
91        let toml = toml_from_str("example = true").unwrap();
92        let result = do_resolve!(toml => "example");
93
94        assert!(result.is_ok());
95        let result = result.unwrap();
96
97        assert!(result.is_some());
98        let result = result.unwrap();
99
100        assert!(is_match!(result, Value::Boolean(true)));
101    }
102
103    #[test]
104    fn test_resolve_present_integer() {
105        let toml = toml_from_str("example = 1").unwrap();
106        let result = do_resolve!(toml => "example");
107
108        assert!(result.is_ok());
109        let result = result.unwrap();
110
111        assert!(result.is_some());
112        let result = result.unwrap();
113
114        assert!(is_match!(result, Value::Integer(1)));
115    }
116
117    #[test]
118    fn test_resolve_present_float() {
119        let toml = toml_from_str("example = 1.0").unwrap();
120        let result = do_resolve!(toml => "example");
121
122        assert!(result.is_ok());
123        let result = result.unwrap();
124
125        assert!(result.is_some());
126        let result = result.unwrap();
127
128        assert!(is_match!(result, Value::Float(_)));
129        assert_eq!(result.as_float(), Some(1.0))
130    }
131
132    #[test]
133    fn test_resolve_present_string() {
134        let toml = toml_from_str("example = 'string'").unwrap();
135        let result = do_resolve!(toml => "example");
136
137        assert!(result.is_ok());
138        let result = result.unwrap();
139
140        assert!(result.is_some());
141        let result = result.unwrap();
142
143        assert!(is_match!(result, Value::String(_)));
144        match result {
145            Value::String(ref s) => assert_eq!("string", s),
146            _ => panic!("What just happened?"),
147        }
148    }
149
150    #[test]
151    fn test_resolve_present_array_bools() {
152        let toml = toml_from_str("example = [ true, false ]").unwrap();
153        let result = do_resolve!(toml => "example");
154
155        assert!(result.is_ok());
156        let result = result.unwrap();
157
158        assert!(result.is_some());
159        let result = result.unwrap();
160
161        assert!(is_match!(result, Value::Array(_)));
162        match result {
163            Value::Array(ref ary) => {
164                assert_eq!(ary[0], Value::Boolean(true));
165                assert_eq!(ary[1], Value::Boolean(false));
166            }
167            _ => panic!("What just happened?"),
168        }
169    }
170
171    #[test]
172    fn test_resolve_present_array_integers() {
173        let toml = toml_from_str("example = [ 1, 1337 ]").unwrap();
174        let result = do_resolve!(toml => "example");
175
176        assert!(result.is_ok());
177        let result = result.unwrap();
178
179        assert!(result.is_some());
180        let result = result.unwrap();
181
182        assert!(is_match!(result, Value::Array(_)));
183        match result {
184            Value::Array(ref ary) => {
185                assert_eq!(ary[0], Value::Integer(1));
186                assert_eq!(ary[1], Value::Integer(1337));
187            }
188            _ => panic!("What just happened?"),
189        }
190    }
191
192    #[test]
193    fn test_resolve_present_array_floats() {
194        let toml = toml_from_str("example = [ 1.0, 133.25 ]").unwrap();
195        let result = do_resolve!(toml => "example");
196
197        assert!(result.is_ok());
198        let result = result.unwrap();
199
200        assert!(result.is_some());
201        let result = result.unwrap();
202
203        assert!(is_match!(result, Value::Array(_)));
204        match result {
205            Value::Array(ref ary) => {
206                assert!(is_match!(ary[0], Value::Float(_)));
207                assert_eq!(ary[0].as_float(), Some(1.0));
208                assert!(is_match!(ary[1], Value::Float(_)));
209                assert_eq!(ary[1].as_float(), Some(133.25));
210            }
211            _ => panic!("What just happened?"),
212        }
213    }
214
215    #[test]
216    fn test_resolve_array_index_query_1() {
217        let toml = toml_from_str("example = [ 1 ]").unwrap();
218        let result = do_resolve!(toml => "example.[0]");
219
220        assert!(result.is_ok());
221        let result = result.unwrap();
222
223        assert!(result.is_some());
224        let result = result.unwrap();
225
226        assert!(is_match!(result, Value::Integer(1)));
227    }
228
229    #[test]
230    fn test_resolve_array_index_query_2() {
231        let toml = toml_from_str("example = [ 1, 2, 3, 4, 5 ]").unwrap();
232        let result = do_resolve!(toml => "example.[4]");
233
234        assert!(result.is_ok());
235        let result = result.unwrap();
236
237        assert!(result.is_some());
238        let result = result.unwrap();
239
240        assert!(is_match!(result, Value::Integer(5)));
241    }
242
243    #[test]
244    fn test_resolve_table_element_query() {
245        let toml = toml_from_str(
246            r#"
247        [table]
248        value = 42
249        "#,
250        )
251        .unwrap();
252        let result = do_resolve!(toml => "table.value");
253
254        assert!(result.is_ok());
255        let result = result.unwrap();
256
257        assert!(result.is_some());
258        let result = result.unwrap();
259
260        assert!(is_match!(result, Value::Integer(42)));
261    }
262
263    #[test]
264    fn test_resolve_table_with_many_elements_element_query() {
265        let toml = toml_from_str(
266            r#"
267        [table]
268        value1 = 42
269        value2 = 43
270        value3 = 44
271        value4 = 45
272        value5 = 46
273        "#,
274        )
275        .unwrap();
276        let result = do_resolve!(toml => "table.value1");
277
278        assert!(result.is_ok());
279        let result = result.unwrap();
280
281        assert!(result.is_some());
282        let result = result.unwrap();
283
284        assert!(is_match!(result, Value::Integer(42)));
285    }
286
287    #[test]
288    fn test_resolve_table_array_query() {
289        let toml = toml_from_str(
290            r#"
291        [table]
292        value1 = [ 42.0, 50.0 ]
293        "#,
294        )
295        .unwrap();
296        let result = do_resolve!(toml => "table.value1");
297
298        assert!(result.is_ok());
299        let result = result.unwrap();
300
301        assert!(result.is_some());
302        let result = result.unwrap();
303
304        assert!(is_match!(result, Value::Array(_)));
305        match result {
306            Value::Array(ref ary) => {
307                assert!(is_match!(ary[0], Value::Float(_)));
308                assert_eq!(ary[0].as_float(), Some(42.0));
309                assert!(is_match!(ary[1], Value::Float(_)));
310                assert_eq!(ary[1].as_float(), Some(50.0));
311            }
312            _ => panic!("What just happened?"),
313        }
314    }
315
316    #[test]
317    fn test_resolve_table_array_element_query() {
318        let toml = toml_from_str(
319            r#"
320        [table]
321        value1 = [ 42 ]
322        "#,
323        )
324        .unwrap();
325        let result = do_resolve!(toml => "table.value1.[0]");
326
327        assert!(result.is_ok());
328        let result = result.unwrap();
329
330        assert!(result.is_some());
331        let result = result.unwrap();
332
333        assert!(is_match!(result, Value::Integer(42)));
334    }
335
336    #[test]
337    fn test_resolve_multi_table_query() {
338        let toml = toml_from_str(
339            r#"
340        [table0]
341        value = [ 1 ]
342        [table1]
343        value = [ "Foo" ]
344        [table2]
345        value = [ 42.0 ]
346        [table3]
347        value = [ true ]
348        "#,
349        )
350        .unwrap();
351        let result = do_resolve!(toml => "table1.value.[0]");
352
353        assert!(result.is_ok());
354        let result = result.unwrap();
355
356        assert!(result.is_some());
357        let result = result.unwrap();
358
359        assert!(is_match!(result, Value::String(_)));
360        match result {
361            Value::String(ref s) => assert_eq!("Foo", s),
362            _ => panic!("What just happened?"),
363        }
364    }
365
366    static FRUIT_TABLE: &str = r#"
367    [[fruit.blah]]
368      name = "apple"
369
370      [fruit.blah.physical]
371        color = "red"
372        shape = "round"
373
374    [[fruit.blah]]
375      name = "banana"
376
377      [fruit.blah.physical]
378        color = "yellow"
379        shape = "bent"
380    "#;
381
382    #[test]
383    fn test_resolve_array_table_query_1() {
384        let toml = toml_from_str(FRUIT_TABLE).unwrap();
385        let result = do_resolve!(toml => "fruit.blah.[0].name");
386
387        assert!(result.is_ok());
388        let result = result.unwrap();
389
390        assert!(result.is_some());
391        let result = result.unwrap();
392
393        assert!(is_match!(result, Value::String(_)));
394        match result {
395            Value::String(ref s) => assert_eq!("apple", s),
396            _ => panic!("What just happened?"),
397        }
398    }
399
400    #[test]
401    fn test_resolve_array_table_query_2() {
402        let toml = toml_from_str(FRUIT_TABLE).unwrap();
403        let result = do_resolve!(toml => "fruit.blah.[0].physical");
404
405        assert!(result.is_ok());
406        let result = result.unwrap();
407
408        assert!(result.is_some());
409        let result = result.unwrap();
410
411        assert!(is_match!(result, Value::Table(_)));
412        match result {
413            Value::Table(ref tab) => {
414                match tab.get("color") {
415                    Some(&Value::String(ref s)) => assert_eq!("red", s),
416                    _ => unreachable!(),
417                }
418                match tab.get("shape") {
419                    Some(&Value::String(ref s)) => assert_eq!("round", s),
420                    _ => unreachable!(),
421                }
422            }
423            _ => panic!("What just happened?"),
424        }
425    }
426
427    #[test]
428    fn test_resolve_query_on_result() {
429        let toml = toml_from_str(FRUIT_TABLE).unwrap();
430        let result = do_resolve!(toml => "fruit.blah.[1].physical");
431
432        assert!(result.is_ok());
433        let result = result.unwrap();
434
435        assert!(result.is_some());
436        let result = result.unwrap();
437
438        let tokens = tokenize_with_seperator(&String::from("color"), '.').unwrap();
439        let result = resolve(result, &tokens, true);
440
441        assert!(result.is_ok());
442        let result = result.unwrap();
443
444        assert!(result.is_some());
445        let result = result.unwrap();
446
447        assert!(is_match!(result, Value::String(_)));
448        match result {
449            Value::String(ref s) => assert_eq!("yellow", s),
450            _ => panic!("What just happened?"),
451        }
452    }
453
454    #[test]
455    fn test_resolve_query_empty_table() {
456        let toml = toml_from_str(
457            r#"
458        [example]
459        "#,
460        )
461        .unwrap();
462        let result = do_resolve!(toml => "example");
463
464        assert!(result.is_ok());
465        let result = result.unwrap();
466
467        assert!(result.is_some());
468        let result = result.unwrap();
469
470        assert!(is_match!(result, Value::Table(_)));
471        match result {
472            Value::Table(ref t) => assert!(t.is_empty()),
473            _ => panic!("What just happened?"),
474        }
475    }
476
477    #[test]
478    fn test_resolve_query_member_of_empty_table() {
479        let toml = toml_from_str(
480            r#"
481        [example]
482        "#,
483        )
484        .unwrap();
485        let result = do_resolve!(toml => "example.foo");
486
487        assert!(result.is_err());
488        let result = result.unwrap_err();
489
490        assert!(is_match!(result, Error::IdentifierNotFoundInDocument { .. }));
491    }
492
493    #[test]
494    fn test_resolve_query_index_in_table() {
495        let toml = toml_from_str(
496            r#"
497        [example]
498        "#,
499        )
500        .unwrap();
501        let result = do_resolve!(toml => "example.[0]");
502
503        assert!(result.is_err());
504        let result = result.unwrap_err();
505
506        assert!(is_match!(result, Error::NoIndexInTable { .. }));
507    }
508
509    #[test]
510    fn test_resolve_query_identifier_in_array() {
511        let toml = toml_from_str(
512            r#"
513        [example]
514        foo = [ 1, 2, 3 ]
515        "#,
516        )
517        .unwrap();
518        let result = do_resolve!(toml => "example.foo.bar");
519
520        assert!(result.is_err());
521        let result = result.unwrap_err();
522
523        assert!(is_match!(result, Error::NoIdentifierInArray { .. }));
524    }
525
526    #[test]
527    fn test_resolve_query_value_as_table() {
528        let toml = toml_from_str(
529            r#"
530        [example]
531        foo = 1
532        "#,
533        )
534        .unwrap();
535        let result = do_resolve!(toml => "example.foo.bar");
536
537        assert!(result.is_err());
538        let result = result.unwrap_err();
539
540        assert!(is_match!(result, Error::QueryingValueAsTable { .. }));
541    }
542
543    #[test]
544    fn test_resolve_query_value_as_array() {
545        let toml = toml_from_str(
546            r#"
547        [example]
548        foo = 1
549        "#,
550        )
551        .unwrap();
552        let result = do_resolve!(toml => "example.foo.[0]");
553
554        assert!(result.is_err());
555        let result = result.unwrap_err();
556
557        assert!(is_match!(result, Error::QueryingValueAsArray { .. }));
558    }
559
560    #[test]
561    fn test_indexing_out_of_bounds() {
562        let toml = toml_from_str(
563            r#"
564        [example]
565        foo = [ 1, 2, 3 ]
566        "#,
567        )
568        .unwrap();
569        let result = do_resolve!(toml => "example.foo.[12]");
570
571        assert!(result.is_err());
572        let result = result.unwrap_err();
573
574        assert!(is_match!(result, Error::IndexOutOfBounds { .. }));
575    }
576
577    #[test]
578    fn test_indexing_out_of_bounds_edgecase_1() {
579        let toml = toml_from_str(
580            r#"
581        [example]
582        foo = []
583        "#,
584        )
585        .unwrap();
586        let result = do_resolve!(toml => "example.foo.[0]");
587
588        assert!(result.is_err());
589        let result = result.unwrap_err();
590
591        assert!(is_match!(result, Error::IndexOutOfBounds { .. }));
592    }
593
594    #[test]
595    fn test_indexing_out_of_bounds_edgecase_2() {
596        let toml = toml_from_str(
597            r#"
598        [example]
599        foo = [ 1 ]
600        "#,
601        )
602        .unwrap();
603        let result = do_resolve!(toml => "example.foo.[1]");
604
605        assert!(result.is_err());
606        let result = result.unwrap_err();
607
608        assert!(is_match!(result, Error::IndexOutOfBounds { .. }));
609    }
610}