toml_query/resolver/
mut_resolver.rs

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