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<Option<&'doc O>>
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);
if !obj.has_index(*idx) {
if let Some(len) = obj.array_len() {
return Err(Error::IndexOutOfBounds(*idx, len));
} else {
return Err(Error::IndexOutOfBounds(*idx, 0));
}
}
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(), format!("Error expected but having: {:?}", result));
let result = result.unwrap_err();
assert!(is_match!(result, Error::IndexOutOfBounds { .. }));
}
}