/// The query resolver that operates on the AST and the TOML object use std::ops::Index; use serde::Deserialize; use crate::error::{Error, Result}; use crate::tokenizer::Token; use crate::object::{Object, ObjectType}; pub fn resolve<'doc, O>(obj: &'doc O, tokens: &Token, error_if_not_found: bool) -> Result> where O: Object<'doc> { trace!("Resolving Token: {:?}", tokens); match obj.get_type() { ObjectType::Map => { trace!("Found object type Map"); match tokens { Token::Identifier { ref ident, .. } => { trace!("Checking object at ident {}", ident); match obj.at_key(ident)? { None => if error_if_not_found { trace!("Not found, erroring"); Err(Error::IdentifierNotFoundInDocument(ident.to_owned())) } else { trace!("Not found, not erroring"); Ok(None) } Some(sub_document) => { trace!("Found sub-document, getting next token"); match tokens.next() { Some(next) => resolve(sub_document, next, error_if_not_found), None => { trace!("No next token, returning"); Ok(Some(sub_document)) }, } }, } }, Token::Index { idx, .. } => { trace!("Token is index, but cannot index at document, erroring..."); Err(Error::NoIndexInTable(*idx)) }, } }, ObjectType::Array => { trace!("Found object type Array"); match tokens { Token::Index { idx, .. } => { trace!("Checking object at index {}", idx); match tokens.next() { Some(next) => { trace!("There is another token..."); if let Some(subobj) = obj.at_index(*idx)? { trace!("And there's a subobject, resolving..."); resolve(subobj, next, error_if_not_found) } else { trace!("But no subobject at this index. Returning"); Ok(None) } }, None => { trace!("No more tokens, returning array at index {}", idx); obj.at_index(*idx) }, } }, Token::Identifier { ref ident, .. } => { trace!("Token is identifier, but cannot get identifier when having array, erroring..."); Err(Error::NoIdentifierInArray(ident.clone())) }, } }, ObjectType::Atom => { trace!("Object is an atom, cannot resolve further"); match tokens { Token::Identifier { ref ident, .. } => Err(Error::QueryingValueAsTable(ident.clone())), Token::Index { idx, .. } => Err(Error::QueryingValueAsArray(*idx)), } } } } #[cfg(test)] mod test { use super::resolve; use crate::error::*; use crate::tokenizer::*; use toml::from_str as toml_from_str; use toml::Value; fn setup_logging() { let _ = env_logger::try_init(); } macro_rules! do_resolve { ( $toml:ident => $query:expr ) => { resolve( &$toml, &tokenize_with_seperator(&String::from($query), '.').unwrap(), true, ) }; } #[test] fn test_resolve_empty_toml_simple_query() { setup_logging(); let toml : toml::Value = toml_from_str("").unwrap(); let result = do_resolve!(toml => "example"); assert!(result.is_err()); let result = result.unwrap_err(); assert!(is_match!(result, Error::IdentifierNotFoundInDocument { .. })); } #[test] fn test_resolve_present_bool() { setup_logging(); let toml: toml::Value = toml_from_str("example = true").unwrap(); let result = do_resolve!(toml => "example"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Boolean(true))); } #[test] fn test_resolve_present_integer() { setup_logging(); let toml: toml::Value = toml_from_str("example = 1").unwrap(); let result = do_resolve!(toml => "example"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Integer(1))); } #[test] fn test_resolve_present_float() { setup_logging(); let toml: toml::Value = toml_from_str("example = 1.0").unwrap(); let result = do_resolve!(toml => "example"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Float(_))); assert_eq!(result.as_float(), Some(1.0)) } #[test] fn test_resolve_present_string() { setup_logging(); let toml: toml::Value = toml_from_str("example = 'string'").unwrap(); let result = do_resolve!(toml => "example"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::String(_))); match result { Value::String(ref s) => assert_eq!("string", s), _ => panic!("What just happened?"), } } #[test] fn test_resolve_present_array_bools() { setup_logging(); let toml: toml::Value = toml_from_str("example = [ true, false ]").unwrap(); let result = do_resolve!(toml => "example"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Array(_))); match result { Value::Array(ref ary) => { assert_eq!(ary[0], Value::Boolean(true)); assert_eq!(ary[1], Value::Boolean(false)); } _ => panic!("What just happened?"), } } #[test] fn test_resolve_present_array_integers() { setup_logging(); let toml: toml::Value = toml_from_str("example = [ 1, 1337 ]").unwrap(); let result = do_resolve!(toml => "example"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Array(_))); match result { Value::Array(ref ary) => { assert_eq!(ary[0], Value::Integer(1)); assert_eq!(ary[1], Value::Integer(1337)); } _ => panic!("What just happened?"), } } #[test] fn test_resolve_present_array_floats() { setup_logging(); let toml: toml::Value = toml_from_str("example = [ 1.0, 133.25 ]").unwrap(); let result = do_resolve!(toml => "example"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Array(_))); match result { Value::Array(ref ary) => { assert!(is_match!(ary[0], Value::Float(_))); assert_eq!(ary[0].as_float(), Some(1.0)); assert!(is_match!(ary[1], Value::Float(_))); assert_eq!(ary[1].as_float(), Some(133.25)); } _ => panic!("What just happened?"), } } #[test] fn test_resolve_array_index_query_1() { setup_logging(); let toml: toml::Value = toml_from_str("example = [ 1 ]").unwrap(); let result = do_resolve!(toml => "example.[0]"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Integer(1))); } #[test] fn test_resolve_array_index_query_2() { setup_logging(); let toml: toml::Value = toml_from_str("example = [ 1, 2, 3, 4, 5 ]").unwrap(); let result = do_resolve!(toml => "example.[4]"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Integer(5)), format!("Not 5: {:?}", result)); } #[test] fn test_resolve_table_element_query() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [table] value = 42 "#, ) .unwrap(); let result = do_resolve!(toml => "table.value"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Integer(42))); } #[test] fn test_resolve_table_with_many_elements_element_query() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [table] value1 = 42 value2 = 43 value3 = 44 value4 = 45 value5 = 46 "#, ) .unwrap(); let result = do_resolve!(toml => "table.value1"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Integer(42))); } #[test] fn test_resolve_table_array_query() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [table] value1 = [ 42.0, 50.0 ] "#, ) .unwrap(); let result = do_resolve!(toml => "table.value1"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Array(_))); match result { Value::Array(ref ary) => { assert!(is_match!(ary[0], Value::Float(_))); assert_eq!(ary[0].as_float(), Some(42.0)); assert!(is_match!(ary[1], Value::Float(_))); assert_eq!(ary[1].as_float(), Some(50.0)); } _ => panic!("What just happened?"), } } #[test] fn test_resolve_table_array_element_query() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [table] value1 = [ 42 ] "#, ) .unwrap(); let result = do_resolve!(toml => "table.value1.[0]"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Integer(42))); } #[test] fn test_resolve_multi_table_query() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [table0] value = [ 1 ] [table1] value = [ "Foo" ] [table2] value = [ 42.0 ] [table3] value = [ true ] "#, ) .unwrap(); let result = do_resolve!(toml => "table1.value.[0]"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::String(_))); match result { Value::String(ref s) => assert_eq!("Foo", s), _ => panic!("What just happened?"), } } static FRUIT_TABLE: &str = r#" [[fruit.blah]] name = "apple" [fruit.blah.physical] color = "red" shape = "round" [[fruit.blah]] name = "banana" [fruit.blah.physical] color = "yellow" shape = "bent" "#; #[test] fn test_resolve_array_table_query_1() { setup_logging(); let toml: toml::Value = toml_from_str(FRUIT_TABLE).unwrap(); let result = do_resolve!(toml => "fruit.blah.[0].name"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::String(_))); match result { Value::String(ref s) => assert_eq!("apple", s), _ => panic!("What just happened?"), } } #[test] fn test_resolve_array_table_query_2() { setup_logging(); let toml: toml::Value = toml_from_str(FRUIT_TABLE).unwrap(); let result = do_resolve!(toml => "fruit.blah.[0].physical"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Table(_))); match result { Value::Table(ref tab) => { match tab.get("color") { Some(&Value::String(ref s)) => assert_eq!("red", s), _ => unreachable!(), } match tab.get("shape") { Some(&Value::String(ref s)) => assert_eq!("round", s), _ => unreachable!(), } } _ => panic!("What just happened?"), } } #[test] fn test_resolve_query_on_result() { setup_logging(); let toml: toml::Value = toml_from_str(FRUIT_TABLE).unwrap(); let result = do_resolve!(toml => "fruit.blah.[1].physical"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); let tokens = tokenize_with_seperator(&String::from("color"), '.').unwrap(); let result = resolve(result, &tokens, true); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::String(_))); match result { Value::String(ref s) => assert_eq!("yellow", s), _ => panic!("What just happened?"), } } #[test] fn test_resolve_query_empty_table() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [example] "#, ) .unwrap(); let result = do_resolve!(toml => "example"); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.is_some()); let result = result.unwrap(); assert!(is_match!(result, Value::Table(_))); match result { Value::Table(ref t) => assert!(t.is_empty()), _ => panic!("What just happened?"), } } #[test] fn test_resolve_query_member_of_empty_table() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [example] "#, ) .unwrap(); let result = do_resolve!(toml => "example.foo"); assert!(result.is_err()); let result = result.unwrap_err(); assert!(is_match!(result, Error::IdentifierNotFoundInDocument { .. })); } #[test] fn test_resolve_query_index_in_table() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [example] "#, ) .unwrap(); let result = do_resolve!(toml => "example.[0]"); assert!(result.is_err()); let result = result.unwrap_err(); assert!(is_match!(result, Error::NoIndexInTable { .. })); } #[test] fn test_resolve_query_identifier_in_array() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [example] foo = [ 1, 2, 3 ] "#, ) .unwrap(); let result = do_resolve!(toml => "example.foo.bar"); assert!(result.is_err()); let result = result.unwrap_err(); assert!(is_match!(result, Error::NoIdentifierInArray { .. })); } #[test] fn test_resolve_query_value_as_table() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [example] foo = 1 "#, ) .unwrap(); let result = do_resolve!(toml => "example.foo.bar"); assert!(result.is_err()); let result = result.unwrap_err(); assert!(is_match!(result, Error::QueryingValueAsTable { .. })); } #[test] fn test_resolve_query_value_as_array() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [example] foo = 1 "#, ) .unwrap(); let result = do_resolve!(toml => "example.foo.[0]"); assert!(result.is_err()); let result = result.unwrap_err(); assert!(is_match!(result, Error::QueryingValueAsArray { .. })); } #[test] fn test_indexing_out_of_bounds() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [example] foo = [ 1, 2, 3 ] "#, ) .unwrap(); let result = do_resolve!(toml => "example.foo.[12]"); assert!(result.is_err()); let result = result.unwrap_err(); assert!(is_match!(result, Error::IndexOutOfBounds { .. })); } #[test] fn test_indexing_out_of_bounds_edgecase_1() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [example] foo = [] "#, ) .unwrap(); let result = do_resolve!(toml => "example.foo.[0]"); assert!(result.is_err()); let result = result.unwrap_err(); assert!(is_match!(result, Error::IndexOutOfBounds { .. })); } #[test] fn test_indexing_out_of_bounds_edgecase_2() { setup_logging(); let toml: toml::Value = toml_from_str( r#" [example] foo = [ 1 ] "#, ) .unwrap(); let result = do_resolve!(toml => "example.foo.[1]"); assert!(result.is_err()); let result = result.unwrap_err(); assert!(is_match!(result, Error::IndexOutOfBounds { .. })); } }