path-value 0.1.5

Universal type and access property(s) by path
Documentation
use parser::PathParser;
use std::convert::TryFrom;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;

use crate::error::*;

mod parser {
    use pest::Parser;

    use crate::error::*;

    use super::{Path, PathNode};

    #[derive(Parser)]
    #[grammar = "path/path.pest"]
    pub struct PathParser;

    impl PathParser {
        pub fn parse_to_path(s: &str) -> Result<Path> {
            let mut result: Vec<PathNode> = Vec::new();
            let path = PathParser::parse(Rule::path, s)
                .map_err(|e| Error::path_parse(e, s))?
                .next()
                .unwrap();
            for sub_path in path.into_inner() {
                if let Some(ident) = sub_path.into_inner().next() {
                    match ident.as_rule() {
                        Rule::path_ident => {
                            result.push(PathNode::Identifier(ident.as_str().to_string()))
                        }
                        Rule::path_index_ident => {
                            let mut path_index_ident_inner = ident.into_inner();
                            result.push(PathNode::Identifier(
                                path_index_ident_inner.next().unwrap().as_str().to_string(),
                            ));
                            result.push(PathNode::Index(
                                path_index_ident_inner
                                    .next()
                                    .unwrap()
                                    .as_str()
                                    .parse()
                                    .unwrap(),
                            ));
                        }
                        _ => unreachable!(),
                    };
                }
            }

            Ok(Path(result))
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum PathNode {
    Identifier(String),
    Index(isize),
}

#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct Path(Vec<PathNode>);

impl Default for Path {
    fn default() -> Self {
        Path(Vec::new())
    }
}

impl Deref for Path {
    type Target = Vec<PathNode>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Path {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl FromStr for Path {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        PathParser::parse_to_path(s)
    }
}

impl TryFrom<String> for Path {
    type Error = Error;

    fn try_from(value: String) -> Result<Self> {
        PathParser::parse_to_path(&value)
    }
}

impl<'a> TryFrom<&'a str> for Path {
    type Error = Error;

    fn try_from(value: &'a str) -> Result<Self> {
        PathParser::parse_to_path(value)
    }
}

#[allow(unused_imports)]
mod tests {
    use super::{Path, PathNode};

    #[test]
    fn test_empty() {
        let parsed = "".parse::<Path>();
        assert!(matches!(parsed, Err(err)));
    }

    #[test]
    fn test_root() {
        let except_path = Path(vec![]);
        let parsed = "/".parse::<Path>();
        assert!(matches!(parsed, Ok(path) if path == except_path ));
    }

    #[test]
    fn test_1_level() {
        let except_path = Path(vec![PathNode::Identifier("a".to_string())]);
        let parsed = "/a".parse::<Path>();
        assert!(matches!(parsed, Ok(path) if path == except_path ));
    }

    #[test]
    fn test_n_level() {
        let except_path = Path(vec![
            PathNode::Identifier("a".to_string()),
            PathNode::Identifier("b".to_string()),
            PathNode::Identifier("c".to_string()),
        ]);
        let parsed = "/a/b/c".parse::<Path>();
        assert!(matches!(parsed, Ok(path) if path == except_path ));
    }

    #[test]
    fn test_1_level_index() {
        let except_path = Path(vec![
            PathNode::Identifier("a".to_string()),
            PathNode::Index(0),
        ]);
        let parsed = "/a[0]".parse::<Path>();
        assert!(matches!(parsed, Ok(path) if path == except_path ));
    }

    #[test]
    fn test_n_level_index() {
        let except_path = Path(vec![
            PathNode::Identifier("a".to_string()),
            PathNode::Index(0),
            PathNode::Identifier("b".to_string()),
            PathNode::Identifier("c".to_string()),
            PathNode::Index(1),
        ]);
        let parsed = "/a[0]/b/c[1]".parse::<Path>();
        assert!(matches!(parsed, Ok(path) if path == except_path ));
    }
}