From 96207596240651cde1f7c19bcb12ba2575a980eb Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 10 Jan 2020 19:23:39 +0100 Subject: Switch to thiserror --- src/error.rs | 50 +++-- src/lib.rs | 4 +- src/object.rs | 25 ++- src/read.rs | 49 +---- src/resolver.rs | 613 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 669 insertions(+), 72 deletions(-) create mode 100644 src/resolver.rs (limited to 'src') diff --git a/src/error.rs b/src/error.rs index eb9d161..82eff3d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,69 +1,67 @@ /// Error types +use thiserror::Error; + pub type Result = ::std::result::Result; -#[derive(Debug, Fail)] +#[derive(Debug, Error)] pub enum Error { - #[cfg(feature = "typed")] - #[fail(display = "{}", _0)] - Serialize(#[cause] ::serde::ser::Error), + #[error("Serialization error")] + Serialize(/*#[from] Box<::serde::ser::Error>*/), - #[cfg(feature = "typed")] - #[fail(display = "{}", _0)] - Deserialize(#[cause] ::serde::de::Error), + #[error("Deserialization error")] + Deserialize(/*#[from] Box<::serde::de::Error>*/), // Errors for tokenizer - #[fail(display = "Parsing the query '{}' failed", _0)] + #[error("Parsing the query '{}' failed", _0)] QueryParsingError(String), - #[fail(display = "The query on the document is empty")] + #[error("The query on the document is empty")] EmptyQueryError, - #[fail(display = "The passed query has an empty identifier")] + #[error("The passed query has an empty identifier")] EmptyIdentifier, - #[fail(display = "The passed query tries to access an array but does not specify the index")] + #[error("The passed query tries to access an array but does not specify the index")] ArrayAccessWithoutIndex, - #[fail( - display = "The passed query tries to access an array but does not specify a valid index" - )] + #[error("The passed query tries to access an array but does not specify a valid index")] ArrayAccessWithInvalidIndex, // Errors for Resolver - #[fail(display = "The identfier '{}' is not present in the document", _0)] + #[error("The identfier '{}' is not present in the document", _0)] IdentifierNotFoundInDocument(String), - #[fail(display = "Got an index query '[{}]' but have table", _0)] + #[error("Got an index query '[{}]' but have table", _0)] NoIndexInTable(usize), - #[fail(display = "Got an identifier query '{}' but have array", _0)] + #[error("Got an identifier query '{}' but have array", _0)] NoIdentifierInArray(String), - #[fail(display = "Got an identifier query '{}' but have value", _0)] + #[error("Got an identifier query '{}' but have value", _0)] QueryingValueAsTable(String), - #[fail(display = "Got an index query '{}' but have value", _0)] + #[error("Got an index query '{}' but have value", _0)] QueryingValueAsArray(usize), - #[fail(display = "Cannot delete table '{:?}' which is not empty", _0)] + #[error("Cannot delete table '{:?}' which is not empty", _0)] CannotDeleteNonEmptyTable(Option), - #[fail(display = "Cannot delete array '{:?}' which is not empty", _0)] + #[error("Cannot delete array '{:?}' which is not empty", _0)] CannotDeleteNonEmptyArray(Option), - #[fail(display = "Cannot access {} because expected {}", _0, _1)] + #[error("Cannot access {} because expected {}", _0, _1)] CannotAccessBecauseTypeMismatch(&'static str, &'static str), - #[fail(display = "Cannot delete in array at {}, array has length {}", _0, _1)] + #[error("Cannot delete in array at {}, array has length {}", _0, _1)] ArrayIndexOutOfBounds(usize, usize), - #[fail(display = "Cannot access array at {}, array has length {}", _0, _1)] + #[error("Cannot access array at {}, array has length {}", _0, _1)] IndexOutOfBounds(usize, usize), - #[fail(display = "Type Error. Requested {}, but got {}", _0, _1)] + #[error("Type Error. Requested {}, but got {}", _0, _1)] TypeError(&'static str, &'static str), - #[fail(display = "Value at '{}' not there", _0)] + #[error("Value at '{}' not there", _0)] NotAvailable(String), } diff --git a/src/lib.rs b/src/lib.rs index 5e9f209..bfe3909 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ #[macro_use] extern crate log; -#[macro_use] extern crate failure; -#[macro_use] extern crate failure_derive; +#[macro_use] extern crate thiserror; #[macro_use] extern crate regex; #[macro_use] extern crate lazy_static; #[macro_use] extern crate is_match; @@ -16,3 +15,4 @@ pub mod query; pub mod read; mod tokenizer; +mod resolver; diff --git a/src/object.rs b/src/object.rs index e0eae87..640ade5 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,12 +1,15 @@ use crate::error::*; +use serde::Serialize; +use serde::Deserialize; + pub enum ObjectType { Atom, Map, Array, } -pub trait Object { +pub trait Object<'doc>: Serialize + Deserialize<'doc> + Clone { fn get_type(&self) -> ObjectType; fn has_key(&self, key: &str) -> bool; fn has_index(&self, idx: usize) -> bool; @@ -15,10 +18,12 @@ pub trait Object { fn at_index_mut<'a>(&'a mut self, idx: usize) -> Result>; fn at_key<'a>(&'a self, key: &str) -> Result>; fn at_key_mut<'a>(&'a mut self, key: &str) -> Result>; + + fn array_len(&self) -> Option; } #[cfg(feature = "backend_toml")] -impl Object for toml::Value { +impl<'doc> Object<'doc> for toml::Value { fn get_type(&self) -> ObjectType { match self { toml::Value::Boolean(_) @@ -77,11 +82,18 @@ impl Object for toml::Value { _ => Err(crate::error::Error::QueryingValueAsTable(key.to_string())), } } + + fn array_len(&self) -> Option { + match self { + toml::Value::Array(a) => Some(a.len()), + _ => None , + } + } } #[cfg(feature = "backend_serde_json")] -impl Object for serde_json::Value { +impl<'doc> Object<'doc> for serde_json::Value { fn get_type(&self) -> ObjectType { match self { serde_json::Value::Null @@ -139,5 +151,12 @@ impl Object for serde_json::Value { _ => Err(crate::error::Error::QueryingValueAsTable(key.to_string())), } } + + fn array_len(&self) -> Option { + match self { + serde_json::Value::Array(a) => Some(a.len()), + _ => None , + } + } } diff --git a/src/read.rs b/src/read.rs index 4779ce5..4847422 100644 --- a/src/read.rs +++ b/src/read.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use std::convert::TryInto; use crate::error::*; use crate::tokenizer::tokenize_with_seperator; @@ -6,13 +7,13 @@ use crate::object::*; use crate::tokenizer::Token; use crate::query::Query; -pub trait Read<'doc> : Object + Sized { +pub trait Read<'doc> : Object<'doc> + Sized { fn read(&'doc self, query: &Query) -> Result> { - query_with_token(self, query.token()) - } - - fn read_mut(&'doc mut self, query: &Query) -> Result> { - query_with_token_mut(self, query.token()) + //match query_with_token(self, query.token())? { + // None => Ok(None), + // Some(o) => Ok(Some(o.as_deserialize())), + //} + unimplemented!() } } @@ -23,7 +24,7 @@ impl<'doc> Read<'doc> for toml::Value { } impl<'doc> Read<'doc> for serde_json::Value { } fn query_with_token<'doc, O>(obj: &'doc O, token: &Token) -> Result> - where O: Object + where O: Object<'doc> { match token { Token::Identifier{ ident, .. } => { @@ -54,37 +55,3 @@ fn query_with_token<'doc, O>(obj: &'doc O, token: &Token) -> Result(obj: &'doc mut O, token: &Token) -> Result> - where O: Object -{ - match token { - Token::Identifier{ ident, .. } => { - let object = obj.at_key_mut(ident)?; - if let Some(object) = object { - if let Some(next_token) = token.next() { - query_with_token_mut(object, next_token) - } else { - Ok(Some(object)) - } - } else { - Ok(None) - } - } - - Token::Index { idx, .. } => { - let object = obj.at_index_mut(*idx)?; - if let Some(object) = object { - if let Some(next_token) = token.next() { - query_with_token_mut(object, next_token) - } else { - Ok(Some(object)) - } - } else { - Ok(None) - } - } - } -} - - - diff --git a/src/resolver.rs b/src/resolver.rs new file mode 100644 index 0000000..e34c6d0 --- /dev/null +++ b/src/resolver.rs @@ -0,0 +1,613 @@ +/// 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, D>(obj: &'doc O, tokens: &Token, error_if_not_found: bool) + -> Result> + where D: Deserialize<'doc>, + O: Object<'doc> +{ + match obj.get_type() { + ObjectType::Map => { + match tokens { + Token::Identifier { ref ident, .. } => match obj.at_key(ident)? { + None => if error_if_not_found { + Err(Error::IdentifierNotFoundInDocument(ident.to_owned())) + } else { + Ok(None) + } + Some(sub_document) => match tokens.next() { + Some(next) => resolve(sub_document, next, error_if_not_found), + None => Ok(Some(sub_document)), + }, + }, + + Token::Index { idx, .. } => Err(Error::NoIndexInTable(*idx)), + } + }, + + ObjectType::Array => { + match tokens { + Token::Index { idx, .. } => match tokens.next() { + Some(next) => { + if let Some(subobj) = obj.at_index(*idx)? { + resolve(subobj, next, error_if_not_found) + } else { + Ok(None) + } + }, + None => { + ary.at_index(*idx).ok_or_else(|| { + Error::IndexOutOfBounds(*idx, obj.array_len().unwrap()) // unwrap is safe here + }) + } + }, + Token::Identifier { ref ident, .. } => Err(Error::NoIdentifierInArray(ident.clone())), + } + }, + + ObjectType::Atom => { + 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; + + 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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))); + } + + #[test] + fn test_resolve_table_element_query() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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() { + let toml = 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 { .. })); + } +} -- cgit v1.2.3