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 {
from(err: SerdeJsonError) -> Error48     fn from(err: SerdeJsonError) -> Error {
49         Error::SerdeError { err }
50     }
51 }
52 impl From<fmt::Error> for Error {
from(err: fmt::Error) -> Error53     fn from(err: fmt::Error) -> Error {
54         Error::StdFormatError { err }
55     }
56 }
57 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result58     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 {
description(&self) -> &str110     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 
lookup_error(source: &str, step: &str, path: PathSlice, current: &Value) -> Error126 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 
truthiness_error(source: &str, path: PathSlice) -> Error156 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 
unprintable_error() -> Error168 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 
not_iterable_error(source: &str, path: PathSlice) -> Error174 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 
unknown_template(source: &str, name: &str) -> Error186 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 
unknown_formatter(source: &str, name: &str) -> Error195 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 
called_template_error(source: &str, template_name: &str, err: Error) -> Error204 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 
called_formatter_error(source: &str, formatter_name: &str, err: Error) -> Error214 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.
get_offset(source: &str, target: &str) -> (usize, usize)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