| 1 | //! Module containing the error type returned by TinyTemplate if an error occurs.
|
| 2 |
|
| 3 | use instruction::{path_to_str, PathSlice};
|
| 4 | use serde_json::Error as SerdeJsonError;
|
| 5 | use serde_json::Value;
|
| 6 | use std::error::Error as StdError;
|
| 7 | use std::fmt;
|
| 8 |
|
| 9 | /// Enum representing the potential errors that TinyTemplate can encounter.
|
| 10 | #[derive(Debug)]
|
| 11 | pub enum Error {
|
| 12 | ParseError {
|
| 13 | msg: String,
|
| 14 | line: usize,
|
| 15 | column: usize,
|
| 16 | },
|
| 17 | RenderError {
|
| 18 | msg: String,
|
| 19 | line: usize,
|
| 20 | column: usize,
|
| 21 | },
|
| 22 | SerdeError {
|
| 23 | err: SerdeJsonError,
|
| 24 | },
|
| 25 | GenericError {
|
| 26 | msg: String,
|
| 27 | },
|
| 28 | StdFormatError {
|
| 29 | err: fmt::Error,
|
| 30 | },
|
| 31 | CalledTemplateError {
|
| 32 | name: String,
|
| 33 | err: Box<Error>,
|
| 34 | line: usize,
|
| 35 | column: usize,
|
| 36 | },
|
| 37 | CalledFormatterError {
|
| 38 | name: String,
|
| 39 | err: Box<Error>,
|
| 40 | line: usize,
|
| 41 | column: usize,
|
| 42 | },
|
| 43 |
|
| 44 | #[doc (hidden)]
|
| 45 | __NonExhaustive,
|
| 46 | }
|
| 47 | impl From<SerdeJsonError> for Error {
|
| 48 | fn from(err: SerdeJsonError) -> Error {
|
| 49 | Error::SerdeError { err }
|
| 50 | }
|
| 51 | }
|
| 52 | impl From<fmt::Error> for Error {
|
| 53 | fn from(err: fmt::Error) -> Error {
|
| 54 | Error::StdFormatError { err }
|
| 55 | }
|
| 56 | }
|
| 57 | impl fmt::Display for Error {
|
| 58 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
| 59 | match self {
|
| 60 | Error::ParseError { msg, line, column } => write!(
|
| 61 | f,
|
| 62 | "Failed to parse the template (line {}, column {}). Reason: {}" ,
|
| 63 | line, column, msg
|
| 64 | ),
|
| 65 | Error::RenderError { msg, line, column } => {
|
| 66 | write!(
|
| 67 | f,
|
| 68 | "Encountered rendering error on line {}, column {}. Reason: {}" ,
|
| 69 | line, column, msg
|
| 70 | )
|
| 71 | }
|
| 72 | Error::SerdeError { err } => {
|
| 73 | write!(f, "Unexpected serde error while converting the context to a serde_json::Value. Error: {}" , err)
|
| 74 | }
|
| 75 | Error::GenericError { msg } => {
|
| 76 | write!(f, "{}" , msg)
|
| 77 | }
|
| 78 | Error::StdFormatError { err } => {
|
| 79 | write!(f, "Unexpected formatting error: {}" , err)
|
| 80 | }
|
| 81 | Error::CalledTemplateError {
|
| 82 | name,
|
| 83 | err,
|
| 84 | line,
|
| 85 | column,
|
| 86 | } => {
|
| 87 | write!(
|
| 88 | f,
|
| 89 | "Call to sub-template \"{} \" on line {}, column {} failed. Reason: {}" ,
|
| 90 | name, line, column, err
|
| 91 | )
|
| 92 | }
|
| 93 | Error::CalledFormatterError {
|
| 94 | name,
|
| 95 | err,
|
| 96 | line,
|
| 97 | column,
|
| 98 | } => {
|
| 99 | write!(
|
| 100 | f,
|
| 101 | "Call to value formatter \"{} \" on line {}, column {} failed. Reason: {}" ,
|
| 102 | name, line, column, err
|
| 103 | )
|
| 104 | }
|
| 105 | Error::__NonExhaustive => unreachable!(),
|
| 106 | }
|
| 107 | }
|
| 108 | }
|
| 109 | impl StdError for Error {
|
| 110 | fn description(&self) -> &str {
|
| 111 | match self {
|
| 112 | Error::ParseError { .. } => "ParseError" ,
|
| 113 | Error::RenderError { .. } => "RenderError" ,
|
| 114 | Error::SerdeError { .. } => "SerdeError" ,
|
| 115 | Error::GenericError { msg } => &msg,
|
| 116 | Error::StdFormatError { .. } => "StdFormatError" ,
|
| 117 | Error::CalledTemplateError { .. } => "CalledTemplateError" ,
|
| 118 | Error::CalledFormatterError { .. } => "CalledFormatterError" ,
|
| 119 | Error::__NonExhaustive => unreachable!(),
|
| 120 | }
|
| 121 | }
|
| 122 | }
|
| 123 |
|
| 124 | pub type Result<T> = ::std::result::Result<T, Error>;
|
| 125 |
|
| 126 | pub(crate) fn lookup_error(source: &str, step: &str, path: PathSlice, current: &Value) -> Error {
|
| 127 | let avail_str = if let Value::Object(object_map) = current {
|
| 128 | let mut avail_str = " Available values at this level are " .to_string();
|
| 129 | for (i, key) in object_map.keys().enumerate() {
|
| 130 | if i > 0 {
|
| 131 | avail_str.push_str(", " );
|
| 132 | }
|
| 133 | avail_str.push(' \'' );
|
| 134 | avail_str.push_str(key);
|
| 135 | avail_str.push(' \'' );
|
| 136 | }
|
| 137 | avail_str
|
| 138 | } else {
|
| 139 | "" .to_string()
|
| 140 | };
|
| 141 |
|
| 142 | let (line, column) = get_offset(source, step);
|
| 143 |
|
| 144 | Error::RenderError {
|
| 145 | msg: format!(
|
| 146 | "Failed to find value '{}' from path '{}'.{}" ,
|
| 147 | step,
|
| 148 | path_to_str(path),
|
| 149 | avail_str
|
| 150 | ),
|
| 151 | line,
|
| 152 | column,
|
| 153 | }
|
| 154 | }
|
| 155 |
|
| 156 | pub(crate) fn truthiness_error(source: &str, path: PathSlice) -> Error {
|
| 157 | let (line, column) = get_offset(source, path.last().unwrap());
|
| 158 | Error::RenderError {
|
| 159 | msg: format!(
|
| 160 | "Path '{}' produced a value which could not be checked for truthiness." ,
|
| 161 | path_to_str(path)
|
| 162 | ),
|
| 163 | line,
|
| 164 | column,
|
| 165 | }
|
| 166 | }
|
| 167 |
|
| 168 | pub(crate) fn unprintable_error() -> Error {
|
| 169 | Error::GenericError {
|
| 170 | msg: "Expected a printable value but found array or object." .to_string(),
|
| 171 | }
|
| 172 | }
|
| 173 |
|
| 174 | pub(crate) fn not_iterable_error(source: &str, path: PathSlice) -> Error {
|
| 175 | let (line, column) = get_offset(source, path.last().unwrap());
|
| 176 | Error::RenderError {
|
| 177 | msg: format!(
|
| 178 | "Expected an array for path '{}' but found a non-iterable value." ,
|
| 179 | path_to_str(path)
|
| 180 | ),
|
| 181 | line,
|
| 182 | column,
|
| 183 | }
|
| 184 | }
|
| 185 |
|
| 186 | pub(crate) fn unknown_template(source: &str, name: &str) -> Error {
|
| 187 | let (line, column) = get_offset(source, name);
|
| 188 | Error::RenderError {
|
| 189 | msg: format!("Tried to call an unknown template '{}'" , name),
|
| 190 | line,
|
| 191 | column,
|
| 192 | }
|
| 193 | }
|
| 194 |
|
| 195 | pub(crate) fn unknown_formatter(source: &str, name: &str) -> Error {
|
| 196 | let (line, column) = get_offset(source, name);
|
| 197 | Error::RenderError {
|
| 198 | msg: format!("Tried to call an unknown formatter '{}'" , name),
|
| 199 | line,
|
| 200 | column,
|
| 201 | }
|
| 202 | }
|
| 203 |
|
| 204 | pub(crate) fn called_template_error(source: &str, template_name: &str, err: Error) -> Error {
|
| 205 | let (line, column) = get_offset(source, template_name);
|
| 206 | Error::CalledTemplateError {
|
| 207 | name: template_name.to_string(),
|
| 208 | err: Box::new(err),
|
| 209 | line,
|
| 210 | column,
|
| 211 | }
|
| 212 | }
|
| 213 |
|
| 214 | pub(crate) fn called_formatter_error(source: &str, formatter_name: &str, err: Error) -> Error {
|
| 215 | let (line, column) = get_offset(source, formatter_name);
|
| 216 | Error::CalledFormatterError {
|
| 217 | name: formatter_name.to_string(),
|
| 218 | err: Box::new(err),
|
| 219 | line,
|
| 220 | column,
|
| 221 | }
|
| 222 | }
|
| 223 |
|
| 224 | /// Find the line number and column of the target string within the source string. Will panic if
|
| 225 | /// target is not a substring of source.
|
| 226 | pub(crate) fn get_offset(source: &str, target: &str) -> (usize, usize) {
|
| 227 | let offset = target.as_ptr() as isize - source.as_ptr() as isize;
|
| 228 | let to_scan = &source[0..(offset as usize)];
|
| 229 |
|
| 230 | let mut line = 1;
|
| 231 | let mut column = 0;
|
| 232 |
|
| 233 | for byte in to_scan.bytes() {
|
| 234 | match byte as char {
|
| 235 | ' \n' => {
|
| 236 | line += 1;
|
| 237 | column = 0;
|
| 238 | }
|
| 239 | _ => {
|
| 240 | column += 1;
|
| 241 | }
|
| 242 | }
|
| 243 | }
|
| 244 |
|
| 245 | (line, column)
|
| 246 | }
|
| 247 | |