rwf2 / Rocket

A web framework for Rust.
https://rocket.rs
Other
24.41k stars 1.56k forks source link

How to specify different template_dir for different sub-apps? #850

Closed tecywiz121 closed 3 years ago

tecywiz121 commented 5 years ago

Hey! I'm using rocket 0.4 to build a pretty simple web application, but I have some plugable modules that have their own views/templates.

I'd like to keep each module's templates with that module, but I can't figure out how to specify different template_dir values.

This seems like it's related to #394, but is a much simpler problem to solve (if it isn't already!) A possible solution would be to have a template_dirs variable that accepts an array of directories to search instead.

tecywiz121 commented 5 years ago

This crate is where I'd like to be able to add some additional templates for a login and a logout page.

SergioBenitez commented 5 years ago

Can you before more specific about what you're calling a "module" here and point me to where in your code these modules are defined as well as where the separate templates live?

If you're launching multiple instances of Rocket, at present, you'll need to build a Config with an appropriate template_dir extra for each instance, or use a different Rocket.toml file, or supply the template_dir via the ROCKET_TEMPLATE_DIR environment variable.

tecywiz121 commented 5 years ago

Sure thing! I've moved things around a little bit to make it more clear. Everything is on the auth branch for now.

The "core" application is called bellhop, and lives here.

I'm doing some refactoring to support different types of authentication. I've moved some routes from the bellhop crate into another crate called bellhop-auth-dummy, and you can see those routes here.

I'm having trouble figuring out how to move the templates from here into the bellhop-auth-dummy crate.

SergioBenitez commented 5 years ago

@tecywiz121 I'm sorry, but I'm still not sure if I totally understand. Here's what I've grokked thus far:

  1. You have one Rocket application in the crate bellhop at ./bellhop.
  2. You have a second, distinct Rocket application bellhop-auth-dummy in ./bellhop-auth-dummy.
  3. You have templates for bellhop in ./bellhop/templates.
  4. You have templates for bellhop-auth-dummy in ./bellhop/templates/login.

Is that the correct lay of the land?

If so, then the answer to your question depends on which templates, if any, are shared by bellhop and bellhop-auth-dummy. If none, then simply move the login templates to the bellhop-auth-dummy directory and either crate a new Rocket.toml or override the template configuration parameter when running the application via a ROCKET_TEMPLATE_DIR=path/to/bellhop-auth-dummy/templates environment variable.

If they are sharing templates, then you can simple use the same template_dir for both and use the proper path in each case, which appears to be what you're doing.

tecywiz121 commented 5 years ago

No worries, I'm probably explaining myself poorly. I'm coming from a django background, so I'm probably taking a lot for granted. I really appreciate you taking the time to respond!

  1. You have one Rocket application in the crate bellhop at ./bellhop.

I have most of a single Rocket application at ./bellhop, but that crate is a library. The main.rs is over in bellhop-bin.

The intended design is to be able to compose together bellhop with different authentication plugins (like bellhop-auth-dummy) into a single binary.

  1. You have a second, distinct Rocket application bellhop-auth-dummy in ./bellhop-auth-dummy.

I wouldn't call it a "distinct" Rocket application. It's probably closer to a fairing. In fact, it probably should be a fairing now that I think about it.

bellhop-auth-dummy is like a plugin that provides one flavor of authentication for bellhop.

  1. You have templates for bellhop in ./bellhop/templates.

Correct.

  1. You have templates for bellhop-auth-dummy in ./bellhop/templates/login.

Yes, but I don't want them there anymore. I'd rather them be in ./bellhop-auth-dummy/templates/login.

Is that the correct lay of the land?

Other than bellhop and bellhop-auth-dummy being separate Rocket applications, I think so.

SergioBenitez commented 5 years ago

@tecywiz121 I think everything I suggested afterwards still applies. Can you take a look?

tecywiz121 commented 5 years ago

I did read your suggestions, but I don't think they'll work for my use case. I believe you presented three options:

Here are my follow-up questions/comments.

No templates shared, create a second Rocket.toml

This would be perfect! Since the plugin (bellhop-auth-dummy) and the core crate (bellhop) get compiled together into a single binary, how do I specify a second Rocket.toml? I might have missed this in the docs, and if so, I'm sorry!

No templates shared, override with environment variable

This definitely won't work, since as far as I know, a running executable can't have multiple values for the same environment variable.

Templates are shared, use the same path

Like you mentioned, this is what I'm doing now. The problem with this approach is that it requires all plugin's templates to be added to the bellhop crate.

This poses a significant problem if the plugins can't be released open source, which is going to be true of some of the plugins I'll be writing.

jebrosen commented 5 years ago

Like you mentioned, this is what I'm doing now. The problem with this approach is that it requires all plugin's templates to be added to the bellhop crate.

This actually touches on a bigger, more complex issue: in what way will plugins normally be "plugged in" to the overall app? And what aspects of this happen at compile time and at runtime?

Here are a few points that might help with your design:

This also all depends on who--you, developers, packagers, end users, etc.--will be "choosing" plugins and what resources they will have access to at the time.

tecywiz121 commented 5 years ago

This actually touches on a bigger, more complex issue: in what way will plugins normally be "plugged in" to the overall app? And what aspects of this happen at compile time and at runtime?

I'll leave the general case up to greater minds than mine, but in Bellhop's case, the plugins are all compiled together in bellhop-bin's main.rs

Use a compile-time template library instead

This is actually a surprisingly simple workaround. I'll go looking around, but do you have any recommendations?

Expose a method in plugin crates that would register plugin templates into an &mut Handlebars or &mut Tera (with some kind of namespacing!)

I don't think it's possible to access this from the interface rocket_contrib provides as of right now, but I think this could work too. Plus I wouldn't have to re-write all my templates!

Have some build system merge templates/ directories from multiple sources into one dist/ directory

To do this with Cargo, I think I'd have to write a build.rs helper library that knows where to find the source directory for dependency crates. With the hashes cargo appends, I'm not sure this would be an easy approach.

Modify Rocket's template support to look in multiple directories

This is what I had suggested in the original post: switching template_dir to template_dirs.

jebrosen commented 5 years ago

This poses a significant problem if the plugins can't be released open source, which is going to be true of some of the plugins I'll be writing.

I'll leave the general case up to greater minds than mine, but in Bellhop's case, the plugins are all compiled together in bellhop-bin's main.rs

This sounds like you need to provide plugins as .rlib files or another non-source form. You will probably need to either use compile-time templates, embed the template files in the crate and provide them with some known global function, or distribute the template sources alongside the plugin (if that source is okay to release).

Use a compile-time template library instead

This is actually a surprisingly simple workaround. I'll go looking around, but do you have any recommendations?

askama, horrorshow, maud, and ructe are a few examples. I won't make a specific recommendation because they are very subjective and dependent on the needs of an individual project and syntax preferences.

Expose a method in plugin crates that would register plugin templates into an &mut Handlebars or &mut Tera (with some kind of namespacing!)

I don't think it's possible to access this from the interface rocket_contrib provides as of right now, but I think this could work too. Plus I wouldn't have to re-write all my templates!

Template::custom would help with this. To be clear, this approach would still require the plugin's templates to be either hardcoded into the binary (e.g. with include_str!()), or be in something like a "bellhop_plugin_xxxxx_templates" directory at runtime.


Since this is all getting a bit off-topic from Rocket itself, I'll focus from now onward on the only thing I think Rocket is in a position to improve:

A possible solution would be to have a template_dirs variable that accepts an array of directories to search instead.

I think this wouldn't actually help much in your case, so I'm not (yet) convinced it should be added. A person or build process would still have to specify the multiple template_dirs manually, and a person or build process would still have to figure out which directories have to be copied to the deployment target.

tecywiz121 commented 5 years ago

Thanks for the pointer to Template::custom! I'm going to explore using that to set up a different template directory for each plugin, and I'll also try taking a look at askama, which seems to have good support for Rocket.

A person or build process would still have to specify the multiple template_dirs manually, and a person or build process would still have to figure out which directories have to be copied to the deployment target.

I don't see how having multiple directories is different in that regard compared to having a single directory. Someone still has to set template_dir in Rocket.toml, and copy it to the deployed machine.

In my documentation for each plugin, I'd add a section like:

Installation

Attaching the Plugin

Modify your main.rs to look something like this:

fn main() {
    Bellhop::default()
        .auth(bellhop_auth_dummy::Dummy) // Insert this line
        .start()
}

Template Directory

Modify Rocket.toml:

[global]
template_dirs = [
    "/opt/bellhop/templates",
    "/opt/bellhop_auth_dummy/templates", # Insert this line
]
jebrosen commented 5 years ago

I don't see how having multiple directories is different in that regard compared to having a single directory. Someone still has to set template_dir in Rocket.toml, and copy it to the deployed machine.

In my documentation for each plugin, I'd add a section like:

...

For comparison I was thinking of this, which doesn't involve modifying rocket_contrib and I think wouldn't be any more complicated to deploy than what you posted.

Installation

Attaching the Plugin

Modify your main.rs to look something like this:

fn main() {
    Bellhop::default()
        .auth(bellhop_auth_dummy::Dummy) // Insert this line
        .start()
}

Template Directory

Modify Rocket.toml:

[global]
template_dir ="/opt/bellhop/templates"

Where "/opt/bellhop/templates/bellhop_auth_dummy" contains the plugin templates.

The bottom line: I'm not opposed to supporting multiple template_dirs in the future, but it's not a current priority of mine. If you or someone else is committed to or needs a design that would benefit from template_dirs that might make it more important, but it sounds like you're still open to exploring other (possibly better) options.

tecywiz121 commented 5 years ago

I'm trying out registering a second template directory with Template::custom over here, but I'm getting the following:

GET /extended text/html:
    => Matched: GET /extended (extended_template)
    => Error: Template 'bar' does not exist.
    => Known templates: foo
    => Searched in '"/home/sc/Personal/rocket_template_bug/templates"'.
    => Outcome: Failure
    => Warning: Responding with 500 Internal Server Error catcher.
    => Response succeeded.

The odd part is that this line prints true.


The bottom line: I'm not opposed to supporting multiple template_dirs in the future, but it's not a current priority of mine.

That's good to hear. I'll keep investigating for now. Thanks for the help!

jebrosen commented 5 years ago

Aha, that's an unfortunate (and somewhat confusing) consequence of the current design. Template::render only knows about templates discovered via the template_dir mechanism. The inner Handlebars engine, however, does know about bar since it was added directly -- this means that the (main crate) foo template could reference the (plugin crate) bar template, but Template::render can't.

I'll have to think about this a bit more. It would be nice if templates added manually (e.g. small helpers or include_str templates) were available to Template::render, but I'm afraid it might be a breaking change and have other unintended consequences.

czifro commented 3 years ago

Is it possible to directly add templates to the context used by Template::render? Looking at the code, I see that if there was a way to add templates to the context, that might address the issue.

SergioBenitez commented 3 years ago

Is it possible to directly add templates to the context used by Template::render? Looking at the code, I see that if there was a way to add templates to the context, that might address the issue.

As of yesterday on master, yes.