diff --git a/src/bin.rs b/src/bin.rs index bb1c1f1..12e1bd3 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -8,6 +8,7 @@ use renderer::DustRenderer; use renderer::Loopable; use renderer::RenderError; use renderer::Renderable; +use renderer::WalkError; use renderer::Walkable; use std::env; use std::fs; @@ -91,67 +92,48 @@ impl Renderable for serde_json::Value { } impl Walkable for serde_json::Value { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { match self { - serde_json::Value::Null => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::Bool(_boolean) => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::Number(_num) => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::String(_string) => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::Array(_arr) => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::Object(obj) => { - obj.get(segment) - .map(|val| val as _) - .ok_or(RenderError::WontWalk { - segment: segment.to_string(), - elem: self, - }) - } + serde_json::Value::Null => Err(WalkError::CantWalk), + serde_json::Value::Bool(_boolean) => Err(WalkError::CantWalk), + serde_json::Value::Number(_num) => Err(WalkError::CantWalk), + serde_json::Value::String(_string) => Err(WalkError::CantWalk), + serde_json::Value::Array(_arr) => Err(WalkError::CantWalk), + serde_json::Value::Object(obj) => obj + .get(segment) + .map(|val| val as _) + .ok_or(WalkError::CantWalk), } } } impl Loopable for serde_json::Value { - fn get_loop_elements(&self) -> Result, RenderError> { + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { match self { - serde_json::Value::Null => Ok(Vec::new()), + serde_json::Value::Null => Vec::new(), serde_json::Value::Bool(boolean) => { if *boolean { - Ok(vec![self]) + vec![self] } else { - Ok(Vec::new()) + Vec::new() } } - serde_json::Value::Number(_num) => Ok(vec![self]), + serde_json::Value::Number(_num) => vec![self], serde_json::Value::String(string_value) => { if string_value.is_empty() { - Ok(Vec::new()) + Vec::new() } else { - Ok(vec![self]) + vec![self] } } serde_json::Value::Array(array_value) => { if array_value.is_empty() { - Ok(Vec::new()) + Vec::new() } else { - Ok(array_value.iter().map(|x| x as _).collect()) + array_value.iter().map(|x| x as _).collect() } } - serde_json::Value::Object(_obj) => Ok(vec![self]), + serde_json::Value::Object(_obj) => vec![self], } } } diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index 14f1aae..dee6680 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -1,11 +1,12 @@ use crate::parser::Filter; use crate::renderer::errors::RenderError; +use crate::renderer::errors::WalkError; use std::fmt::Debug; pub trait ContextElement: Debug + Walkable + Renderable + Loopable {} pub trait Walkable { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError>; + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>; } pub trait Renderable { @@ -22,5 +23,5 @@ pub trait Loopable { /// once with the context being the element at that path. Finally, /// if its an array-like value then it will render n-times, once /// for each element of the array. - fn get_loop_elements(&self) -> Result, RenderError>; + fn get_loop_elements(&self) -> Vec<&dyn ContextElement>; } diff --git a/src/renderer/errors.rs b/src/renderer/errors.rs index 8af8bb1..5a38068 100644 --- a/src/renderer/errors.rs +++ b/src/renderer/errors.rs @@ -1,27 +1,16 @@ -use crate::renderer::context_element::ContextElement; use std::error; use std::fmt; -pub enum RenderError<'a> { +/// Fatal errors while rendering. +/// +/// A RenderError will halt rendering. +pub enum RenderError { Generic(String), - /// For when walking is absolutely impossible - CantWalk { - segment: String, - elem: &'a dyn ContextElement, - }, - /// For when walking fails (example, a missing key on a map) - WontWalk { - segment: String, - elem: &'a dyn ContextElement, - }, - NotFound { - path: &'a Vec<&'a str>, - breadcrumbs: Vec<&'a dyn ContextElement>, - }, - /// Attempting to render and unrenderable type (for example, an object without any filters) - CantRender { - elem: &'a dyn ContextElement, - }, + TemplateNotFound(String), +} + +pub enum WalkError { + CantWalk, } #[derive(Clone)] @@ -29,43 +18,51 @@ pub struct CompileError { pub message: String, } -impl fmt::Display for RenderError<'_> { +impl fmt::Display for RenderError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RenderError::Generic(msg) => write!(f, "{}", msg), - RenderError::CantWalk { segment, elem } => { - write!(f, "Tried to walk to {} from {:?}", segment, elem) - } - RenderError::WontWalk { segment, elem } => { - write!(f, "Failed to walk to {} from {:?}", segment, elem) - } - RenderError::CantRender { elem } => write!(f, "Cant render {:?}", elem), - RenderError::NotFound { path, breadcrumbs } => { - write!(f, "Could not find {:?} in {:?}", path, breadcrumbs) + RenderError::TemplateNotFound(name) => { + write!(f, "No template named {} in context", name) } } } } -impl fmt::Debug for RenderError<'_> { +impl fmt::Debug for RenderError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RenderError::Generic(msg) => write!(f, "{}", msg), - RenderError::CantWalk { segment, elem } => { - write!(f, "Tried to walk to {} from {:?}", segment, elem) - } - RenderError::WontWalk { segment, elem } => { - write!(f, "Failed to walk to {} from {:?}", segment, elem) - } - RenderError::CantRender { elem } => write!(f, "Cant render {:?}", elem), - RenderError::NotFound { path, breadcrumbs } => { - write!(f, "Could not find {:?} in {:?}", path, breadcrumbs) + RenderError::TemplateNotFound(name) => { + write!(f, "No template named {} in context", name) } } } } -impl error::Error for RenderError<'_> { +impl error::Error for RenderError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + None + } +} + +impl fmt::Display for WalkError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WalkError::CantWalk => write!(f, "Failed to walk"), + } + } +} + +impl fmt::Debug for WalkError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WalkError::CantWalk => write!(f, "Failed to walk"), + } + } +} + +impl error::Error for WalkError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { None } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5c86aab..6d0c041 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -11,6 +11,7 @@ pub use context_element::Renderable; pub use context_element::Walkable; pub use errors::CompileError; pub use errors::RenderError; +pub use errors::WalkError; pub use renderer::compile_template; pub use renderer::CompiledTemplate; pub use renderer::DustRenderer; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 5a9164d..562933a 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -4,6 +4,7 @@ use crate::renderer::context_element::ContextElement; use crate::renderer::Loopable; use crate::renderer::RenderError; use crate::renderer::Renderable; +use crate::renderer::WalkError; use crate::renderer::Walkable; use std::collections::HashMap; @@ -38,17 +39,15 @@ impl<'a> Renderable for ParametersContext<'a> { } impl<'a> Loopable for ParametersContext<'a> { - fn get_loop_elements(&self) -> Result, RenderError> { - Ok(vec![self]) + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { + // TODO: Would this even ever be called? Won't matter, but I'd like to know. + vec![self] } } impl<'a> Walkable for ParametersContext<'a> { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { // TODO: Actually implement - Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }) + Err(WalkError::CantWalk) } } diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 1e18a8a..1b4314b 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -7,6 +7,7 @@ use crate::parser::TemplateElement; use crate::renderer::context_element::ContextElement; use crate::renderer::errors::CompileError; use crate::renderer::errors::RenderError; +use crate::renderer::errors::WalkError; use crate::renderer::parameters_context::ParametersContext; use std::collections::HashMap; @@ -50,14 +51,11 @@ impl<'a> DustRenderer<'a> { &'a self, name: &str, breadcrumbs: &Vec<&'a dyn ContextElement>, - ) -> Result> { + ) -> Result { let main_template = match self.templates.get(name) { Some(tmpl) => tmpl, None => { - return Err(RenderError::Generic(format!( - "No template named {} in context", - name - ))); + return Err(RenderError::TemplateNotFound(name.to_owned())); } }; self.render_body(&main_template.contents, breadcrumbs) @@ -67,7 +65,7 @@ impl<'a> DustRenderer<'a> { &'a self, body: &'a Body, breadcrumbs: &Vec<&'a dyn ContextElement>, - ) -> Result> { + ) -> Result { let mut output = String::new(); for elem in &body.elements { match elem { @@ -85,7 +83,7 @@ impl<'a> DustRenderer<'a> { &'a self, tag: &'a DustTag, breadcrumbs: &Vec<&'a dyn ContextElement>, - ) -> Result> { + ) -> Result { match tag { DustTag::DTComment(_comment) => (), DustTag::DTSpecial(special) => { @@ -101,21 +99,20 @@ impl<'a> DustRenderer<'a> { DustTag::DTReference(reference) => { let val = walk_path(breadcrumbs, &reference.path.keys); match val { - Err(RenderError::NotFound { .. }) => return Ok("".to_owned()), + Err(WalkError::CantWalk) => return Ok("".to_owned()), Ok(final_val) => { - let loop_elements = final_val.get_loop_elements()?; + let loop_elements = final_val.get_loop_elements(); if loop_elements.is_empty() { return Ok("".to_owned()); } else { return final_val.render(&reference.filters); } } - Err(render_error) => return Err(render_error), } } DustTag::DTSection(container) => { let val = walk_path(breadcrumbs, &container.path.keys); - let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val)?; + let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val); if loop_elements.is_empty() { // Oddly enough if the value is falsey (like // an empty array or null), Dust uses the @@ -145,7 +142,7 @@ impl<'a> DustRenderer<'a> { } DustTag::DTExists(container) => { let val = walk_path(breadcrumbs, &container.path.keys); - let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val)?; + let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val); if loop_elements.is_empty() { return match &container.else_contents { Some(body) => self.render_body(&body, breadcrumbs), @@ -160,7 +157,7 @@ impl<'a> DustRenderer<'a> { } DustTag::DTNotExists(container) => { let val = walk_path(breadcrumbs, &container.path.keys); - let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val)?; + let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val); if !loop_elements.is_empty() { return match &container.else_contents { Some(body) => self.render_body(&body, breadcrumbs), @@ -196,13 +193,11 @@ impl<'a> DustRenderer<'a> { /// block, this will return an empty vector. fn get_loop_elements<'b>( &'a self, - walk_result: Result<&'b dyn ContextElement, RenderError<'b>>, - ) -> Result, RenderError<'b>> { - if let Err(RenderError::NotFound { .. }) = walk_result { - // If reference does not exist in the context, render the else block - Ok(vec![]) - } else { - Ok(walk_result?.get_loop_elements()?) + walk_result: Result<&'b dyn ContextElement, WalkError>, + ) -> Vec<&'b dyn ContextElement> { + match walk_result { + Err(WalkError::CantWalk) => Vec::new(), + Ok(walk_target) => walk_target.get_loop_elements(), } } } @@ -216,50 +211,46 @@ enum WalkResult<'a> { fn walk_path_from_single_level<'a>( context: &'a dyn ContextElement, path: &Vec<&str>, -) -> Result, RenderError<'a>> { +) -> WalkResult<'a> { if path.is_empty() { - return Ok(WalkResult::FullyWalked(context)); + return WalkResult::FullyWalked(context); } let mut walk_failure = WalkResult::NoWalk; let mut output = context; for elem in path.iter() { let new_val = output.walk(elem); - if let Err(RenderError::WontWalk { .. }) = new_val { - return Ok(walk_failure); - } else if let Err(RenderError::CantWalk { .. }) = new_val { - return Ok(walk_failure); + match output.walk(elem) { + Err(WalkError::CantWalk { .. }) => { + return walk_failure; + } + Ok(new_val) => { + walk_failure = WalkResult::PartialWalk; + output = new_val; + } } - walk_failure = WalkResult::PartialWalk; - output = new_val?; } - Ok(WalkResult::FullyWalked(output)) + WalkResult::FullyWalked(output) } fn walk_path<'a>( breadcrumbs: &Vec<&'a dyn ContextElement>, path: &'a Vec<&str>, -) -> Result<&'a dyn ContextElement, RenderError<'a>> { +) -> Result<&'a dyn ContextElement, WalkError> { for context in breadcrumbs.iter().rev() { - match walk_path_from_single_level(*context, path)? { + match walk_path_from_single_level(*context, path) { // If no walking was done at all, keep looping WalkResult::NoWalk => {} // If we partially walked then stop trying to find // anything WalkResult::PartialWalk => { - return Err(RenderError::NotFound { - path: path, - breadcrumbs: breadcrumbs.clone(), - }); + return Err(WalkError::CantWalk); } WalkResult::FullyWalked(new_context) => return Ok(new_context), } } - Err(RenderError::NotFound { - path: path, - breadcrumbs: breadcrumbs.clone(), - }) + Err(WalkError::CantWalk) } #[cfg(test)] @@ -291,57 +282,48 @@ mod tests { impl Renderable for HashMap<&str, I> { fn render(&self, _filters: &Vec) -> Result { // TODO: handle the filters - Err(RenderError::CantRender { elem: self }) + Ok("[object Object]".to_owned()) } } impl Walkable for HashMap<&str, I> { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { - let child = self.get(segment).ok_or(RenderError::WontWalk { - segment: segment.to_string(), - elem: self, - })?; + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { + let child = self.get(segment).ok_or(WalkError::CantWalk)?; Ok(child) } } impl Walkable for &str { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { - Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }) + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { + Err(WalkError::CantWalk) } } impl Walkable for u32 { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { - Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }) + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { + Err(WalkError::CantWalk) } } impl Loopable for &str { - fn get_loop_elements(&self) -> Result, RenderError> { + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { if self.is_empty() { - Ok(Vec::new()) + Vec::new() } else { - Ok(vec![self]) + vec![self] } } } impl Loopable for u32 { - fn get_loop_elements(&self) -> Result, RenderError> { - Ok(vec![self]) + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { + vec![self] } } impl Loopable for HashMap<&str, I> { - fn get_loop_elements(&self) -> Result, RenderError> { - Ok(vec![self]) + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { + vec![self] } }