~matthiasbeyer/serde-select

96207596240651cde1f7c19bcb12ba2575a980eb — Matthias Beyer 4 years ago ba67e24
Switch to thiserror
6 files changed, 670 insertions(+), 74 deletions(-)

M Cargo.toml
M src/error.rs
M src/lib.rs
M src/object.rs
M src/read.rs
A src/resolver.rs
M Cargo.toml => Cargo.toml +1 -2
@@ 10,8 10,7 @@ license       = "MPL-2.0"
repository    = "https://github.com/matthiasbeyer/serde-select"

[dependencies]
failure        = "0.1"
failure_derive = "0.1"
thiserror      = "1"
serde          = "1"
regex          = "1.0"
log            = "0.4"

M src/error.rs => src/error.rs +24 -26
@@ 1,69 1,67 @@
/// Error types

use thiserror::Error;

pub type Result<T> = ::std::result::Result<T, Error>;

#[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<String>),

    #[fail(display = "Cannot delete array '{:?}' which is not empty", _0)]
    #[error("Cannot delete array '{:?}' which is not empty", _0)]
    CannotDeleteNonEmptyArray(Option<String>),

    #[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),
}

M src/lib.rs => src/lib.rs +2 -2
@@ 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;

M src/object.rs => src/object.rs +22 -3
@@ 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<Option<&'a mut Self>>;
    fn at_key<'a>(&'a self, key: &str) -> Result<Option<&'a Self>>;
    fn at_key_mut<'a>(&'a mut self, key: &str) -> Result<Option<&'a mut Self>>;

    fn array_len(&self) -> Option<usize>;
}

#[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<usize> {
        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<usize> {
        match self {
            serde_json::Value::Array(a) => Some(a.len()),
            _ => None ,
        }
    }
}


M src/read.rs => src/read.rs +8 -41
@@ 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<Option<&'doc Self>> {
        query_with_token(self, query.token())
    }

    fn read_mut(&'doc mut self, query: &Query) -> Result<Option<&'doc mut Self>> {
        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<Option<&'doc O>>
    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<Option<&'doc
    }
}

fn query_with_token_mut<'doc, O>(obj: &'doc mut O, token: &Token) -> Result<Option<&'doc mut O>>
    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)
            }
        }
    }
}




A src/resolver.rs => src/resolver.rs +613 -0
@@ 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<Option<&'doc D>>
    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 { .. }));
    }
}