• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sunng87 / handlebars-rust / 9558872391

18 Jun 2024 04:14AM UTC coverage: 81.461% (-0.1%) from 81.561%
9558872391

push

github

web-flow
Merge pull request #647 from sunng87/refactor/non_exhaustive

refactor: add non_exhaustive to all pub items with pub members

1472 of 1807 relevant lines covered (81.46%)

8.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.01
/src/error.rs
1
use std::error::Error as StdError;
2
use std::fmt::{self, Write};
3
use std::io::Error as IOError;
4
use std::string::FromUtf8Error;
5

6
use serde_json::error::Error as SerdeError;
7
use thiserror::Error;
8

9
#[cfg(feature = "dir_source")]
10
use walkdir::Error as WalkdirError;
11

12
#[cfg(feature = "script_helper")]
13
use rhai::{EvalAltResult, ParseError};
14

15
/// Error when rendering data on template.
16
#[non_exhaustive]
17
#[derive(Debug)]
18
pub struct RenderError {
19
    pub template_name: Option<String>,
20
    pub line_no: Option<usize>,
21
    pub column_no: Option<usize>,
22
    reason: Box<RenderErrorReason>,
23
    unimplemented: bool,
24
    // backtrace: Backtrace,
25
}
26

27
impl fmt::Display for RenderError {
×
28
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
×
29
        let desc = self.reason.to_string();
×
30

31
        match (self.line_no, self.column_no) {
×
32
            (Some(line), Some(col)) => write!(
×
33
                f,
×
34
                "Error rendering \"{}\" line {}, col {}: {}",
35
                self.template_name.as_deref().unwrap_or("Unnamed template"),
2✔
36
                line,
×
37
                col,
×
38
                desc
×
39
            ),
40
            _ => write!(f, "{}", desc),
×
41
        }
42
    }
43
}
44

45
impl From<IOError> for RenderError {
×
46
    fn from(e: IOError) -> RenderError {
×
47
        RenderErrorReason::IOError(e).into()
×
48
    }
49
}
50

51
impl From<FromUtf8Error> for RenderError {
×
52
    fn from(e: FromUtf8Error) -> Self {
×
53
        RenderErrorReason::Utf8Error(e).into()
×
54
    }
55
}
56

57
impl From<TemplateError> for RenderError {
×
58
    fn from(e: TemplateError) -> Self {
×
59
        RenderErrorReason::TemplateError(e).into()
×
60
    }
61
}
62

63
/// Template rendering error
64
#[non_exhaustive]
65
#[derive(Debug, Error)]
66
pub enum RenderErrorReason {
67
    #[error("Template not found {0}")]
68
    TemplateNotFound(String),
69
    #[error("Failed to parse template {0}")]
70
    TemplateError(
71
        #[from]
72
        #[source]
73
        TemplateError,
74
    ),
75
    #[error("Failed to access variable in strict mode {0:?}")]
76
    MissingVariable(Option<String>),
77
    #[error("Partial not found {0}")]
78
    PartialNotFound(String),
79
    #[error("Helper not found {0}")]
80
    HelperNotFound(String),
81
    #[error("Helper/Decorator {0} param at index {1} required but not found")]
82
    ParamNotFoundForIndex(&'static str, usize),
83
    #[error("Helper/Decorator {0} param with name {1} required but not found")]
84
    ParamNotFoundForName(&'static str, String),
85
    #[error("Helper/Decorator {0} param with name {1} type mismatch for {2}")]
86
    ParamTypeMismatchForName(&'static str, String, String),
87
    #[error("Helper/Decorator {0} hash with name {1} type mismatch for {2}")]
88
    HashTypeMismatchForName(&'static str, String, String),
89
    #[error("Decorator not found {0}")]
90
    DecoratorNotFound(String),
91
    #[error("Can not include current template in partial")]
92
    CannotIncludeSelf,
93
    #[error("Invalid logging level: {0}")]
94
    InvalidLoggingLevel(String),
95
    #[error("Invalid param type, {0} expected")]
96
    InvalidParamType(&'static str),
97
    #[error("Block content required")]
98
    BlockContentRequired,
99
    #[error("Invalid json path {0}")]
100
    InvalidJsonPath(String),
101
    #[error("Cannot access array/vector with string index, {0}")]
102
    InvalidJsonIndex(String),
103
    #[error("Failed to access JSON data: {0}")]
104
    SerdeError(
105
        #[from]
106
        #[source]
107
        SerdeError,
108
    ),
109
    #[error("IO Error: {0}")]
110
    IOError(
111
        #[from]
112
        #[source]
113
        IOError,
114
    ),
115
    #[error("FromUtf8Error: {0}")]
116
    Utf8Error(
117
        #[from]
118
        #[source]
119
        FromUtf8Error,
120
    ),
121
    #[error("Nested error: {0}")]
122
    NestedError(#[source] Box<dyn StdError + Send + Sync + 'static>),
123
    #[cfg(feature = "script_helper")]
124
    #[error("Cannot convert data to Rhai dynamic: {0}")]
125
    ScriptValueError(
126
        #[from]
127
        #[source]
128
        Box<EvalAltResult>,
129
    ),
130
    #[cfg(feature = "script_helper")]
131
    #[error("Failed to load rhai script: {0}")]
132
    ScriptLoadError(
133
        #[from]
134
        #[source]
135
        ScriptError,
136
    ),
137
    #[error("Unimplemented")]
138
    Unimplemented,
139
    #[error("{0}")]
140
    Other(String),
141
}
142

143
impl From<RenderErrorReason> for RenderError {
×
144
    fn from(e: RenderErrorReason) -> RenderError {
2✔
145
        RenderError {
146
            template_name: None,
147
            line_no: None,
148
            column_no: None,
149
            reason: Box::new(e),
×
150
            unimplemented: false,
151
        }
152
    }
153
}
154

155
impl From<RenderError> for RenderErrorReason {
×
156
    fn from(e: RenderError) -> Self {
×
157
        *e.reason
×
158
    }
159
}
160

161
impl RenderError {
×
162
    #[deprecated(since = "5.0.0", note = "Use RenderErrorReason instead")]
×
163
    pub fn new<T: AsRef<str>>(desc: T) -> RenderError {
×
164
        RenderErrorReason::Other(desc.as_ref().to_string()).into()
×
165
    }
166

167
    pub fn strict_error(path: Option<&String>) -> RenderError {
1✔
168
        RenderErrorReason::MissingVariable(path.map(|p| p.to_owned())).into()
3✔
169
    }
170

171
    #[deprecated(since = "5.0.0", note = "Use RenderErrorReason::NestedError instead")]
×
172
    pub fn from_error<E>(_error_info: &str, cause: E) -> RenderError
×
173
    where
174
        E: StdError + Send + Sync + 'static,
175
    {
176
        RenderErrorReason::NestedError(Box::new(cause)).into()
×
177
    }
178

179
    #[inline]
×
180
    pub(crate) fn is_unimplemented(&self) -> bool {
2✔
181
        matches!(*self.reason, RenderErrorReason::Unimplemented)
2✔
182
    }
183

184
    /// Get `RenderErrorReason` for this error
185
    pub fn reason(&self) -> &RenderErrorReason {
2✔
186
        self.reason.as_ref()
2✔
187
    }
188
}
189

190
impl StdError for RenderError {
×
191
    fn source(&self) -> Option<&(dyn StdError + 'static)> {
1✔
192
        Some(self.reason())
1✔
193
    }
194
}
195

196
/// Template parsing error
197
#[non_exhaustive]
198
#[derive(Debug, Error)]
199
pub enum TemplateErrorReason {
200
    #[error("helper {0:?} was opened, but {1:?} is closing")]
201
    MismatchingClosedHelper(String, String),
202
    #[error("decorator {0:?} was opened, but {1:?} is closing")]
203
    MismatchingClosedDecorator(String, String),
204
    #[error("invalid handlebars syntax: {0}")]
205
    InvalidSyntax(String),
206
    #[error("invalid parameter {0:?}")]
207
    InvalidParam(String),
208
    #[error("nested subexpression is not supported")]
209
    NestedSubexpression,
210
    #[error("Template \"{1}\": {0}")]
211
    IoError(IOError, String),
212
    #[cfg(feature = "dir_source")]
213
    #[error("Walk dir error: {err}")]
214
    WalkdirError {
215
        #[from]
216
        err: WalkdirError,
217
    },
218
}
219

220
/// Error on parsing template.
221
#[derive(Debug, Error)]
222
pub struct TemplateError {
223
    reason: Box<TemplateErrorReason>,
224
    template_name: Option<String>,
225
    line_no: Option<usize>,
226
    column_no: Option<usize>,
227
    segment: Option<String>,
228
}
229

230
impl TemplateError {
×
231
    #[allow(deprecated)]
×
232
    pub fn of(e: TemplateErrorReason) -> TemplateError {
1✔
233
        TemplateError {
234
            reason: Box::new(e),
×
235
            template_name: None,
236
            line_no: None,
237
            column_no: None,
238
            segment: None,
239
        }
240
    }
241

242
    pub fn at(mut self, template_str: &str, line_no: usize, column_no: usize) -> TemplateError {
1✔
243
        self.line_no = Some(line_no);
1✔
244
        self.column_no = Some(column_no);
1✔
245
        self.segment = Some(template_segment(template_str, line_no, column_no));
2✔
246
        self
1✔
247
    }
248

249
    pub fn in_template(mut self, name: String) -> TemplateError {
1✔
250
        self.template_name = Some(name);
2✔
251
        self
1✔
252
    }
253

254
    /// Get underlying reason for the error
255
    pub fn reason(&self) -> &TemplateErrorReason {
1✔
256
        &self.reason
1✔
257
    }
258

259
    /// Get the line number and column number of this error
260
    pub fn pos(&self) -> Option<(usize, usize)> {
1✔
261
        match (self.line_no, self.column_no) {
1✔
262
            (Some(line_no), Some(column_no)) => Some((line_no, column_no)),
1✔
263
            _ => None,
×
264
        }
265
    }
266

267
    /// Get template name of this error
268
    /// Returns `None` when the template has no associated name
269
    pub fn name(&self) -> Option<&String> {
×
270
        self.template_name.as_ref()
×
271
    }
272
}
273

274
impl From<(IOError, String)> for TemplateError {
275
    fn from(err_info: (IOError, String)) -> TemplateError {
×
276
        let (e, name) = err_info;
×
277
        TemplateError::of(TemplateErrorReason::IoError(e, name))
×
278
    }
279
}
280

281
#[cfg(feature = "dir_source")]
282
impl From<WalkdirError> for TemplateError {
283
    fn from(e: WalkdirError) -> TemplateError {
×
284
        TemplateError::of(TemplateErrorReason::from(e))
×
285
    }
286
}
287

288
fn template_segment(template_str: &str, line: usize, col: usize) -> String {
1✔
289
    let range = 3;
1✔
290
    let line_start = if line >= range { line - range } else { 0 };
2✔
291
    let line_end = line + range;
2✔
292

293
    let mut buf = String::new();
1✔
294
    for (line_count, line_content) in template_str.lines().enumerate() {
3✔
295
        if line_count >= line_start && line_count <= line_end {
1✔
296
            let _ = writeln!(&mut buf, "{line_count:4} | {line_content}");
3✔
297
            if line_count == line - 1 {
1✔
298
                buf.push_str("     |");
1✔
299
                for c in 0..line_content.len() {
2✔
300
                    if c != col {
1✔
301
                        buf.push('-');
2✔
302
                    } else {
303
                        buf.push('^');
2✔
304
                    }
305
                }
306
                buf.push('\n');
2✔
307
            }
308
        }
309
    }
310

311
    buf
1✔
312
}
313

314
impl fmt::Display for TemplateError {
315
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
×
316
        match (self.line_no, self.column_no, &self.segment) {
×
317
            (Some(line), Some(col), Some(seg)) => writeln!(
×
318
                f,
319
                "Template error: {}\n    --> Template error in \"{}\":{}:{}\n     |\n{}     |\n     = reason: {}",
320
                self.reason(),
×
321
                self.template_name
×
322
                    .as_ref()
323
                    .unwrap_or(&"Unnamed template".to_owned()),
×
324
                line,
325
                col,
326
                seg,
×
327
                self.reason()
×
328
            ),
329
            _ => write!(f, "{}", self.reason()),
×
330
        }
331
    }
332
}
333

334
#[cfg(feature = "script_helper")]
335
#[non_exhaustive]
336
#[derive(Debug, Error)]
337
pub enum ScriptError {
338
    #[error(transparent)]
339
    IoError(#[from] IOError),
340

341
    #[error(transparent)]
342
    ParseError(#[from] ParseError),
343
}
344

345
#[cfg(test)]
346
mod test {
347
    use super::*;
348

349
    #[test]
350
    fn test_source_error() {
351
        let reason = RenderErrorReason::TemplateNotFound("unnamed".to_owned());
352
        let render_error = RenderError::from(reason);
353

354
        let reason2 = render_error.source().unwrap();
355
        assert!(matches!(
356
            reason2.downcast_ref::<RenderErrorReason>().unwrap(),
357
            RenderErrorReason::TemplateNotFound(_)
358
        ));
359
    }
360
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc