1//! Module containing the error type returned by TinyTemplate if an error occurs.
2
3use instruction::{path_to_str, PathSlice};
4use serde_json::Error as SerdeJsonError;
5use serde_json::Value;
6use std::error::Error as StdError;
7use std::fmt;
8
9/// Enum representing the potential errors that TinyTemplate can encounter.
10#[derive(Debug)]
11pub 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}
47impl From<SerdeJsonError> for Error {
48 fn from(err: SerdeJsonError) -> Error {
49 Error::SerdeError { err }
50 }
51}
52impl From<fmt::Error> for Error {
53 fn from(err: fmt::Error) -> Error {
54 Error::StdFormatError { err }
55 }
56}
57impl 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}
109impl 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
124pub type Result<T> = ::std::result::Result<T, Error>;
125
126pub(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
156pub(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
168pub(crate) fn unprintable_error() -> Error {
169 Error::GenericError {
170 msg: "Expected a printable value but found array or object.".to_string(),
171 }
172}
173
174pub(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
186pub(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
195pub(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
204pub(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
214pub(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.
226pub(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