//! Module containing the error type returned by TinyTemplate if an error occurs. use instruction::{path_to_str, PathSlice}; use serde_json::Error as SerdeJsonError; use serde_json::Value; use std::error::Error as StdError; use std::fmt; /// Enum representing the potential errors that TinyTemplate can encounter. #[derive(Debug)] pub enum Error { ParseError { msg: String, line: usize, column: usize, }, RenderError { msg: String, line: usize, column: usize, }, SerdeError { err: SerdeJsonError, }, GenericError { msg: String, }, StdFormatError { err: fmt::Error, }, CalledTemplateError { name: String, err: Box, line: usize, column: usize, }, CalledFormatterError { name: String, err: Box, line: usize, column: usize, }, #[doc(hidden)] __NonExhaustive, } impl From for Error { fn from(err: SerdeJsonError) -> Error { Error::SerdeError { err } } } impl From for Error { fn from(err: fmt::Error) -> Error { Error::StdFormatError { err } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::ParseError { msg, line, column } => write!( f, "Failed to parse the template (line {}, column {}). Reason: {}", line, column, msg ), Error::RenderError { msg, line, column } => { write!( f, "Encountered rendering error on line {}, column {}. Reason: {}", line, column, msg ) } Error::SerdeError { err } => { write!(f, "Unexpected serde error while converting the context to a serde_json::Value. Error: {}", err) } Error::GenericError { msg } => { write!(f, "{}", msg) } Error::StdFormatError { err } => { write!(f, "Unexpected formatting error: {}", err) } Error::CalledTemplateError { name, err, line, column, } => { write!( f, "Call to sub-template \"{}\" on line {}, column {} failed. Reason: {}", name, line, column, err ) } Error::CalledFormatterError { name, err, line, column, } => { write!( f, "Call to value formatter \"{}\" on line {}, column {} failed. Reason: {}", name, line, column, err ) } Error::__NonExhaustive => unreachable!(), } } } impl StdError for Error { fn description(&self) -> &str { match self { Error::ParseError { .. } => "ParseError", Error::RenderError { .. } => "RenderError", Error::SerdeError { .. } => "SerdeError", Error::GenericError { msg } => &msg, Error::StdFormatError { .. } => "StdFormatError", Error::CalledTemplateError { .. } => "CalledTemplateError", Error::CalledFormatterError { .. } => "CalledFormatterError", Error::__NonExhaustive => unreachable!(), } } } pub type Result = ::std::result::Result; pub(crate) fn lookup_error(source: &str, step: &str, path: PathSlice, current: &Value) -> Error { let avail_str = if let Value::Object(object_map) = current { let mut avail_str = " Available values at this level are ".to_string(); for (i, key) in object_map.keys().enumerate() { if i > 0 { avail_str.push_str(", "); } avail_str.push('\''); avail_str.push_str(key); avail_str.push('\''); } avail_str } else { "".to_string() }; let (line, column) = get_offset(source, step); Error::RenderError { msg: format!( "Failed to find value '{}' from path '{}'.{}", step, path_to_str(path), avail_str ), line, column, } } pub(crate) fn truthiness_error(source: &str, path: PathSlice) -> Error { let (line, column) = get_offset(source, path.last().unwrap()); Error::RenderError { msg: format!( "Path '{}' produced a value which could not be checked for truthiness.", path_to_str(path) ), line, column, } } pub(crate) fn unprintable_error() -> Error { Error::GenericError { msg: "Expected a printable value but found array or object.".to_string(), } } pub(crate) fn not_iterable_error(source: &str, path: PathSlice) -> Error { let (line, column) = get_offset(source, path.last().unwrap()); Error::RenderError { msg: format!( "Expected an array for path '{}' but found a non-iterable value.", path_to_str(path) ), line, column, } } pub(crate) fn unknown_template(source: &str, name: &str) -> Error { let (line, column) = get_offset(source, name); Error::RenderError { msg: format!("Tried to call an unknown template '{}'", name), line, column, } } pub(crate) fn unknown_formatter(source: &str, name: &str) -> Error { let (line, column) = get_offset(source, name); Error::RenderError { msg: format!("Tried to call an unknown formatter '{}'", name), line, column, } } pub(crate) fn called_template_error(source: &str, template_name: &str, err: Error) -> Error { let (line, column) = get_offset(source, template_name); Error::CalledTemplateError { name: template_name.to_string(), err: Box::new(err), line, column, } } pub(crate) fn called_formatter_error(source: &str, formatter_name: &str, err: Error) -> Error { let (line, column) = get_offset(source, formatter_name); Error::CalledFormatterError { name: formatter_name.to_string(), err: Box::new(err), line, column, } } /// Find the line number and column of the target string within the source string. Will panic if /// target is not a substring of source. pub(crate) fn get_offset(source: &str, target: &str) -> (usize, usize) { let offset = target.as_ptr() as isize - source.as_ptr() as isize; let to_scan = &source[0..(offset as usize)]; let mut line = 1; let mut column = 0; for byte in to_scan.bytes() { match byte as char { '\n' => { line += 1; column = 0; } _ => { column += 1; } } } (line, column) }