Skip to content

Commit

Permalink
Add some structured errors to Error enum
Browse files Browse the repository at this point in the history
Close #322
  • Loading branch information
Vincent Prouillet committed Dec 6, 2018
1 parent a9cd38d commit 5b1845f
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 52 deletions.
5 changes: 2 additions & 3 deletions benches/escaping.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![feature(test)]
extern crate test;
extern crate tera;
extern crate test;

use tera::escape_html;

Expand All @@ -10,7 +10,7 @@ const HTML_SHORT: &'static str = "Here->An <<example>> of rust codefn foo(u: &u3
const HTML_LONG: &'static str = "A somewhat longer paragraph containing a character that needs to be escaped, because e.g. the author mentions the movie Fast&Furious in it. This paragraph is also quite long to match the non-html one.";

// taken from https://github.com/djc/askama/blob/master/askama_escape/benches/all.rs
const NO_HTML_VERY_LONG: &'static str = r#"
const NO_HTML_VERY_LONG: &'static str = r#"
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin scelerisque eu urna in aliquet.
Phasellus ac nulla a urna sagittis consequat id quis est. Nullam eu ex eget erat accumsan dictum
ac lobortis urna. Etiam fermentum ut quam at dignissim. Curabitur vestibulum luctus tellus, sit
Expand Down Expand Up @@ -63,7 +63,6 @@ const HTML_VERY_LONG: &'static str = r#"
justo.
</p>"#;


#[bench]
fn bench_escape_no_html_short(b: &mut test::Bencher) {
b.iter(|| escape_html(NO_HTML_SHORT));
Expand Down
1 change: 0 additions & 1 deletion src/builtins/filters/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ mod tests {
assert_eq!(result.unwrap(), to_value("").unwrap());
}


#[test]
fn test_first() {
let result = first(&to_value(&vec![1, 2, 3, 4]).unwrap(), &HashMap::new());
Expand Down
74 changes: 73 additions & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,40 @@ use std::convert::Into;
use std::error::Error as StdError;
use std::fmt;

/// The kind of an error.
/// The kind of an error (non-exhaustive)
#[derive(Debug)]
pub enum ErrorKind {
/// Generic error
Msg(String),
/// A loop was found while looking up the inheritance chain
CircularExtend {
/// Name of the template with the loop
tpl: String,
/// All the parents templates we found so far
inheritance_chain: Vec<String>,
},
/// A template is extending a template that wasn't found in the Tera instance
MissingParent {
/// The template we are currently looking at
current: String,
/// The missing template
parent: String,
},
/// A template was missing (more generic version of MissingParent)
TemplateNotFound(String),
/// A filter wasn't found
FilterNotFound(String),
/// A test wasn't found
TestNotFound(String),
/// A function wasn't found
FunctionNotFound(String),
/// An error happened while serializing JSON
Json(serde_json::Error),
/// This enum may grow additional variants, so this makes sure clients
/// don't count on exhaustive matching. (Otherwise, adding a new variant
/// could break existing code.)
#[doc(hidden)]
__Nonexhaustive,
}

/// The Error type
Expand All @@ -24,7 +51,22 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
ErrorKind::Msg(ref message) => write!(f, "{}", message),
ErrorKind::CircularExtend {ref tpl, ref inheritance_chain} => write!(
f,
"Circular extend detected for template '{}'. Inheritance chain: `{:?}`",
tpl, inheritance_chain
),
ErrorKind::MissingParent {ref current, ref parent} => write!(
f,
"Template '{}' is inheriting from '{}', which doesn't exist or isn't loaded.",
current, parent
),
ErrorKind::TemplateNotFound(ref name) => write!(f, "Template '{}' not found", name),
ErrorKind::FilterNotFound(ref name) => write!(f, "Filter '{}' not found", name),
ErrorKind::TestNotFound(ref name) => write!(f, "Test '{}' not found", name),
ErrorKind::FunctionNotFound(ref name) => write!(f, "Function '{}' not found", name),
ErrorKind::Json(ref e) => write!(f, "{}", e),
ErrorKind::__Nonexhaustive => write!(f, "Nonexhaustive"),
}
}
}
Expand All @@ -41,6 +83,36 @@ impl Error {
Self { kind: ErrorKind::Msg(value.to_string()), cause: None }
}

/// Creates a circular extend error
pub fn circular_extend(tpl: impl ToString, inheritance_chain: Vec<String>) -> Self {
Self { kind: ErrorKind::CircularExtend { tpl: tpl.to_string(), inheritance_chain}, cause: None }
}

/// Creates a missing parent error
pub fn missing_parent(current: impl ToString, parent: impl ToString) -> Self {
Self { kind: ErrorKind::MissingParent { current: current.to_string(), parent: parent.to_string() }, cause: None }
}

/// Creates a template not found error
pub fn template_not_found(tpl: impl ToString) -> Self {
Self { kind: ErrorKind::TemplateNotFound(tpl.to_string()), cause: None }
}

/// Creates a filter not found error
pub fn filter_not_found(name: impl ToString) -> Self {
Self { kind: ErrorKind::FilterNotFound(name.to_string()), cause: None }
}

/// Creates a test not found error
pub fn test_not_found(name: impl ToString) -> Self {
Self { kind: ErrorKind::TestNotFound(name.to_string()), cause: None }
}

/// Creates a function not found error
pub fn function_not_found(name: impl ToString) -> Self {
Self { kind: ErrorKind::FunctionNotFound(name.to_string()), cause: None }
}

/// Creates generic error with a cause
pub fn chain(value: impl ToString, cause: impl Into<Box<dyn StdError>>) -> Self {
Self { kind: ErrorKind::Msg(value.to_string()), cause: Some(cause.into()) }
Expand Down
2 changes: 1 addition & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ fn parse_basic_expression(pair: Pair<Rule>) -> ExprVal {
let mut test = parse_test(pair);
test.negated = true;
ExprVal::Test(test)
},
}
Rule::fn_call => ExprVal::FunctionCall(parse_fn_call(pair)),
Rule::macro_call => ExprVal::MacroCall(parse_macro_call(pair)),
Rule::string => ExprVal::String(replace_string_markers(pair.as_str())),
Expand Down
28 changes: 12 additions & 16 deletions src/renderer/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,28 +71,24 @@ fn process_path<'a>(path: &str, call_stack: &CallStack<'a>) -> Result<Val<'a>> {
if !path.contains('[') {
match call_stack.lookup(path) {
Some(v) => Ok(v),
None => {
Err(Error::msg(format!(
"Variable `{}` not found in context while rendering '{}'",
path,
call_stack.active_template().name
)))
}
None => Err(Error::msg(format!(
"Variable `{}` not found in context while rendering '{}'",
path,
call_stack.active_template().name
))),
}
} else {
let full_path = evaluate_sub_variables(path, call_stack)?;

match call_stack.lookup(full_path.as_ref()) {
Some(v) => Ok(v),
None => {
Err(Error::msg(format!(
"Variable `{}` not found in context while rendering '{}': \
the evaluated version was `{}`. Maybe the index is out of bounds?",
path,
call_stack.active_template().name,
full_path,
)))
}
None => Err(Error::msg(format!(
"Variable `{}` not found in context while rendering '{}': \
the evaluated version was `{}`. Maybe the index is out of bounds?",
path,
call_stack.active_template().name,
full_path,
))),
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions src/renderer/tests/basic.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::{BTreeMap, HashMap};
use std::error::Error;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

use serde_json::Value;

Expand Down Expand Up @@ -696,6 +696,12 @@ fn stateful_global_fn() {
tera
}

assert_eq!(make_tera().render("fn.html", &Context::new()).unwrap(), "<h1>1, 1, 2...</h1>".to_owned());
assert_eq!(make_tera().render("fn.html", &Context::new()).unwrap(), "<h1>1, 2, 2...</h1>".to_owned());
assert_eq!(
make_tera().render("fn.html", &Context::new()).unwrap(),
"<h1>1, 1, 2...</h1>".to_owned()
);
assert_eq!(
make_tera().render("fn.html", &Context::new()).unwrap(),
"<h1>1, 2, 2...</h1>".to_owned()
);
}
38 changes: 12 additions & 26 deletions src/tera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct Tera {
#[doc(hidden)]
pub testers: HashMap<String, Arc<dyn Test>>,
#[doc(hidden)]
pub global_functions: HashMap<String, Arc<dyn Function>>,
pub functions: HashMap<String, Arc<dyn Function>>,
// Which extensions does Tera automatically autoescape on.
// Defaults to [".html", ".htm", ".xml"]
#[doc(hidden)]
Expand All @@ -54,7 +54,7 @@ impl Tera {
glob: Some(dir.to_string()),
templates: HashMap::new(),
filters: HashMap::new(),
global_functions: HashMap::new(),
functions: HashMap::new(),
testers: HashMap::new(),
autoescape_suffixes: vec![".html", ".htm", ".xml"],
escape_fn: escape_html,
Expand Down Expand Up @@ -203,10 +203,7 @@ impl Tera {
mut parents: Vec<String>,
) -> Result<Vec<String>> {
if !parents.is_empty() && start.name == template.name {
return Err(Error::msg(format!(
"Circular extend detected for template '{}'. Inheritance chain: `{:?}`",
start.name, parents,
)));
return Err(Error::circular_extend(&start.name, parents));
}

match template.parent {
Expand All @@ -215,10 +212,7 @@ impl Tera {
parents.push(parent.name.clone());
build_chain(templates, start, parent, parents)
}
None => Err(Error::msg(format!(
"Template '{}' is inheriting from '{}', which doesn't exist or isn't loaded.",
template.name, p,
))),
None => Err(Error::missing_parent(&template.name, &p))
},
None => Ok(parents),
}
Expand All @@ -241,15 +235,7 @@ impl Tera {

// and then see if our parents have it
for parent in &parents {
let t = self.get_template(parent).map_err(|e| {
Error::chain(
format!(
"Couldn't find template {} while building inheritance chains",
parent,
),
e,
)
})?;
let t = self.get_template(parent)?;

if let Some(b) = t.blocks.get(block_name) {
definitions.push((t.name.clone(), b.clone()));
Expand Down Expand Up @@ -360,7 +346,7 @@ impl Tera {
pub fn get_template(&self, template_name: &str) -> Result<&Template> {
match self.templates.get(template_name) {
Some(tpl) => Ok(tpl),
None => Err(Error::msg(format!("Template '{}' not found", template_name))),
None => Err(Error::template_not_found(template_name)),
}
}

Expand Down Expand Up @@ -453,7 +439,7 @@ impl Tera {
pub fn get_filter(&self, filter_name: &str) -> Result<&dyn Filter> {
match self.filters.get(filter_name) {
Some(fil) => Ok(&**fil),
None => Err(Error::msg(format!("Filter '{}' not found", filter_name))),
None => Err(Error::filter_not_found(filter_name)),
}
}

Expand All @@ -473,7 +459,7 @@ impl Tera {
pub fn get_tester(&self, tester_name: &str) -> Result<&dyn Test> {
match self.testers.get(tester_name) {
Some(t) => Ok(&**t),
None => Err(Error::msg(format!("Tester '{}' not found", tester_name))),
None => Err(Error::test_not_found(tester_name)),
}
}

Expand All @@ -491,9 +477,9 @@ impl Tera {
#[doc(hidden)]
#[inline]
pub fn get_function(&self, fn_name: &str) -> Result<&dyn Function> {
match self.global_functions.get(fn_name) {
match self.functions.get(fn_name) {
Some(t) => Ok(&**t),
None => Err(Error::msg(format!("Global function '{}' not found", fn_name))),
None => Err(Error::function_not_found(fn_name)),
}
}

Expand All @@ -505,7 +491,7 @@ impl Tera {
/// tera.register_function("range", range);
/// ```
pub fn register_function<F: Function + 'static>(&mut self, name: &str, function: F) {
self.global_functions.insert(name.to_string(), Arc::new(function));
self.functions.insert(name.to_string(), Arc::new(function));
}

fn register_tera_filters(&mut self) {
Expand Down Expand Up @@ -672,7 +658,7 @@ impl Default for Tera {
templates: HashMap::new(),
filters: HashMap::new(),
testers: HashMap::new(),
global_functions: HashMap::new(),
functions: HashMap::new(),
autoescape_suffixes: vec![".html", ".htm", ".xml"],
escape_fn: escape_html,
};
Expand Down
2 changes: 1 addition & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const FLAG: u8 = b'>' - b'"';
#[inline]
pub fn escape_html(input: &str) -> String {
let mut start = 0;
let mut output = String::with_capacity(input.len() + input.len()/2);
let mut output = String::with_capacity(input.len() + input.len() / 2);
let bytes = input.as_bytes();
for (i, b) in bytes.iter().enumerate() {
if b.wrapping_sub(b'"') <= FLAG {
Expand Down

0 comments on commit 5b1845f

Please sign in to comment.