Repository: sunng87/handlebars-rust (rust)

Issue:
Refactor internal RenderError into RenderErrorReason
In previous versions of handler, we used error messages for `RenderError`, making it impossible to parse the error and adding handlers if necessary.

Pull request description:
Refactor more RenderError into RenderErrorReason
Fixes #572 



Patch:
diff --git a/examples/decorator.rs b/examples/decorator.rs
index 6d5357b..7dc926a 100644
--- a/examples/decorator.rs
+++ b/examples/decorator.rs
@@ -8,7 +8,8 @@ use std::error::Error;
 use serde_json::value::{Map, Value as Json};
 
 use handlebars::{
-    to_json, Context, Decorator, Handlebars, Helper, JsonRender, Output, RenderContext, RenderError,
+    to_json, Context, Decorator, Handlebars, Helper, JsonRender, Output, RenderContext,
+    RenderError, RenderErrorReason,
 };
 
 // default format helper
@@ -22,7 +23,7 @@ fn format_helper(
     // get parameter from helper or throw an error
     let param = h
         .param(0)
-        .ok_or(RenderError::new("Param 0 is required for format helper."))?;
+        .ok_or(RenderErrorReason::ParamNotFoundForIndex("format", 0))?;
     write!(out, "{} pts", param.value().render())?;
     Ok(())
 }
@@ -49,7 +50,7 @@ fn format_decorator(
                 // get parameter from helper or throw an error
                 let param = h
                     .param(0)
-                    .ok_or(RenderError::new("Param 0 is required for format helper."))?;
+                    .ok_or(RenderErrorReason::ParamNotFoundForIndex("format", 0))?;
                 write!(out, "{} {}", param.value().render(), suffix)?;
                 Ok(())
             },
@@ -80,7 +81,7 @@ fn set_decorator(
         rc.set_context(Context::wraps(new_ctx_data)?);
         Ok(())
     } else {
-        Err(RenderError::new("Cannot extend non-object data"))
+        Err(RenderErrorReason::Other("Cannot extend non-object data".to_owned()).into())
     }
 }
 
@@ -92,19 +93,18 @@ fn rank_helper(
     _: &mut RenderContext,
     out: &mut dyn Output,
 ) -> Result<(), RenderError> {
-    let rank = h
-        .param(0)
-        .and_then(|v| v.value().as_u64())
-        .ok_or(RenderError::new(
-            "Param 0 with u64 type is required for rank helper.",
-        ))? as usize;
+    let rank = h.param(0).and_then(|v| v.value().as_u64()).ok_or(
+        RenderErrorReason::ParamTypeMismatchForName("rank", "0".to_string(), "u64".to_string()),
+    )? as usize;
     let total = h
         .param(1)
         .as_ref()
         .and_then(|v| v.value().as_array())
         .map(|arr| arr.len())
-        .ok_or(RenderError::new(
-            "Param 1 with array type is required for rank helper",
+        .ok_or(RenderErrorReason::ParamTypeMismatchForName(
+            "rank",
+            "1".to_string(),
+            "array".to_string(),
         ))?;
     if rank == 0 {
         out.write("champion")?;
diff --git a/examples/error.rs b/examples/error.rs
index ed0f7c4..672c4bc 100644
--- a/examples/error.rs
+++ b/examples/error.rs
@@ -5,7 +5,9 @@ extern crate serde_json;
 
 use std::error::Error as StdError;
 
-use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
+use handlebars::{
+    Context, Handlebars, Helper, Output, RenderContext, RenderError, RenderErrorReason,
+};
 use thiserror::Error;
 
 #[derive(Debug, Error)]
@@ -26,7 +28,7 @@ pub fn error_helper(
 ) -> Result<(), RenderError> {
     let param = h
         .param(0)
-        .ok_or(RenderError::new("Param 0 is required for error helper."))?;
+        .ok_or(RenderErrorReason::ParamNotFoundForIndex("error", 0))?;
     match param.value().as_str() {
         Some("db") => Err(RenderError::from_error(
             "helper error",
diff --git a/examples/render.rs b/examples/render.rs
index a6f26d0..e31e072 100644
--- a/examples/render.rs
+++ b/examples/render.rs
@@ -10,6 +10,7 @@ use std::error::Error;
 
 use handlebars::{
     to_json, Context, Handlebars, Helper, JsonRender, Output, RenderContext, RenderError,
+    RenderErrorReason,
 };
 
 // define a custom helper
@@ -23,7 +24,7 @@ fn format_helper(
     // get parameter from helper or throw an error
     let param = h
         .param(0)
-        .ok_or(RenderError::new("Param 0 is required for format helper."))?;
+        .ok_or(RenderErrorReason::ParamNotFoundForIndex("format", 0))?;
     write!(out, "{} pts", param.value().render())?;
     Ok(())
 }
@@ -36,19 +37,18 @@ fn rank_helper(
     _: &mut RenderContext,
     out: &mut dyn Output,
 ) -> Result<(), RenderError> {
-    let rank = h
-        .param(0)
-        .and_then(|v| v.value().as_u64())
-        .ok_or(RenderError::new(
-            "Param 0 with u64 type is required for rank helper.",
-        ))? as usize;
+    let rank = h.param(0).and_then(|v| v.value().as_u64()).ok_or(
+        RenderErrorReason::ParamTypeMismatchForName("rank", "0".to_string(), "u64".to_string()),
+    )? as usize;
     let total = h
         .param(1)
         .as_ref()
         .and_then(|v| v.value().as_array())
         .map(|arr| arr.len())
-        .ok_or(RenderError::new(
-            "Param 1 with array type is required for rank helper",
+        .ok_or(RenderErrorReason::ParamTypeMismatchForName(
+            "rank",
+            "1".to_string(),
+            "array".to_string(),
         ))?;
     if rank == 0 {
         out.write("champion")?;
diff --git a/examples/render_file.rs b/examples/render_file.rs
index 7863086..f3f8e53 100644
--- a/examples/render_file.rs
+++ b/examples/render_file.rs
@@ -14,6 +14,7 @@ use std::io::{Read, Write};
 
 use handlebars::{
     to_json, Context, Handlebars, Helper, JsonRender, Output, RenderContext, RenderError,
+    RenderErrorReason,
 };
 
 // define a custom helper
@@ -24,9 +25,10 @@ fn format_helper(
     _: &mut RenderContext,
     out: &mut dyn Output,
 ) -> Result<(), RenderError> {
+    // get parameter from helper or throw an error
     let param = h
         .param(0)
-        .ok_or(RenderError::new("Param 0 is required for format helper."))?;
+        .ok_or(RenderErrorReason::ParamNotFoundForIndex("format", 0))?;
     write!(out, "{} pts", param.value().render())?;
     Ok(())
 }
@@ -39,19 +41,18 @@ fn rank_helper(
     _: &mut RenderContext,
     out: &mut dyn Output,
 ) -> Result<(), RenderError> {
-    let rank = h
-        .param(0)
-        .and_then(|ref v| v.value().as_u64())
-        .ok_or(RenderError::new(
-            "Param 0 with u64 type is required for rank helper.",
-        ))? as usize;
+    let rank = h.param(0).and_then(|v| v.value().as_u64()).ok_or(
+        RenderErrorReason::ParamTypeMismatchForName("rank", "0".to_string(), "u64".to_string()),
+    )? as usize;
     let total = h
         .param(1)
         .as_ref()
         .and_then(|v| v.value().as_array())
         .map(|arr| arr.len())
-        .ok_or(RenderError::new(
-            "Param 1 with array type is required for rank helper",
+        .ok_or(RenderErrorReason::ParamTypeMismatchForName(
+            "rank",
+            "1".to_string(),
+            "array".to_string(),
         ))?;
     if rank == 0 {
         out.write("champion")?;
diff --git a/src/decorators/inline.rs b/src/decorators/inline.rs
index 72fac60..8eec1a6 100644
--- a/src/decorators/inline.rs
+++ b/src/decorators/inline.rs
@@ -3,18 +3,19 @@ use crate::decorators::{DecoratorDef, DecoratorResult};
 use crate::error::RenderError;
 use crate::registry::Registry;
 use crate::render::{Decorator, RenderContext};
+use crate::RenderErrorReason;
 
 #[derive(Clone, Copy)]
 pub struct InlineDecorator;
 
 fn get_name<'reg: 'rc, 'rc>(d: &Decorator<'rc>) -> Result<String, RenderError> {
     d.param(0)
-        .ok_or_else(|| RenderError::new("Param required for decorator \"inline\""))
+        .ok_or_else(|| RenderErrorReason::ParamNotFoundForIndex("inline", 0).into())
         .and_then(|v| {
             v.value()
                 .as_str()
                 .map(|v| v.to_owned())
-                .ok_or_else(|| RenderError::new("inline name must be string"))
+                .ok_or_else(|| RenderErrorReason::InvalidParamType("String").into())
         })
 }
 
@@ -30,7 +31,7 @@ impl DecoratorDef for InlineDecorator {
 
         let template = d
             .template()
-            .ok_or_else(|| RenderError::new("inline should have a block"))?;
+            .ok_or(RenderErrorReason::BlockContentRequired)?;
 
         rc.set_partial(name, template);
         Ok(())
diff --git a/src/error.rs b/src/error.rs
index 9947f76..97de3f9 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -75,22 +75,41 @@ impl From<TemplateError> for RenderError {
 /// Template rendering error
 #[derive(Debug, Error)]
 pub enum RenderErrorReason {
-    #[error("missing variable path {0:?}")]
+    #[error("Template not found {0}")]
+    TemplateNotFound(String),
+    #[error("Failed to access variable in strict mode {0:?}")]
     MissingVariable(Option<String>),
-    #[error("partial not found {0}")]
+    #[error("Partial not found {0}")]
     PartialNotFound(String),
+    #[error("Helper not found {0}")]
+    HelperNotFound(String),
+    #[error("Helper/Decorator {0} param at index {1} required but not found")]
+    ParamNotFoundForIndex(&'static str, usize),
+    #[error("Helper/Decorator {0} param with name {1} required but not found")]
+    ParamNotFoundForName(&'static str, String),
+    #[error("Helper/Decorator {0} param with name {1} type mismatch for {2}")]
+    ParamTypeMismatchForName(&'static str, String, String),
+    #[error("Helper/Decorator {0} hash with name {1} type mismatch for {2}")]
+    HashTypeMismatchForName(&'static str, String, String),
+    #[error("Decorator not found {0}")]
+    DecoratorNotFound(String),
+    #[error("Can not include current template in partial")]
+    CannotIncludeSelf,
+    #[error("Invalid logging level: {0}")]
+    InvalidLoggingLevel(String),
+    #[error("Invalid param type, {0} expected")]
+    InvalidParamType(&'static str),
+    #[error("Block content required")]
+    BlockContentRequired,
+    #[error("Invalid json path {0}")]
+    InvalidJsonPath(String),
+    #[error("{0}")]
+    Other(String),
 }
 
 impl From<RenderErrorReason> for RenderError {
     fn from(e: RenderErrorReason) -> RenderError {
-        match e {
-            RenderErrorReason::MissingVariable(_) => {
-                RenderError::from_error("Failed to access variable in strict mode.", e)
-            }
-            RenderErrorReason::PartialNotFound(_) => {
-                RenderError::from_error("Partial not found.", e)
-            }
-        }
+        RenderError::from_error(&e.to_string(), e)
     }
 }
 
@@ -109,6 +128,7 @@ impl From<ScriptError> for RenderError {
 }
 
 impl RenderError {
+    #[deprecated(since = "5.0.0", note = "Use RenderErrorReason instead")]
     pub fn new<T: AsRef<str>>(desc: T) -> RenderError {
         RenderError {
             desc: desc.as_ref().to_owned(),
@@ -131,10 +151,11 @@ impl RenderError {
     where
         E: StdError + Send + Sync + 'static,
     {
-        let mut e = RenderError::new(error_info);
-        e.cause = Some(Box::new(cause));
-
-        e
+        RenderError {
+            desc: error_info.to_owned(),
+            cause: Some(Box::new(cause)),
+            ..Default::default()
+        }
     }
 
     #[inline]
diff --git a/src/helpers/helper_each.rs b/src/helpers/helper_each.rs
index 2fec8f0..6246d35 100644
--- a/src/helpers/helper_each.rs
+++ b/src/helpers/helper_each.rs
@@ -10,6 +10,7 @@ use crate::output::Output;
 use crate::registry::Registry;
 use crate::render::{Helper, RenderContext, Renderable};
 use crate::util::copy_on_push_vec;
+use crate::RenderErrorReason;
 
 fn update_block_context(
     block: &mut BlockContext<'_>,
@@ -74,7 +75,7 @@ impl HelperDef for EachHelper {
     ) -> HelperResult {
         let value = h
             .param(0)
-            .ok_or_else(|| RenderError::new("Param not found for helper \"each\""))?;
+            .ok_or(RenderErrorReason::ParamNotFoundForIndex("each", 0))?;
 
         let template = h.template();
 
diff --git a/src/helpers/helper_if.rs b/src/helpers/helper_if.rs
index e9ea33c..60aee45 100644
--- a/src/helpers/helper_if.rs
+++ b/src/helpers/helper_if.rs
@@ -1,10 +1,10 @@
 use crate::context::Context;
-use crate::error::RenderError;
 use crate::helpers::{HelperDef, HelperResult};
 use crate::json::value::JsonTruthy;
 use crate::output::Output;
 use crate::registry::Registry;
 use crate::render::{Helper, RenderContext, Renderable};
+use crate::RenderErrorReason;
 
 #[derive(Clone, Copy)]
 pub struct IfHelper {
@@ -22,7 +22,7 @@ impl HelperDef for IfHelper {
     ) -> HelperResult {
         let param = h
             .param(0)
-            .ok_or_else(|| RenderError::new("Param not found for helper \"if\""))?;
+            .ok_or(RenderErrorReason::ParamNotFoundForIndex("if", 0))?;
         let include_zero = h
             .hash_get("includeZero")
             .and_then(|v| v.value().as_bool())
diff --git a/src/helpers/helper_log.rs b/src/helpers/helper_log.rs
index 36394be..720ef77 100644
--- a/src/helpers/helper_log.rs
+++ b/src/helpers/helper_log.rs
@@ -1,6 +1,4 @@
 use crate::context::Context;
-#[cfg(not(feature = "no_logging"))]
-use crate::error::RenderError;
 use crate::helpers::{HelperDef, HelperResult};
 #[cfg(not(feature = "no_logging"))]
 use crate::json::value::JsonRender;
@@ -8,6 +6,8 @@ use crate::output::Output;
 use crate::registry::Registry;
 use crate::render::{Helper, RenderContext};
 #[cfg(not(feature = "no_logging"))]
+use crate::RenderErrorReason;
+#[cfg(not(feature = "no_logging"))]
 use log::Level;
 #[cfg(not(feature = "no_logging"))]
 use std::str::FromStr;
@@ -46,10 +46,7 @@ impl HelperDef for LogHelper {
         if let Ok(log_level) = Level::from_str(level) {
             log!(log_level, "{}", param_to_log)
         } else {
-            return Err(RenderError::new(&format!(
-                "Unsupported logging level {}",
-                level
-            )));
+            return Err(RenderErrorReason::InvalidLoggingLevel(level.to_string()).into());
         }
         Ok(())
     }
diff --git a/src/helpers/helper_lookup.rs b/src/helpers/helper_lookup.rs
index 5afb14e..0794b68 100644
--- a/src/helpers/helper_lookup.rs
+++ b/src/helpers/helper_lookup.rs
@@ -6,6 +6,7 @@ use crate::helpers::HelperDef;
 use crate::json::value::ScopedJson;
 use crate::registry::Registry;
 use crate::render::{Helper, RenderContext};
+use crate::RenderErrorReason;
 
 #[derive(Clone, Copy)]
 pub struct LookupHelper;
@@ -20,10 +21,10 @@ impl HelperDef for LookupHelper {
     ) -> Result<ScopedJson<'rc>, RenderError> {
         let collection_value = h
             .param(0)
-            .ok_or_else(|| RenderError::new("Param not found for helper \"lookup\""))?;
+            .ok_or(RenderErrorReason::ParamNotFoundForIndex("lookup", 0))?;
         let index = h
             .param(1)
-            .ok_or_else(|| RenderError::new("Insufficient params for helper \"lookup\""))?;
+            .ok_or(RenderErrorReason::ParamNotFoundForIndex("lookup", 1))?;
 
         let value = match *collection_value.value() {
             Json::Array(ref v) => index.value().as_u64().and_then(|u| v.get(u as usize)),
diff --git a/src/helpers/helper_with.rs b/src/helpers/helper_with.rs
index 96f0792..eb85133 100644
--- a/src/helpers/helper_with.rs
+++ b/src/helpers/helper_with.rs
@@ -7,6 +7,7 @@ use crate::json::value::JsonTruthy;
 use crate::output::Output;
 use crate::registry::Registry;
 use crate::render::{Helper, RenderContext, Renderable};
+use crate::RenderErrorReason;
 
 #[derive(Clone, Copy)]
 pub struct WithHelper;
@@ -22,7 +23,7 @@ impl HelperDef for WithHelper {
     ) -> HelperResult {
         let param = h
             .param(0)
-            .ok_or_else(|| RenderError::new("Param not found for helper \"with\""))?;
+            .ok_or(RenderErrorReason::ParamNotFoundForIndex("with", 0))?;
 
         if param.value().is_truthy(false) {
             let mut block = create_block(param);
diff --git a/src/json/path.rs b/src/json/path.rs
index 17a7a91..288c156 100644
--- a/src/json/path.rs
+++ b/src/json/path.rs
@@ -5,6 +5,7 @@ use pest::Parser;
 
 use crate::error::RenderError;
 use crate::grammar::{HandlebarsParser, Rule};
+use crate::RenderErrorReason;
 
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub enum PathSeg {
@@ -38,7 +39,7 @@ impl Path {
                 let segs = parse_json_path_from_iter(&mut parsed.peekable(), raw.len());
                 Ok(Path::new(raw, segs))
             })
-            .map_err(|_| RenderError::new("Invalid JSON path"))?
+            .map_err(|_| RenderErrorReason::InvalidJsonPath(raw.to_owned()))?
     }
 
     pub(crate) fn raw(&self) -> &str {
diff --git a/src/lib.rs b/src/lib.rs
index 0693765..9959c90 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -254,7 +254,7 @@
 //! ```
 //! use std::io::Write;
 //! # use std::error::Error;
-//! use handlebars::{Handlebars, HelperDef, RenderContext, Helper, Context, JsonRender, HelperResult, Output, RenderError};
+//! use handlebars::*;
 //!
 //! // implement by a structure impls HelperDef
 //! #[derive(Clone, Copy)]
@@ -287,7 +287,8 @@
 //!   // via closure
 //!   handlebars.register_helper("closure-helper",
 //!       Box::new(|h: &Helper, r: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output| -> HelperResult {
-//!           let param = h.param(0).ok_or(RenderError::new("param not found"))?;
+//!           let param =
+//!           h.param(0).ok_or(RenderErrorReason::ParamNotFoundForIndex("closure-helper", 0))?;
 //!
 //!           out.write("3rd helper: ")?;
 //!           out.write(param.value().render().as_ref())?;
diff --git a/src/macros.rs b/src/macros.rs
index 92f7778..c299adb 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -66,18 +66,10 @@ macro_rules! handlebars_helper {
                                 Some(x.value())
                             }
                         })
-                        .ok_or_else(|| $crate::RenderError::new(&format!(
-                            "`{}` helper: Couldn't read parameter {}",
-                            stringify!($struct_name), stringify!($name),
-                        )))
+                        .ok_or_else(|| $crate::RenderErrorReason::ParamNotFoundForName(stringify!($struct_name), stringify!($name).to_string()))
                         .and_then(|x|
                                   handlebars_helper!(@as_json_value x, $tpe$(<$($gen),+>)?)
-                                  .ok_or_else(|| $crate::RenderError::new(&format!(
-                                      "`{}` helper: Couldn't convert parameter {} to type `{}`. \
-                                       It's {:?} as JSON. Got these params: {:?}",
-                                      stringify!($struct_name), stringify!($name), stringify!($tpe$(<$($gen),+>)?),
-                                      x, h.params(),
-                                  )))
+                                  .ok_or_else(|| $crate::RenderErrorReason::ParamTypeMismatchForName(stringify!($struct_name), stringify!($name).to_string(), stringify!($tpe$(<$($gen),+>)?).to_string()).into())
                         )?;
                     param_idx += 1;
                 )*
@@ -88,12 +80,9 @@ macro_rules! handlebars_helper {
                                 .map(|x| x.value())
                                 .map(|x|
                                      handlebars_helper!(@as_json_value x, $hash_tpe)
-                                     .ok_or_else(|| $crate::RenderError::new(&format!(
-                                         "`{}` helper: Couldn't convert hash {} to type `{}`. \
-                                          It's {:?} as JSON. Got these hash: {:?}",
-                                         stringify!($struct_name), stringify!($hash_name), stringify!($hash_tpe),
-                                         x, h.hash(),
-                                     )))
+                                     .ok_or_else(|| $crate::RenderErrorReason::HashTypeMismatchForName(
+                                         stringify!($struct_name), stringify!($hash_name).to_string(), stringify!($hash_tpe).to_string()
+                                     ))
                                 )
                                 .unwrap_or_else(|| Ok($dft_val))?;
                         )*
diff --git a/src/partial.rs b/src/partial.rs
index 54b31b7..d1cc7af 100644
--- a/src/partial.rs
+++ b/src/partial.rs
@@ -50,7 +50,7 @@ pub fn expand_partial<'reg: 'rc, 'rc>(
 
     let tname = d.name();
     if rc.is_current_template(tname) {
-        return Err(RenderError::new("Cannot include self in >"));
+        return Err(RenderErrorReason::CannotIncludeSelf.into());
     }
 
     let partial = find_partial(rc, r, d, tname)?;
diff --git a/src/registry.rs b/src/registry.rs
index f810628..d1a69de 100644
--- a/src/registry.rs
+++ b/src/registry.rs
@@ -18,6 +18,7 @@ use crate::render::{RenderContext, Renderable};
 use crate::sources::{FileSource, Source};
 use crate::support::str::{self, StringWriter};
 use crate::template::{Template, TemplateOptions};
+use crate::RenderErrorReason;
 
 #[cfg(feature = "dir_source")]
 use walkdir::WalkDir;
@@ -521,7 +522,7 @@ impl<'reg> Registry<'reg> {
         if let Some(result) = self.get_or_load_template_optional(name) {
             result
         } else {
-            Err(RenderError::new(format!("Template not found: {name}")))
+            Err(RenderErrorReason::TemplateNotFound(name.to_owned()).into())
         }
     }
 
diff --git a/src/render.rs b/src/render.rs
index 894b1dc..f9a4fc8 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -12,7 +12,6 @@ use crate::helpers::HelperDef;
 use crate::json::path::Path;
 use crate::json::value::{JsonRender, PathAndJson, ScopedJson};
 use crate::output::{Output, StringOutput};
-use crate::partial;
 use crate::registry::Registry;
 use crate::support;
 use crate::template::TemplateElement::*;
@@ -20,6 +19,7 @@ use crate::template::{
     BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
     TemplateMapping,
 };
+use crate::{partial, RenderErrorReason};
 
 const HELPER_MISSING: &str = "helperMissing";
 const BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
@@ -367,7 +367,7 @@ impl<'reg: 'rc, 'rc> Helper<'rc> {
     ///
     /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
     ///     let v = h.param(0).map(|v| v.value())
-    ///         .ok_or(RenderError::new("param not found"));
+    ///         .ok_or(RenderErrorReason::ParamNotFoundForIndex("myhelper", 0));
     ///     // ..
     ///     Ok(())
     /// }
@@ -394,7 +394,7 @@ impl<'reg: 'rc, 'rc> Helper<'rc> {
     ///
     /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
     ///     let v = h.hash_get("v").map(|v| v.value())
-    ///         .ok_or(RenderError::new("param not found"));
+    ///         .ok_or(RenderErrorReason::ParamNotFoundForIndex("my_helper", 0));
     ///     // ..
     ///     Ok(())
     /// }
@@ -653,7 +653,7 @@ impl Parameter {
 
                         helper
                             .ok_or_else(|| {
-                                RenderError::new(format!("Helper not defined: {:?}", ht.name))
+                                RenderErrorReason::HelperNotFound(name.to_string()).into()
                             })
                             .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
                     }
@@ -759,7 +759,7 @@ fn render_helper<'reg: 'rc, 'rc>(
         }
 
         helper
-            .ok_or_else(|| RenderError::new(format!("Helper not defined: {:?}", h.name())))
+            .ok_or_else(|| RenderErrorReason::HelperNotFound(h.name().to_owned()).into())
             .and_then(|d| d.call(&h, registry, ctx, rc, out))
     }
 }
@@ -863,10 +863,7 @@ impl Evaluable for TemplateElement {
                 let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
                 match registry.get_decorator(di.name()) {
                     Some(d) => d.call(&di, registry, ctx, rc),
-                    None => Err(RenderError::new(format!(
-                        "Decorator not defined: {:?}",
-                        dt.name
-                    ))),
+                    None => Err(RenderErrorReason::DecoratorNotFound(di.name().to_owned()).into()),
                 }
             }
             _ => Ok(()),


Test patch:
diff --git a/tests/helper_function_lifetime.rs b/tests/helper_function_lifetime.rs
index ccea427..59ade4b 100644
--- a/tests/helper_function_lifetime.rs
+++ b/tests/helper_function_lifetime.rs
@@ -7,10 +7,9 @@ fn ifcond<'reg, 'rc>(
     render_ctx: &mut RenderContext<'reg, 'rc>,
     out: &mut dyn Output,
 ) -> Result<(), RenderError> {
-    let cond = h
-        .param(0)
-        .and_then(|ref v| v.value().as_bool())
-        .ok_or(RenderError::new("Ifcond takes a boolean !"))? as bool;
+    let cond = h.param(0).and_then(|ref v| v.value().as_bool()).ok_or(
+        RenderErrorReason::ParamTypeMismatchForName("ifcond", "0".to_owned(), "bool".to_owned()),
+    )? as bool;
     let temp = if cond { h.template() } else { h.inverse() };
     match temp {
         Some(t) => t.render(handle, ctx, render_ctx, out),
diff --git a/tests/subexpression.rs b/tests/subexpression.rs
index 2677708..bcd4baa 100644
--- a/tests/subexpression.rs
+++ b/tests/subexpression.rs
@@ -2,10 +2,14 @@ extern crate handlebars;
 #[macro_use]
 extern crate serde_json;
 
+use std::error::Error;
 use std::sync::atomic::{AtomicU16, Ordering};
 use std::sync::Arc;
 
-use handlebars::{Context, Handlebars, Helper, HelperDef, RenderContext, RenderError, ScopedJson};
+use handlebars::{
+    Context, Handlebars, Helper, HelperDef, RenderContext, RenderError, RenderErrorReason,
+    ScopedJson,
+};
 
 #[test]
 fn test_subexpression() {
@@ -78,10 +82,13 @@ fn invalid_json_path() {
     let hbs = Handlebars::new();
 
     let error = hbs.render_template("{{x[]@this}}", &data).unwrap_err();
+    let cause = error
+        .source()
+        .unwrap()
+        .downcast_ref::<RenderErrorReason>()
+        .unwrap();
 
-    let expected = "Error rendering \"Unnamed template\" line 1, col 1: Helper not defined: \"x\"";
-
-    assert_eq!(format!("{}", error), expected);
+    assert!(matches!(cause, RenderErrorReason::HelperNotFound(_)));
 }
 
 struct MyHelper;