Merge branch 'rework_errors' into partial
This commit is contained in:
commit
ad498abb74
60
src/bin.rs
60
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<Vec<&dyn ContextElement>, 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],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Vec<&dyn ContextElement>, RenderError>;
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement>;
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<Vec<&dyn ContextElement>, 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)
|
||||
}
|
||||
}
|
||||
|
@ -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<String, RenderError<'a>> {
|
||||
) -> Result<String, RenderError> {
|
||||
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<String, RenderError<'a>> {
|
||||
) -> Result<String, RenderError> {
|
||||
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<String, RenderError<'a>> {
|
||||
) -> Result<String, RenderError> {
|
||||
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<Vec<&'b dyn ContextElement>, 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<WalkResult<'a>, 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<I: ContextElement> Renderable for HashMap<&str, I> {
|
||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||
// TODO: handle the filters
|
||||
Err(RenderError::CantRender { elem: self })
|
||||
Ok("[object Object]".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: ContextElement> 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<Vec<&dyn ContextElement>, 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<Vec<&dyn ContextElement>, RenderError> {
|
||||
Ok(vec![self])
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||
vec![self]
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: ContextElement> Loopable for HashMap<&str, I> {
|
||||
fn get_loop_elements(&self) -> Result<Vec<&dyn ContextElement>, RenderError> {
|
||||
Ok(vec![self])
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||
vec![self]
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user