use crate::rules::errors::Error; use crate::rules::path_value::PathAwareValue; use fancy_regex::Regex; use lazy_static::lazy_static; use std::collections::BTreeMap; lazy_static! { static ref RELATIVE_PATH: Regex = Regex::new(r"^(\d+)(#|/.*)").ok().unwrap(); } #[derive(Clone, Debug, PartialEq)] pub(crate) struct Traversal<'value> { nodes: BTreeMap<&'value str, Node<'value>>, } #[derive(Clone, Debug, PartialEq)] pub(crate) struct Node<'value> { parent: Option<&'value str>, value: &'value PathAwareValue, } impl<'value> Node<'value> { pub(crate) fn parent(&self) -> Option<&str> { self.parent } pub(crate) fn value(&self) -> &PathAwareValue { self.value } } #[derive(Clone, Debug, PartialEq)] pub(crate) enum TraversalResult<'a, 'b> { Value(&'b Node<'a>), Key(&'a str), } #[cfg(test)] impl<'a, 'b> TraversalResult<'a, 'b> { pub(crate) fn as_value(&self) -> Option<&Node<'_>> { match self { Self::Value(n) => Some(n), _ => None, } } } fn from_value<'value>( current: &'value PathAwareValue, parent: Option<&'value str>, nodes: &mut BTreeMap<&'value str, Node<'value>>, ) { match current { PathAwareValue::Null(path) | PathAwareValue::String((path, _)) | PathAwareValue::Regex((path, _)) | PathAwareValue::Bool((path, _)) | PathAwareValue::Int((path, _)) | PathAwareValue::Float((path, _)) | PathAwareValue::RangeInt((path, _)) | PathAwareValue::RangeFloat((path, _)) | PathAwareValue::RangeChar((path, _)) | PathAwareValue::Char((path, _)) => { nodes.insert( &path.0, Node { parent, value: current, }, ); } PathAwareValue::Map((path, map)) => { nodes.insert( &path.0, Node { value: current, parent, }, ); let parent = Some(path.0.as_str()); for (_key, each) in map.values.iter() { from_value(each, parent, nodes); } } PathAwareValue::List((path, list)) => { nodes.insert( &path.0, Node { value: current, parent, }, ); let parent = Some(path.0.as_str()); for each in list.iter() { from_value(each, parent, nodes); } } } } impl<'value> Traversal<'value> { pub(crate) fn root(&self) -> Option<&Node> { self.nodes.get("/") } pub(crate) fn at<'traverse>( &'traverse self, pointer: &str, node: &'traverse Node, ) -> crate::rules::Result { if pointer.is_empty() || pointer == "0" { return Ok(TraversalResult::Value(node)); } if pointer == "0#" { return Ok(TraversalResult::Key(node.value.self_path().relative())); } if let Ok(Some(captures)) = RELATIVE_PATH.captures(pointer) { // // Safe to unwrap as we capture ints in regex // let num = captures.get(1).unwrap().as_str().parse::().unwrap(); let mut ancestor = 0; let mut current = node; while ancestor < num { match current.parent() { Some(prev) => { current = match self.nodes.get(prev) { Some(node) => node, None => { return Err(Error::RetrievalError(format!( "No ancestors found at path {}, current value at {}", prev, current.value.self_path() ))) } } } None => { return Err(Error::RetrievalError(format!( "No more ancestors found. Path {} pointing to beyond root", pointer ))) } } ancestor += 1; } match captures.get(2) { Some(ch) => { let p = ch.as_str(); if p == "#" { return Ok(TraversalResult::Key(current.value.self_path().relative())); } let pointer = format!("{}{}", current.value.self_path().0, p); return self.at(&pointer, current); } None => return Ok(TraversalResult::Value(current)), } } match self.nodes.get(pointer) { Some(node) => Ok(TraversalResult::Value(node)), None => Err(Error::RetrievalError(format!( "Path {} did not yield value. Current Path {}, expected sub-paths {:?}", pointer, node.value().self_path().0, self.nodes.range(pointer..) ))), } } } impl<'v> From<&'v PathAwareValue> for Traversal<'v> { fn from(root: &'v PathAwareValue) -> Self { let mut nodes = BTreeMap::new(); from_value(root, None, &mut nodes); nodes.insert( "/", Node { value: root, parent: None, }, ); Traversal { nodes } } } #[cfg(test)] #[path = "traversal_tests.rs"] mod traversal_tests;