Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent template path behavior across platforms #383

Closed
vallentin opened this issue Nov 11, 2020 · 3 comments
Closed

Inconsistent template path behavior across platforms #383

vallentin opened this issue Nov 11, 2020 · 3 comments

Comments

@vallentin
Copy link
Collaborator

I've encountered a bug with template paths. Which is triggered if templates/ is missing and another directory is used instead. I was quite confused, as the code worked perfectly fine on my Windows machine. However, my CI running Ubuntu failed. I then had the CI also build on Windows and MacOS as well, and apparently it fails on both Ubuntu and MacOS, but not Windows.

I spent some time and figured out what triggers it.

path (as path = "foo.html"): sets the path to the template file. The path is interpreted as relative to the configured template directories (by default, this is a templates directory next to your Cargo.toml).

I don't have my templates inside templates/ I have them inside client/templates/. If I interpret the docs correctly, I'm assuming that templates is simply joined with path. So naturally, my index.html, I'd write it as ../client/templates/index.html.

However, doing so fails in the same way on both Ubuntu and MacOS with the following error, that the template is not found:

error: proc-macro derive panicked
 --> src/main.rs:3:10
  |
3 | #[derive(Template)]
  |          ^^^^^^^^
  |
  = help: message: template "../client/templates/index.html" not found in directories ["/rust/askama-ubuntu/templates"]

error: aborting due to previous error

error: could not compile `askama-ubuntu`.

Workaround 1

If I simply create the templates/ directory, and leave it empty. Then the code successfully builds on all 3 platforms.

Workaround 2

Alternatively, having an askama.toml with client/templates in dirs works as well:

[general]
dirs = ["client/templates"]

However, I only first discovered askama.toml while trying to find out what caused the issue in the first place.


Minimal, Reproducible Example

  • Create a dummy template at client/templates/index.html.
  • Make sure the templates/ directory does not exist
  • askama = "0.10"
  • Add the following code to main.rs
  • cargo build and the previous error appears on Ubuntu and MacOS
use askama::Template;

#[derive(Template)]
#[template(path = "../client/templates/index.html")]
pub struct IndexTemplate;

fn main() {}
@djc
Copy link
Owner

djc commented Nov 13, 2020

I'm honestly not sure whether I want to support templates in directories outside of those directly configured. I am confused that there's an OS difference here, though.

@vallentin
Copy link
Collaborator Author

That's understandable, considering askama.toml and dirs is already a thing. It's more the inconsistency that bugs me. I'd personally be fine with receiving a compile time error, if a path was outside the templates directory (with a note about using dirs).

@vallentin
Copy link
Collaborator Author

vallentin commented Nov 15, 2020

So, I've been digging, and the short version is that this is a Rust standard library issue and not an askama issue.

The longer version is, I tracked down where the differing behavior happened, and it was in find_template, specifically rooted.exists()

pub fn find_template(
&self,
path: &str,
start_at: Option<&Path>,
) -> std::result::Result<PathBuf, CompileError> {
if let Some(root) = start_at {
let relative = root.with_file_name(path);
if relative.exists() {
return Ok(relative);
}
}
for dir in &self.dirs {
let rooted = dir.join(path);
if rooted.exists() {
return Ok(rooted);
}
}
Err(format!(
"template {:?} not found in directories {:?}",
path, self.dirs
)
.into())
}

On Unix it always returned false, due to how paths are resolved by stat. So when exists attempts to open templates/../client/templates/index.html, then it fails, as templates did not exist. Apparently on Unix every path component is opened, to be able to handle symlinks, which in short is mentioned in the docs for Path::components. However, on Windows paths are apparently normalized prior to being resolved, so the behavior mentioned in Path::components docs, is actually not possible on Windows using symlinks.

In short. I had no directory called templates. So when stat tried to resolve the first templates/ component, then it failed and wouldn't continue to resolve the .., resulting in the exists check to (always) return false. This is why simply creating the empty templates/ directory fixed the issue. Since Windows normalizes all ..s prior to resolving, that's why it worked without the templates/ directory exists.

The Rust docs didn't really mention this inconsistency, so I'll forward this issue to Rust's issue tracker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants