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

sunng87 / handlebars-rust / 19895206288

26 Nov 2025 11:52PM UTC coverage: 83.822% (-0.04%) from 83.864%
19895206288

push

github

web-flow
chore(deps-dev): bump node-forge from 1.3.1 to 1.3.2 in /playground/www (#727)

Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.1 to 1.3.2.
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-version: 1.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

1658 of 1978 relevant lines covered (83.82%)

6.92 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

289
fn template_segment(template_str: &str, line: usize, col: usize) -> String {
1✔
290
    let range = 3;
1✔
291
    let line_start = line.saturating_sub(range);
1✔
292
    let line_end = line + range;
1✔
293

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

312
    buf
2✔
313
}
314

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

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

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

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

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

355
        let reason2 = render_error.source().unwrap();
356
        assert!(matches!(
357
            reason2.downcast_ref::<RenderErrorReason>().unwrap(),
358
            RenderErrorReason::TemplateNotFound(_)
359
        ));
360
    }
361
}
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