Repository: rinja-rs/rinja (rust)

Issue:
[Bug] `#[derive(Template)]` panics when using `include` with nested templates
I've built a small test to reproduce the bug, which you can see [here](https://github.com/rinja-rs/rinja/compare/master...LukeMathWalker:rinja:includes).

What I think is happening:

- The base template includes the fragment using a path prefix with the folder that contains it (i.e. `included/`)
- The prefixed path is not valid when used from the leaf template, where the correct path prefix would be `../included/`
- The leaf template is resolving the includes in the base template as if the base template belonged to the same folder of the leaf template, which isn't the case

I don't have a fix (yet), but putting this up here for visibility in case others bump into it.


Pull request description:
Canonicalize paths to ensure includes behave as expected when folders are involved
Fixes #288 

Patch:
diff --git a/rinja_derive/src/config.rs b/rinja_derive/src/config.rs
index ccd90dc4..6bfa17bc 100644
--- a/rinja_derive/src/config.rs
+++ b/rinja_derive/src/config.rs
@@ -182,27 +182,40 @@ impl Config {
         start_at: Option<&Path>,
         file_info: Option<FileInfo<'_>>,
     ) -> Result<Arc<Path>, CompileError> {
-        if let Some(root) = start_at {
-            let relative = root.with_file_name(path);
-            if relative.exists() {
-                return Ok(relative.into());
+        fn _find_template(config: &Config, path: &str, start_at: Option<&Path>) -> Option<PathBuf> {
+            if let Some(root) = start_at {
+                let relative = root.with_file_name(path);
+                if relative.exists() {
+                    return Some(relative);
+                }
             }
-        }
 
-        for dir in &self.dirs {
-            let rooted = dir.join(path);
-            if rooted.exists() {
-                return Ok(rooted.into());
+            for dir in &config.dirs {
+                let rooted = dir.join(path);
+                if rooted.exists() {
+                    return Some(rooted);
+                }
             }
+
+            None
         }
 
-        Err(CompileError::new(
-            format!(
-                "template {:?} not found in directories {:?}",
-                path, self.dirs
-            ),
-            file_info,
-        ))
+        let Some(path) = _find_template(self, path, start_at) else {
+            return Err(CompileError::new(
+                format!(
+                    "template {:?} not found in directories {:?}",
+                    path, self.dirs
+                ),
+                file_info,
+            ));
+        };
+        match path.canonicalize() {
+            Ok(p) => Ok(p.into()),
+            Err(e) => Err(CompileError::no_file_info(
+                format_args!("could not canonicalize path {path:?}: {e}"),
+                None,
+            )),
+        }
     }
 }
 
@@ -394,7 +407,14 @@ mod tests {
     }
 
     fn assert_eq_rooted(actual: &Path, expected: &str) {
-        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
+            .canonicalize()
+            .unwrap();
+        if root.ends_with("rinja_derive_standalone") {
+            root.pop();
+            root.push("rinja_derive");
+        }
+
         root.push("templates");
         let mut inner = PathBuf::new();
         inner.push(expected);


Test patch:
diff --git a/testing/Cargo.toml b/testing/Cargo.toml
index 650ebdbb..3b03019c 100644
--- a/testing/Cargo.toml
+++ b/testing/Cargo.toml
@@ -25,7 +25,7 @@ rinja = { path = "../rinja", version = "0.3.5", features = ["code-in-doc", "serd
 
 criterion = "0.5"
 phf = { version = "0.11", features = ["macros" ] }
-trybuild = "1.0.76"
+trybuild = "1.0.100"
 
 [[bench]]
 name = "all"
diff --git a/testing/templates/include-base.html b/testing/templates/include-base.html
new file mode 100644
index 00000000..60657815
--- /dev/null
+++ b/testing/templates/include-base.html
@@ -0,0 +1,2 @@
+{% include "included/fragment.html" %}
+{% block content %}{% endblock %}
diff --git a/testing/templates/included/fragment.html b/testing/templates/included/fragment.html
new file mode 100644
index 00000000..e69de29b
diff --git a/testing/templates/leaf-templates/includer.html b/testing/templates/leaf-templates/includer.html
new file mode 100644
index 00000000..fee37f45
--- /dev/null
+++ b/testing/templates/leaf-templates/includer.html
@@ -0,0 +1,3 @@
+{% extends "../include-base.html" %}
+
+{% block content %}{% endblock %}
diff --git a/testing/tests/include.rs b/testing/tests/include.rs
index 65af4fa4..82f7e1d1 100644
--- a/testing/tests/include.rs
+++ b/testing/tests/include.rs
@@ -13,6 +13,15 @@ fn test_include() {
     assert_eq!(s.render().unwrap(), "\n  INCLUDED: foo\n  INCLUDED: bar");
 }
 
+#[test]
+fn test_include_with_nested_paths() {
+    #[derive(Template)]
+    #[template(path = "leaf-templates/includer.html")]
+    struct LeafTemplate;
+
+    assert!(LeafTemplate.render().is_ok());
+}
+
 #[test]
 fn test_include_extends() {
     #[derive(Template)]
diff --git a/testing/tests/ui/cycle.stderr b/testing/tests/ui/cycle.stderr
index 90f26b53..bebec761 100644
--- a/testing/tests/ui/cycle.stderr
+++ b/testing/tests/ui/cycle.stderr
@@ -1,6 +1,6 @@
 error: cyclic dependency in graph [
-           "\"$WORKSPACE/target/tests/trybuild/rinja_testing/templates/cycle2.html/" --> \"$WORKSPACE/target/tests/trybuild/rinja_testing/templates/cycle1.html/"",
-           "\"$WORKSPACE/target/tests/trybuild/rinja_testing/templates/cycle1.html/" --> \"$WORKSPACE/target/tests/trybuild/rinja_testing/templates/cycle1.html/"",
+           "\"$DIR/templates/cycle2.html/" --> \"$DIR/templates/cycle1.html/"",
+           "\"$DIR/templates/cycle1.html/" --> \"$DIR/templates/cycle1.html/"",
        ]
  --> tests/ui/cycle.rs:4:19
   |
diff --git a/testing/tests/ui/cycle2.stderr b/testing/tests/ui/cycle2.stderr
index 08d135fe..ece5f3fc 100644
--- a/testing/tests/ui/cycle2.stderr
+++ b/testing/tests/ui/cycle2.stderr
@@ -1,5 +1,5 @@
 error: cyclic dependency in graph [
-           "\"$WORKSPACE/target/tests/trybuild/rinja_testing/templates/cycle1.html/" --> \"$WORKSPACE/target/tests/trybuild/rinja_testing/templates/cycle1.html/"",
+           "\"$DIR/templates/cycle1.html/" --> \"$DIR/templates/cycle1.html/"",
        ]
  --> tests/ui/cycle2.rs:4:19
   |
diff --git a/testing/tests/ui/include-a-folder.stderr b/testing/tests/ui/include-a-folder.stderr
index 08a1cef8..aa491399 100644
--- a/testing/tests/ui/include-a-folder.stderr
+++ b/testing/tests/ui/include-a-folder.stderr
@@ -1,4 +1,4 @@
-error: unable to open template file '$WORKSPACE/target/tests/trybuild/rinja_testing/templates/a_file_that_is_actually_a_folder.html': Is a directory (os error 21)
+error: unable to open template file '$DIR/templates/a_file_that_is_actually_a_folder.html': Is a directory (os error 21)
  --> YouCannotIncludeFolders.txt:1:2
        " include \"a_file_that_is_actually_a_folder.html\" %}"
  --> tests/ui/include-a-folder.rs:4:34