From feef600c193c587c7b4a736090a41791427a30cf Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Tue, 21 Apr 2026 11:45:59 +0200
Subject: [PATCH 01/18] Fix size hint
---
tera/src/tera.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tera/src/tera.rs b/tera/src/tera.rs
index 9260a08..ad0bc72 100644
--- a/tera/src/tera.rs
+++ b/tera/src/tera.rs
@@ -626,7 +626,7 @@ impl Tera {
// 3rd loop: we actually set everything we've done on the templates objects
for (name, tpl) in self.templates.iter_mut() {
- tpl.raw_content_num_bytes += tpl_size_hint.remove(name.as_str()).unwrap();
+ tpl.raw_content_num_bytes = tpl_size_hint.remove(name.as_str()).unwrap();
tpl.parents = tpl_parents.remove(name.as_str()).unwrap();
tpl.block_lineage = tpl_blocks.remove(name.as_str()).unwrap();
}
From 481669748efc07e7aba2478cdd87743062d600a1 Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Tue, 21 Apr 2026 12:49:23 +0200
Subject: [PATCH 02/18] Fix TODO in pow
---
tera/src/value/number.rs | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/tera/src/value/number.rs b/tera/src/value/number.rs
index 26b0e0d..4587924 100644
--- a/tera/src/value/number.rs
+++ b/tera/src/value/number.rs
@@ -149,19 +149,29 @@ pub(crate) fn floor_div(lhs: &Value, rhs: &Value) -> TeraResult {
pub(crate) fn pow(lhs: &Value, rhs: &Value) -> TeraResult {
match (lhs.as_number(), rhs.as_number()) {
(Some(mut left), Some(mut right)) => {
- if left.is_float() || right.is_float() {
+ // Convert to float is one of them is or if exponent is < 0
+ let negative_int_exp = matches!(right, Number::Integer(b) if b < 0);
+ if left.is_float() || right.is_float() || negative_int_exp {
left = left.into_float();
right = right.into_float();
}
let val = match (left, right) {
- // TODO: check that the exponent can fit in a u32 and error otherwise?
- (Number::Integer(a), Number::Integer(b)) => match a.checked_pow(b as u32) {
- Some(val) => Value::from(val),
- None => {
- return Err(Error::message(format!("Unable to perform {lhs} ** {rhs}")));
+ (Number::Integer(a), Number::Integer(b)) => {
+ let exp = u32::try_from(b).map_err(|_| {
+ Error::message(format!(
+ "Exponent {b} is out of range for integer ** (must fit in u32)"
+ ))
+ })?;
+ match a.checked_pow(exp) {
+ Some(val) => Value::from(val),
+ None => {
+ return Err(Error::message(format!(
+ "Unable to perform {lhs} ** {rhs}"
+ )));
+ }
}
- },
+ }
(Number::Float(a), Number::Float(b)) => Value::from(a.powf(b)),
_ => unreachable!(),
};
From 18d44be3db5a0c7fb15e4a97307baefc0b269e34 Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Tue, 21 Apr 2026 12:50:33 +0200
Subject: [PATCH 03/18] Error on invalid UTF-8 file path
---
tera/src/tera.rs | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/tera/src/tera.rs b/tera/src/tera.rs
index ad0bc72..7bd824b 100644
--- a/tera/src/tera.rs
+++ b/tera/src/tera.rs
@@ -696,7 +696,10 @@ impl Tera {
/// the new set of templates.
fn add_file>(&mut self, path: P, name: Option<&str>) -> TeraResult<()> {
let path = path.as_ref();
- let tpl_name = name.unwrap_or_else(|| path.to_str().unwrap());
+ let path_str = path.to_str().ok_or_else(|| {
+ Error::message(format!("Template path is not valid UTF-8: {:?}", path))
+ })?;
+ let tpl_name = name.unwrap_or(path_str);
let mut f = File::open(path)
.map_err(|e| Error::chain(format!("Couldn't open template '{:?}'", path), e))?;
@@ -708,7 +711,7 @@ impl Tera {
let template = Template::new(
tpl_name,
&content,
- Some(path.to_str().unwrap().to_string()),
+ Some(path_str.to_string()),
self.delimiters,
)?;
@@ -1264,6 +1267,19 @@ mod tests {
);
}
+ #[cfg(unix)]
+ #[test]
+ fn add_template_file_errors_on_non_utf8_path() {
+ use std::ffi::OsStr;
+ use std::os::unix::ffi::OsStrExt;
+ use std::path::PathBuf;
+
+ let bad = PathBuf::from(OsStr::from_bytes(b"/tmp/\xff\xfe.html"));
+ let mut tera = Tera::default();
+ let err = tera.add_template_file(&bad, None).unwrap_err();
+ assert!(format!("{err}").contains("not valid UTF-8"));
+ }
+
#[test]
fn custom_delimiters() {
let mut tera = Tera::new();
From 78c5bed396e0a51503ee9135e965f3725a25baa1 Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Tue, 21 Apr 2026 13:02:17 +0200
Subject: [PATCH 04/18] Use char len for lexer rather than 1
---
tera/src/parsing/lexer.rs | 2 +-
...arser__parser_templates_success@tpl_simple.txt.snap | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/tera/src/parsing/lexer.rs b/tera/src/parsing/lexer.rs
index f366ca2..6422167 100644
--- a/tera/src/parsing/lexer.rs
+++ b/tera/src/parsing/lexer.rs
@@ -265,7 +265,7 @@ fn basic_tokenize(
($num_bytes:expr) => {{
let (skipped, new_rest) = rest.split_at($num_bytes);
for c in skipped.chars() {
- current_byte += 1;
+ current_byte += c.len_utf8();
match c {
'\n' => {
current_line += 1;
diff --git a/tera/src/snapshot_tests/snapshots/tera__snapshot_tests__parser__parser_templates_success@tpl_simple.txt.snap b/tera/src/snapshot_tests/snapshots/tera__snapshot_tests__parser__parser_templates_success@tpl_simple.txt.snap
index 4e01e9d..553e52f 100644
--- a/tera/src/snapshot_tests/snapshots/tera__snapshot_tests__parser__parser_templates_success@tpl_simple.txt.snap
+++ b/tera/src/snapshot_tests/snapshots/tera__snapshot_tests__parser__parser_templates_success@tpl_simple.txt.snap
@@ -47,15 +47,15 @@ input_file: tera/src/snapshot_tests/parser_inputs/success/tpl/tpl_simple.txt
left: GetAttr {
expr: Var {
name: "product",
- } @ 9:11-9:18 (207..214),
+ } @ 9:11-9:18 (208..215),
name: "price",
optional: false,
- } @ 9:19-9:24 (215..220),
- right: 1.2 @ 9:27-9:31 (223..227),
- } @ 9:11-9:31 (207..227),
+ } @ 9:19-9:24 (216..221),
+ right: 1.2 @ 9:27-9:31 (224..228),
+ } @ 9:11-9:31 (208..228),
" (VAT inc.)
\n Look at reviews from your friends ",
Var {
name: "username",
- } @ 10:44-10:52 (290..298),
+ } @ 10:44-10:52 (291..299),
"
\n \n