use std::collections::{HashMap, VecDeque}; use serde::Serialize; use serde_json::value::{to_value, Map, Value as Json}; use crate::block::{BlockContext, BlockParamHolder}; use crate::error::RenderError; use crate::grammar::Rule; use crate::json::path::*; use crate::json::value::ScopedJson; use crate::util::extend; pub type Object = HashMap; /// The context wrap data you render on your templates. /// #[derive(Debug, Clone)] pub struct Context { data: Json, } #[derive(Debug)] enum ResolvedPath<'a> { // FIXME: change to borrowed when possible // full path AbsolutePath(Vec), // relative path and path root RelativePath(Vec), // relative path against block param value BlockParamValue(Vec, &'a Json), // relative path against derived value, LocalValue(Vec, &'a Json), } fn parse_json_visitor<'a, 'reg>( relative_path: &[PathSeg], block_contexts: &'a VecDeque>, always_for_absolute_path: bool, ) -> ResolvedPath<'a> { let mut path_context_depth: i64 = 0; let mut with_block_param = None; let mut from_root = false; // peek relative_path for block param, @root and "../../" for path_seg in relative_path { match path_seg { PathSeg::Named(the_path) => { if let Some((holder, base_path)) = get_in_block_params(&block_contexts, the_path) { with_block_param = Some((holder, base_path)); } break; } PathSeg::Ruled(the_rule) => match the_rule { Rule::path_root => { from_root = true; break; } Rule::path_up => path_context_depth += 1, _ => break, }, } } let mut path_stack = Vec::with_capacity(relative_path.len() + 5); match with_block_param { Some((BlockParamHolder::Value(ref value), _)) => { merge_json_path(&mut path_stack, &relative_path[1..]); ResolvedPath::BlockParamValue(path_stack, value) } Some((BlockParamHolder::Path(ref paths), base_path)) => { extend(&mut path_stack, base_path); if !paths.is_empty() { extend(&mut path_stack, paths); } merge_json_path(&mut path_stack, &relative_path[1..]); ResolvedPath::AbsolutePath(path_stack) } None => { if path_context_depth > 0 { let blk = block_contexts .get(path_context_depth as usize) .or_else(|| block_contexts.front()); if let Some(base_value) = blk.and_then(|blk| blk.base_value()) { merge_json_path(&mut path_stack, relative_path); ResolvedPath::LocalValue(path_stack, base_value) } else { if let Some(base_path) = blk.map(|blk| blk.base_path()) { extend(&mut path_stack, base_path); } merge_json_path(&mut path_stack, relative_path); ResolvedPath::AbsolutePath(path_stack) } } else if from_root { merge_json_path(&mut path_stack, relative_path); ResolvedPath::AbsolutePath(path_stack) } else if always_for_absolute_path { if let Some(base_value) = block_contexts.front().and_then(|blk| blk.base_value()) { merge_json_path(&mut path_stack, relative_path); ResolvedPath::LocalValue(path_stack, base_value) } else { if let Some(base_path) = block_contexts.front().map(|blk| blk.base_path()) { extend(&mut path_stack, base_path); } merge_json_path(&mut path_stack, relative_path); ResolvedPath::AbsolutePath(path_stack) } } else { merge_json_path(&mut path_stack, relative_path); ResolvedPath::RelativePath(path_stack) } } } } fn get_data<'a>(d: Option<&'a Json>, p: &str) -> Result, RenderError> { let result = match d { Some(&Json::Array(ref l)) => p.parse::().map(|idx_u| l.get(idx_u))?, Some(&Json::Object(ref m)) => m.get(p), Some(_) => None, None => None, }; Ok(result) } fn get_in_block_params<'a, 'reg>( block_contexts: &'a VecDeque>, p: &str, ) -> Option<(&'a BlockParamHolder, &'a Vec)> { for bc in block_contexts { let v = bc.get_block_param(p); if v.is_some() { return v.map(|v| (v, bc.base_path())); } } None } pub(crate) fn merge_json(base: &Json, addition: &HashMap<&str, &Json>) -> Json { let mut base_map = match base { Json::Object(ref m) => m.clone(), _ => Map::new(), }; for (k, v) in addition.iter() { base_map.insert(k.to_string(), (*v).clone()); } Json::Object(base_map) } impl Context { /// Create a context with null data pub fn null() -> Context { Context { data: Json::Null } } /// Create a context with given data pub fn wraps(e: T) -> Result { to_value(e) .map_err(RenderError::from) .map(|d| Context { data: d }) } /// Navigate the context with relative path and block scopes pub(crate) fn navigate<'reg, 'rc>( &'rc self, relative_path: &[PathSeg], block_contexts: &VecDeque>, ) -> Result, RenderError> { // always use absolute at the moment until we get base_value lifetime issue fixed let resolved_visitor = parse_json_visitor(&relative_path, block_contexts, true); // debug logging debug!("Accessing context value: {:?}", resolved_visitor); match resolved_visitor { ResolvedPath::AbsolutePath(paths) => { let mut ptr = Some(self.data()); for p in paths.iter() { ptr = get_data(ptr, p)?; } Ok(ptr .map(|v| ScopedJson::Context(v, paths)) .unwrap_or_else(|| ScopedJson::Missing)) } ResolvedPath::RelativePath(_paths) => { // relative path is disabled for now unreachable!() // let mut ptr = block_contexts.front().and_then(|blk| blk.base_value()); // for p in paths.iter() { // ptr = get_data(ptr, p)?; // } // Ok(ptr // .map(|v| ScopedJson::Context(v, paths)) // .unwrap_or_else(|| ScopedJson::Missing)) } ResolvedPath::BlockParamValue(paths, value) | ResolvedPath::LocalValue(paths, value) => { let mut ptr = Some(value); for p in paths.iter() { ptr = get_data(ptr, p)?; } Ok(ptr .map(|v| ScopedJson::Derived(v.clone())) .unwrap_or_else(|| ScopedJson::Missing)) } } } /// Return the Json data wrapped in context pub fn data(&self) -> &Json { &self.data } /// Return the mutable reference to Json data wrapped in context pub fn data_mut(&mut self) -> &mut Json { &mut self.data } } #[cfg(test)] mod test { use crate::block::{BlockContext, BlockParams}; use crate::context::{self, Context}; use crate::error::RenderError; use crate::json::path::Path; use crate::json::value::{self, ScopedJson}; use serde_json::value::Map; use std::collections::{HashMap, VecDeque}; fn navigate_from_root<'reg, 'rc>( ctx: &'rc Context, path: &str, ) -> Result, RenderError> { let relative_path = Path::parse(path).unwrap(); ctx.navigate(relative_path.segs().unwrap(), &VecDeque::new()) } #[derive(Serialize)] struct Address { city: String, country: String, } #[derive(Serialize)] struct Person { name: String, age: i16, addr: Address, titles: Vec, } #[test] fn test_render() { let v = "hello"; let ctx = Context::wraps(&v.to_string()).unwrap(); assert_eq!( navigate_from_root(&ctx, "this").unwrap().render(), v.to_string() ); } #[test] fn test_navigation() { let addr = Address { city: "Beijing".to_string(), country: "China".to_string(), }; let person = Person { name: "Ning Sun".to_string(), age: 27, addr, titles: vec!["programmer".to_string(), "cartographer".to_string()], }; let ctx = Context::wraps(&person).unwrap(); assert_eq!( navigate_from_root(&ctx, "./addr/country").unwrap().render(), "China".to_string() ); assert_eq!( navigate_from_root(&ctx, "addr.[country]").unwrap().render(), "China".to_string() ); let v = true; let ctx2 = Context::wraps(&v).unwrap(); assert_eq!( navigate_from_root(&ctx2, "this").unwrap().render(), "true".to_string() ); assert_eq!( navigate_from_root(&ctx, "titles.[0]").unwrap().render(), "programmer".to_string() ); assert_eq!( navigate_from_root(&ctx, "age").unwrap().render(), "27".to_string() ); } #[test] fn test_this() { let mut map_with_this = Map::new(); map_with_this.insert("this".to_string(), value::to_json("hello")); map_with_this.insert("age".to_string(), value::to_json(5usize)); let ctx1 = Context::wraps(&map_with_this).unwrap(); let mut map_without_this = Map::new(); map_without_this.insert("age".to_string(), value::to_json(4usize)); let ctx2 = Context::wraps(&map_without_this).unwrap(); assert_eq!( navigate_from_root(&ctx1, "this").unwrap().render(), "[object]".to_owned() ); assert_eq!( navigate_from_root(&ctx2, "age").unwrap().render(), "4".to_owned() ); } #[test] fn test_merge_json() { let map = json!({ "age": 4 }); let s = "hello".to_owned(); let mut hash = HashMap::new(); let v = value::to_json("h1"); hash.insert("tag", &v); let ctx_a1 = Context::wraps(&context::merge_json(&map, &hash)).unwrap(); assert_eq!( navigate_from_root(&ctx_a1, "age").unwrap().render(), "4".to_owned() ); assert_eq!( navigate_from_root(&ctx_a1, "tag").unwrap().render(), "h1".to_owned() ); let ctx_a2 = Context::wraps(&context::merge_json(&value::to_json(s), &hash)).unwrap(); assert_eq!( navigate_from_root(&ctx_a2, "this").unwrap().render(), "[object]".to_owned() ); assert_eq!( navigate_from_root(&ctx_a2, "tag").unwrap().render(), "h1".to_owned() ); } #[test] fn test_key_name_with_this() { let m = btreemap! { "this_name".to_string() => "the_value".to_string() }; let ctx = Context::wraps(&m).unwrap(); assert_eq!( navigate_from_root(&ctx, "this_name").unwrap().render(), "the_value".to_string() ); } use serde::ser::Error as SerdeError; use serde::{Serialize, Serializer}; struct UnserializableType {} impl Serialize for UnserializableType { fn serialize(&self, _: S) -> Result where S: Serializer, { Err(SerdeError::custom("test")) } } #[test] fn test_serialize_error() { let d = UnserializableType {}; assert!(Context::wraps(&d).is_err()); } #[test] fn test_root() { let m = json!({ "a" : { "b" : { "c" : { "d" : 1 } } }, "b": 2 }); let ctx = Context::wraps(&m).unwrap(); let mut block = BlockContext::new(); *block.base_path_mut() = ["a".to_owned(), "b".to_owned()].to_vec(); let mut blocks = VecDeque::new(); blocks.push_front(block); assert_eq!( ctx.navigate(&Path::parse("@root/b").unwrap().segs().unwrap(), &blocks) .unwrap() .render(), "2".to_string() ); } #[test] fn test_block_params() { let m = json!([{ "a": [1, 2] }, { "b": [2, 3] }]); let ctx = Context::wraps(&m).unwrap(); let mut block_params = BlockParams::new(); block_params .add_path("z", ["0".to_owned(), "a".to_owned()].to_vec()) .unwrap(); block_params.add_value("t", json!("good")).unwrap(); let mut block = BlockContext::new(); block.set_block_params(block_params); let mut blocks = VecDeque::new(); blocks.push_front(block); assert_eq!( ctx.navigate(&Path::parse("z.[1]").unwrap().segs().unwrap(), &blocks) .unwrap() .render(), "2".to_string() ); assert_eq!( ctx.navigate(&Path::parse("t").unwrap().segs().unwrap(), &blocks) .unwrap() .render(), "good".to_string() ); } }