4

I'm writing a small website in rust rocket. I'm using handlebar templates, and the way you return a Template form a handler is quite convenient. But I also want to use my template engine for emails I'm sending out. The problem is, that Templates in rocket_contrib do not really support rendering to a String.

There is a method show(), that creates Option<String>, but that one is not supposed to be used.

Render the template named name located at the path root with the context context into a String. This method is very slow and should not be used in any running Rocket application. This method should only be used during testing to validate Template responses. For other uses, use render instead.

The method render() is what you normally use, but this one returns a Template, which is what I started with... You return those and rocket does its magic to produce the resulting html-page.

Is there any way to use my templates in rocket for emails? My mailer (lettre) expects a String.

kratenko
  • 7,354
  • 4
  • 36
  • 61
  • You can directly use [handlebars](https://github.com/sunng87/handlebars-rust#quick-start) to render the template instead of relying on rocket for email templates – Neeraj Jun 14 '20 at 06:00
  • While that is true, I do have an infrastructure built up in rust that finds my templates in the file system and has my helpers installed. I would have to put that up again in parallel. I would like not to have to do that. – kratenko Jun 14 '20 at 09:29
  • I have this same question. – nph Sep 11 '21 at 12:23

1 Answers1

2

It's perfectly fine to use show(). Here is how it's implemented in Rocket:

    pub fn show<S, C>(rocket: &Rocket, name: S, context: C) -> Option<String>
        where S: Into<Cow<'static, str>>, C: Serialize
    {
        let ctxt = rocket.state::<ContextManager>().map(ContextManager::context).or_else(|| {
            // N.B.: removed some logging for brevity
            None
        })?;

        Template::render(name, context).finalize(&ctxt).ok().map(|v| v.0)
    }

As you can see show() does call render() internally, just as the rustdoc told us to do instead. But here it also calls finalize(). Let's check what does it do:

/// Actually render this template given a template context. This method is
/// called by the `Template` `Responder` implementation as well as
/// `Template::show()`.

Ok, so render() does not really render anything. It's finalize() that's doing the job.

We can also check how the Responder is implemented:

/// Returns a response with the Content-Type derived from the template's
/// extension and a fixed-size body containing the rendered template. If
/// rendering fails, an `Err` of `Status::InternalServerError` is returned.
impl Responder<'static> for Template {
    fn respond_to(self, req: &Request) -> response::Result<'static> {
        let ctxt = req.guard::<State<ContextManager>>().succeeded().ok_or_else(|| {
            // N.B.: removed some logging for brevity
            Status::InternalServerError
        })?.inner().context();

        let (render, content_type) = self.finalize(&ctxt)?;
        Content(content_type, render).respond_to(req)
    }
}

TL;DR: It seems that the documentation is misleading. The template implementation is doing absolutely the same thing when you call show() and when you send a response. In both cases it invokes finalize() internally, which produces the String rendering of the template. Thus there should not be any performance penalty of using show()

PS: The code in the current main branch has changed a bit, but the logic is still the same

Svetlin Zarev
  • 14,713
  • 4
  • 53
  • 82
  • 1
    One question (I am still pretty new to rust), if the `show` function is called in one of the routes, but requires `rocket` as an argument, how does it get that in scope if the rocket is in the `main` function? – nph Sep 11 '21 at 13:38