tinytemplate/
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!(f, "Failed to parse the template (line {}, column {}). Reason: {}", line, column, msg),
61            Error::RenderError { msg, line, column } => {
62                write!(f, "Encountered rendering error on line {}, column {}. Reason: {}", line, column, msg)
63            }
64            Error::SerdeError{ err } => {
65                write!(f, "Unexpected serde error while converting the context to a serde_json::Value. Error: {}", err)
66            }
67            Error::GenericError { msg } => {
68                write!(f, "{}", msg)
69            }
70            Error::StdFormatError{ err } => {
71                write!(f, "Unexpected formatting error: {}", err )
72            }
73            Error::CalledTemplateError{ name, err, line, column } => {
74                write!(f, "Call to sub-template \"{}\" on line {}, column {} failed. Reason: {}", name, line, column, err)
75            }
76            Error::CalledFormatterError{ name, err, line, column } => {
77                write!(f, "Call to value formatter \"{}\" on line {}, column {} failed. Reason: {}", name, line, column, err)
78            }
79            Error::__NonExhaustive => unreachable!(),
80        }
81    }
82}
83impl StdError for Error {
84    fn description(&self) -> &str {
85        match self {
86            Error::ParseError { .. } => "ParseError",
87            Error::RenderError { .. } => "RenderError",
88            Error::SerdeError { .. } => "SerdeError",
89            Error::GenericError { msg } => &msg,
90            Error::StdFormatError { .. } => "StdFormatError",
91            Error::CalledTemplateError { .. } => "CalledTemplateError",
92            Error::CalledFormatterError { .. } => "CalledFormatterError",
93            Error::__NonExhaustive => unreachable!(),
94        }
95    }
96}
97
98pub type Result<T> = ::std::result::Result<T, Error>;
99
100pub(crate) fn lookup_error(source: &str, step: &str, path: PathSlice, current: &Value) -> Error {
101    let avail_str = if let Value::Object(object_map) = current {
102        let mut avail_str = " Available values at this level are ".to_string();
103        for (i, key) in object_map.keys().enumerate() {
104            if i > 0 {
105                avail_str.push_str(", ");
106            }
107            avail_str.push('\'');
108            avail_str.push_str(key);
109            avail_str.push('\'');
110        }
111        avail_str
112    } else {
113        "".to_string()
114    };
115
116    let (line, column) = get_offset(source, step);
117
118    Error::RenderError {
119        msg: format!(
120            "Failed to find value '{}' from path '{}'.{}",
121            step,
122            path_to_str(path),
123            avail_str
124        ),
125        line,
126        column,
127    }
128}
129
130pub(crate) fn truthiness_error(source: &str, path: PathSlice) -> Error {
131    let (line, column) = get_offset(source, path.last().unwrap());
132    Error::RenderError {
133        msg: format!(
134            "Path '{}' produced a value which could not be checked for truthiness.",
135            path_to_str(path)
136        ),
137        line,
138        column,
139    }
140}
141
142pub(crate) fn unprintable_error() -> Error {
143    Error::GenericError {
144        msg: "Expected a printable value but found array or object.".to_string(),
145    }
146}
147
148pub(crate) fn not_iterable_error(source: &str, path: PathSlice) -> Error {
149    let (line, column) = get_offset(source, path.last().unwrap());
150    Error::RenderError {
151        msg: format!(
152            "Expected an array for path '{}' but found a non-iterable value.",
153            path_to_str(path)
154        ),
155        line,
156        column,
157    }
158}
159
160pub(crate) fn unknown_template(source: &str, name: &str) -> Error {
161    let (line, column) = get_offset(source, name);
162    Error::RenderError {
163        msg: format!("Tried to call an unknown template '{}'", name),
164        line,
165        column,
166    }
167}
168
169pub(crate) fn unknown_formatter(source: &str, name: &str) -> Error {
170    let (line, column) = get_offset(source, name);
171    Error::RenderError {
172        msg: format!("Tried to call an unknown formatter '{}'", name),
173        line,
174        column,
175    }
176}
177
178pub(crate) fn called_template_error(source: &str, template_name: &str, err: Error) -> Error {
179    let (line, column) = get_offset(source, template_name);
180    Error::CalledTemplateError {
181        name: template_name.to_string(),
182        err: Box::new(err),
183        line,
184        column,
185    }
186}
187
188pub(crate) fn called_formatter_error(source: &str, formatter_name: &str, err: Error) -> Error {
189    let (line, column) = get_offset(source, formatter_name);
190    Error::CalledFormatterError {
191        name: formatter_name.to_string(),
192        err: Box::new(err),
193        line,
194        column,
195    }
196}
197
198/// Find the line number and column of the target string within the source string. Will panic if
199/// target is not a substring of source.
200pub(crate) fn get_offset(source: &str, target: &str) -> (usize, usize) {
201    let offset = target.as_ptr() as isize - source.as_ptr() as isize;
202    let to_scan = &source[0..(offset as usize)];
203
204    let mut line = 1;
205    let mut column = 0;
206
207    for byte in to_scan.bytes() {
208        match byte as char {
209            '\n' => {
210                line += 1;
211                column = 0;
212            }
213            _ => {
214                column += 1;
215            }
216        }
217    }
218
219    (line, column)
220}