use crate::parser::KVPair; use crate::parser::{Filter, RValue}; use crate::renderer::context_element::CompareContextElement; use crate::renderer::context_element::ContextElement; use crate::renderer::walking::owned_walk_path; use crate::renderer::Loopable; use crate::renderer::RenderError; use crate::renderer::Renderable; use crate::renderer::WalkError; use crate::renderer::Walkable; use std::collections::HashMap; /// Copy the data from an RValue to an Owned struct /// /// In order to get comparisons to work for our `ContextElement` trait /// objects, we need to be able to use `std::any::Any`. Unfortunately, /// `Any` requires that the structs do not have a lifetime (so they /// will have a `'static` lifetime. This means that we cannot have a /// `<'a>` appended to the struct type, so the struct cannot contain /// any borrows. Rather than impose the copy cost in the parser, we /// are imposing the cost of copying the data in the renderer because /// the parser has no reason to not be able to reference data from the /// input string. #[derive(Clone, Debug, PartialEq)] pub enum OwnedRValue { RVPath(OwnedPath), RVString(String), } #[derive(Clone, Debug, PartialEq)] pub struct OwnedPath { pub keys: Vec, } impl From<&RValue<'_>> for OwnedRValue { fn from(original: &RValue<'_>) -> Self { match original { RValue::RVString(text) => OwnedRValue::RVString(text.to_owned()), RValue::RVPath(path) => OwnedRValue::RVPath(OwnedPath { keys: path.keys.iter().map(|k| k.to_string()).collect(), }), } } } #[derive(Debug)] pub struct ParametersContext { params: HashMap, breadcrumbs: Vec>, } impl ParametersContext { pub fn new(breadcrumbs: &Vec<&dyn ContextElement>, params: &Vec) -> ParametersContext { let owned_params: HashMap = params .iter() .map(|kvpair| (kvpair.key.to_string(), OwnedRValue::from(&kvpair.value))) .collect(); let owned_breadcrumbs: Vec> = breadcrumbs.iter().map(|ce| ce.clone_to_box()).collect(); ParametersContext { params: owned_params, breadcrumbs: owned_breadcrumbs, } } } impl ContextElement for ParametersContext {} impl Renderable for ParametersContext { fn render(&self, _filters: &Vec) -> Result { // TODO: Would this even ever be called? Won't matter, but I'd // like to know. Since it is injected 1 above the current // context, we wouldn't be able to access it with `{.}`. Ok("[object Object]".to_owned()) } } impl Loopable for ParametersContext { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { // TODO: Would this even ever be called? Won't matter, but I'd // like to know. Since it is injected 1 above the current // context, we wouldn't be able to access it with `{.}`. vec![self] } } impl Walkable for ParametersContext { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { let rval = self.params.get(segment).ok_or(WalkError::CantWalk)?; match rval { OwnedRValue::RVPath(path) => owned_walk_path(&self.breadcrumbs, &path.keys), OwnedRValue::RVString(text) => Ok(text), } } } impl Clone for ParametersContext { fn clone(&self) -> Self { let new_params: HashMap = self .params .iter() .map(|(k, v)| (k.clone(), v.clone())) .collect(); let new_breadcrumbs: Vec> = self .breadcrumbs .iter() .map(|bread| bread.clone_to_box()) .collect(); ParametersContext { params: new_params, breadcrumbs: new_breadcrumbs, } } } impl CompareContextElement for ParametersContext { fn equals(&self, other: &dyn ContextElement) -> bool { // TODO: Does this ever happen? perhaps I should have a panic here. false } } impl ContextElement for String {} impl Renderable for String { fn render(&self, _filters: &Vec) -> Result { Ok(self.clone()) } } impl Loopable for String { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { if self.is_empty() { Vec::new() } else { vec![self] } } } impl Walkable for String { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { Err(WalkError::CantWalk) } } impl CompareContextElement for String { fn equals(&self, other: &dyn ContextElement) -> bool { // If its a String then compare them directly, otherwise defer // to the other type's implementation of CompareContextElement // since the end user could add any type. match other.to_any().downcast_ref::() { None => other.equals(self), Some(other_string) => self == other_string, } } }