tinytemplate_async/
error.rs

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 = source.find(target).unwrap();
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}