blob: 5cb0e5ff7bfe12a9cae1bc7468cc3c8c1ca834f6 [file] [log] [blame]
use std::error::Error as StdError;
use std::fmt::{self, Write};
use std::io::Error as IOError;
use std::string::FromUtf8Error;
use serde_json::error::Error as SerdeError;
use thiserror::Error;
#[cfg(feature = "dir_source")]
use walkdir::Error as WalkdirError;
#[cfg(feature = "script_helper")]
use rhai::{EvalAltResult, ParseError};
/// Error when rendering data on template.
#[derive(Debug)]
pub struct RenderError {
pub template_name: Option<String>,
pub line_no: Option<usize>,
pub column_no: Option<usize>,
reason: Box<RenderErrorReason>,
unimplemented: bool,
// backtrace: Backtrace,
}
impl fmt::Display for RenderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let desc = self.reason.to_string();
match (self.line_no, self.column_no) {
(Some(line), Some(col)) => write!(
f,
"Error rendering \"{}\" line {}, col {}: {}",
self.template_name.as_deref().unwrap_or("Unnamed template"),
line,
col,
desc
),
_ => write!(f, "{}", desc),
}
}
}
impl From<IOError> for RenderError {
fn from(e: IOError) -> RenderError {
RenderErrorReason::IOError(e).into()
}
}
impl From<FromUtf8Error> for RenderError {
fn from(e: FromUtf8Error) -> Self {
RenderErrorReason::Utf8Error(e).into()
}
}
impl From<TemplateError> for RenderError {
fn from(e: TemplateError) -> Self {
RenderErrorReason::TemplateError(e).into()
}
}
/// Template rendering error
#[derive(Debug, Error)]
pub enum RenderErrorReason {
#[error("Template not found {0}")]
TemplateNotFound(String),
#[error("Failed to parse template {0}")]
TemplateError(
#[from]
#[source]
TemplateError,
),
#[error("Failed to access variable in strict mode {0:?}")]
MissingVariable(Option<String>),
#[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("Cannot access array/vector with string index, {0}")]
InvalidJsonIndex(String),
#[error("Failed to access JSON data: {0}")]
SerdeError(
#[from]
#[source]
SerdeError,
),
#[error("IO Error: {0}")]
IOError(
#[from]
#[source]
IOError,
),
#[error("FromUtf8Error: {0}")]
Utf8Error(
#[from]
#[source]
FromUtf8Error,
),
#[error("Nested error: {0}")]
NestedError(#[source] Box<dyn StdError + Send + Sync + 'static>),
#[cfg(feature = "script_helper")]
#[error("Cannot convert data to Rhai dynamic: {0}")]
ScriptValueError(
#[from]
#[source]
Box<EvalAltResult>,
),
#[cfg(feature = "script_helper")]
#[error("Failed to load rhai script: {0}")]
ScriptLoadError(
#[from]
#[source]
ScriptError,
),
#[error("Unimplemented")]
Unimplemented,
#[error("{0}")]
Other(String),
}
impl From<RenderErrorReason> for RenderError {
fn from(e: RenderErrorReason) -> RenderError {
RenderError {
template_name: None,
line_no: None,
column_no: None,
reason: Box::new(e),
unimplemented: false,
}
}
}
impl RenderError {
#[deprecated(since = "5.0.0", note = "Use RenderErrorReason instead")]
pub fn new<T: AsRef<str>>(desc: T) -> RenderError {
RenderErrorReason::Other(desc.as_ref().to_string()).into()
}
pub fn strict_error(path: Option<&String>) -> RenderError {
RenderErrorReason::MissingVariable(path.map(|p| p.to_owned())).into()
}
#[deprecated(since = "5.0.0", note = "Use RenderErrorReason::NestedError instead")]
pub fn from_error<E>(_error_info: &str, cause: E) -> RenderError
where
E: StdError + Send + Sync + 'static,
{
RenderErrorReason::NestedError(Box::new(cause)).into()
}
#[inline]
pub(crate) fn is_unimplemented(&self) -> bool {
matches!(*self.reason, RenderErrorReason::Unimplemented)
}
/// Get `RenderErrorReason` for this error
pub fn reason(&self) -> &RenderErrorReason {
self.reason.as_ref()
}
}
impl StdError for RenderError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(self.reason())
}
}
/// Template parsing error
#[derive(Debug, Error)]
pub enum TemplateErrorReason {
#[error("helper {0:?} was opened, but {1:?} is closing")]
MismatchingClosedHelper(String, String),
#[error("decorator {0:?} was opened, but {1:?} is closing")]
MismatchingClosedDecorator(String, String),
#[error("invalid handlebars syntax.")]
InvalidSyntax,
#[error("invalid parameter {0:?}")]
InvalidParam(String),
#[error("nested subexpression is not supported")]
NestedSubexpression,
#[error("Template \"{1}\": {0}")]
IoError(IOError, String),
#[cfg(feature = "dir_source")]
#[error("Walk dir error: {err}")]
WalkdirError {
#[from]
err: WalkdirError,
},
}
/// Error on parsing template.
#[derive(Debug, Error)]
pub struct TemplateError {
reason: Box<TemplateErrorReason>,
template_name: Option<String>,
line_no: Option<usize>,
column_no: Option<usize>,
segment: Option<String>,
}
impl TemplateError {
#[allow(deprecated)]
pub fn of(e: TemplateErrorReason) -> TemplateError {
TemplateError {
reason: Box::new(e),
template_name: None,
line_no: None,
column_no: None,
segment: None,
}
}
pub fn at(mut self, template_str: &str, line_no: usize, column_no: usize) -> TemplateError {
self.line_no = Some(line_no);
self.column_no = Some(column_no);
self.segment = Some(template_segment(template_str, line_no, column_no));
self
}
pub fn in_template(mut self, name: String) -> TemplateError {
self.template_name = Some(name);
self
}
/// Get underlying reason for the error
pub fn reason(&self) -> &TemplateErrorReason {
&self.reason
}
/// Get the line number and column number of this error
pub fn pos(&self) -> Option<(usize, usize)> {
match (self.line_no, self.column_no) {
(Some(line_no), Some(column_no)) => Some((line_no, column_no)),
_ => None,
}
}
/// Get template name of this error
/// Returns `None` when the template has no associated name
pub fn name(&self) -> Option<&String> {
self.template_name.as_ref()
}
}
impl From<(IOError, String)> for TemplateError {
fn from(err_info: (IOError, String)) -> TemplateError {
let (e, name) = err_info;
TemplateError::of(TemplateErrorReason::IoError(e, name))
}
}
#[cfg(feature = "dir_source")]
impl From<WalkdirError> for TemplateError {
fn from(e: WalkdirError) -> TemplateError {
TemplateError::of(TemplateErrorReason::from(e))
}
}
fn template_segment(template_str: &str, line: usize, col: usize) -> String {
let range = 3;
let line_start = if line >= range { line - range } else { 0 };
let line_end = line + range;
let mut buf = String::new();
for (line_count, line_content) in template_str.lines().enumerate() {
if line_count >= line_start && line_count <= line_end {
let _ = writeln!(&mut buf, "{line_count:4} | {line_content}");
if line_count == line - 1 {
buf.push_str(" |");
for c in 0..line_content.len() {
if c != col {
buf.push('-');
} else {
buf.push('^');
}
}
buf.push('\n');
}
}
}
buf
}
impl fmt::Display for TemplateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match (self.line_no, self.column_no, &self.segment) {
(Some(line), Some(col), Some(seg)) => writeln!(
f,
"Template error: {}\n --> Template error in \"{}\":{}:{}\n |\n{} |\n = reason: {}",
self.reason(),
self.template_name
.as_ref()
.unwrap_or(&"Unnamed template".to_owned()),
line,
col,
seg,
self.reason()
),
_ => write!(f, "{}", self.reason()),
}
}
}
#[cfg(feature = "script_helper")]
#[derive(Debug, Error)]
pub enum ScriptError {
#[error(transparent)]
IoError(#[from] IOError),
#[error(transparent)]
ParseError(#[from] ParseError),
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_source_error() {
let reason = RenderErrorReason::TemplateNotFound("unnamed".to_owned());
let render_error = RenderError::from(reason);
let reason2 = render_error.source().unwrap();
assert!(matches!(
reason2.downcast_ref::<RenderErrorReason>().unwrap(),
RenderErrorReason::TemplateNotFound(_)
));
}
}