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