opened
sunng87
username_0
I was looking into tower-web to integrate handlebars into its system.

The typical usage of handlebars in web application is to create a server-scoped registry, that holds all templates in memory. We use the registry to render templates returned from handler functions. 

The ideal API for tower-web in my mind is to create a `Template` struct, which contains template `name` and template `data`. The `Template` struct will implement `Response` to render the data structure into actual http response. The problem is `Response` trait only provides a request-scoped `Context` so there seems to be no way to access server-scoped handlebars registry.

So is it possible to bind the registry into some data structure that has server-scoped lifetime? And be accessible from `Response` trait and middlewares.
Fitting handlebars template engine into Response/Serializer or Middleware system
issue
created
lnicola
username_1
Does https://github.com/username_2/tower-web/pull/98 help?
comment
created
sunng87
username_0
It seems relevant. But for now the `Config` is only available for extractors. We'd better add it to `Context` of `Response` too.

The `Template` response works like `File` pretty much except it requires a server-scoped config, the registry.
comment
created
lnicola
username_1
Oh, I didn't notice that feature only works for extractors. A quick workaround might be to pass the registry (an `Arc` of it) to your `Template`, but adding `Config` to responses would probably be better.
comment
created
sunng87
username_0
Checked the code I found the response is implemented via macros, which extractors were Service. It seems no easy way to access `Config` from `Resource`. Why not make `Resource` and `Response` serialization a `Middleware`. Iron was doing it that way and I find it works pretty well.
comment
created
carllerche
username_2
It should be possible to get the config structure into `response::Context` (that is what it is there for).

The context takes a `T: Serializer`, which is service scoped. That trait could have a fn added to access the config.

Returning a `Template` type is a good first step, however, I would like to figure out a way to avoid having to do that. It would be nice to do something like:

```rust
#[derive(Response)]
struct Index {
    message: String,
}

#[derive(Response)]
#[web(view = "my_custom_template")]
struct CustomTemplate {
    message: String,
}

impl_web! {
    impl MyResource {
        // Uses convention to render a template at "my_resource/index" in the registery.
        #[get("/")]
        fn index(&self) -> Index {
            Ok(Index {
                message: "hello".to_string(),
            })
        }

        // Allow specifying the template
        #[get("/foo")]
        fn with_custom_template(&self) -> CustomTemplate {
            Ok(CustomTemplate {
                message: "custom".to_string(),
            })
        }
    }
}

ServiceBuilder::new()
    // By convention, this would look for a `views` directory or something like that and load
    // templates from there.
    .serializer(Handlebars::new())
    .resource(MyResource::new())
    .run(&addr)
    .unwrap();
```

To support this, first adding serializers would need to be supported. Second, we would have to figure out how to handle the `#[web(view = "my_custom_template")]` attribute.

Ideally, tower-web would know nothing about handlebars.

One strategy would be to add a fn to `Response` for introspection. So, you could do something like:

```rust
my_response.attributes()
```

and that would return a type that lets third parties see all the attributes on the response. To support *that* the macro would have to let unknown attributes through. There could be a runtime check at boot or something to validate that all attributes are known... but anyway.

Thoughts?
comment
created
carllerche
username_2
@username_0 Is this something you want to try to take on? I can provide guidance as needed.
comment
created
sunng87
username_0
@username_2 Thanks! I'm quite interested in taking this on and adding templating support for tower-web. 

For the template API, what if the handler might return one of two different templates?

```

        // Allow specifying the template
        #[get("/foo")]
        fn with_custom_template(&self) -> ??  {
            if some_check {
              Ok(CustomTemplate {
                  message: "custom".to_string(),
              })
            } else {
              Ok(AnotherTemplate { ... })
            }
        }
```

Of course we can make a trait called `ModelAndView` so the handler could return `impl ModelAndView`, and `#[web(view ....)]` generates an impl of `ModelAndView`. But to use the attribute we might want to hide the internals, `impl ModelAndView` still leaks it to user. 

At the moment I don't feel it's any better than `Template(name, data)`. The `Template` struct made it more flexible to choose actual view to render, while easier to unittest. And `Template` can be implementation independent too. Either handlebars or tera could work with it.
comment
created
sunng87
username_0
One more question, by `.serializer(Handlebars::new())`, can we still be able to use `DefaultSerializer`, the serde based one for json marshalling? A web application could use both template engine and json.

Current `Serializer` API requires data to implement `serde::Serialize`, which is road blocker to the `Template` struct solution. Another solution is to deal with it like `File`, that impls `Response` for `Template`. It requires `Template` a template engine specific API, I can put it into some crate like `tower-handlebars`. But that won't benefit other template engine like tera.
comment
created
lnicola
username_1
Implementing `Response` is completely fine. The current `serde::Serialize` is certainly useful, but it might be too data-centric, like when you're writing a web service and you're fine with a single serialization format.
comment
created
carllerche
username_2
@username_0 I'm looking [here](https://github.com/username_0/handlebars-rust) and a template is rendered using `json!`, which is serde.

The Tower Web serializer is intended to support multiple formats (json, html, etc...) at a time. So, an application can use both JSON and Handlebars. The API I described above is a builder that would chain a new serializer onto the existing list, not replace it. The idea is that the JSON serializer would handle content types of "application/json" and the Handlebars serializer would handle "text/html".

It looks like you use serde to get the variables for the template, is that correct? So, it seems like the problem is you do not have access to the template name. Perhaps the solution is to add an additional argument to [`response::Serializer`](http://rust-doc.s3-website-us-east-1.amazonaws.com/tower-web/v0.2.2/tower_web/response/trait.Serializer.html) including contextual information:

* Attributes set on the response type
* Resource's module, type, and function
* Original request
* Application configuration

If this information was available in `Serializer::serialize`, it *should* be possible to implement the API I described above without hard coding handlebars into Tower Web.

Sketch:

```rust
struct SerializeContext { ... }

impl SerializeContext {
    /// Returns the module in which the `impl_web!` was
    pub fn resource_impl_module(&self) -> &'static str { ... }

    /// Returns the type name, for example:
    ///
    /// ```
    /// impl_web! {
    ///     impl HelloWorld { ... }
    /// }
    ///
    /// serialize_context.resource_name() // "HelloWorld"
    /// ```
    ///
    ///
    fn resource_name(&self) -> &'static str { ... }

    /// Returns a reference to the original request (without the body).
    fn request(&self) -> &http::Request<()> { ... }

    /// Returns an application configuration type
    pub fn config<T: 'static>(&self) -> Option<&T> { ... }

    /// Returns the `#[web(...)]` attributes set on response types defined with
    /// `#[derive(Response)]`
    ///
    /// This allows access to the attributes at runtime (i.e. not in the macro).
    pub fn response_attributes(&self) -> Attributes { ... }
}

// The template registery probably can be on the serializer and does not have to go through the
// configuration system.
struct HandlebarsSerializer {
    registery: handlebars::Handlebars,
}

impl response::Serializer for HandlebarsSerializer {
    type Format = ();

    // This is wonky... not sure what I was thinking, but we can revisit
    fn lookup(&self, name: &str) -> ContentType<Self::Format> {
        let format = if name == "application/html" {
            Some(())
        } else {
            None
        };

        ContentType::new(HeaderName::from(name).unwrap(), format)
    }

    fn serialize<T>(&self, value: &T, format: &Self::Format, context: &SerializeContext) -> Result<Bytes, Error>
    where T: serde::Serialize
    {
        // Check if a template name is specified.
        if let Some(name) context.response_attributes().get("template") {
            return self.render(name, value);
        }

        // Otherwise, use a conventional name. I don't know what the convention should be, but we
        // can figure it out!
        let name = format!("{}/{}.hbs");

        // This would also handle a "template not found" situation.
        self.render(&name, value)
    }
}
```
comment
created
carllerche
username_2
The entire goal is to be completely data centric :) I would like all HTTP / HTML concerns to be decoupled from business logic. This means that the handler should return the data for the response and the serializer should render it!
comment
created
sunng87
username_0
@username_2 Thanks! That's much clear. So if we want to use different views or data models for a particular handler, is the code below possible?

```rust
        // Allow specifying the template
        #[get("/foo")]
        fn with_custom_template(&self) -> impl Response  {
            if some_check {
              Ok(CustomData1 {
                  message: "custom".to_string(),
              })
            } else {
              Ok(CustomData2 { ... })
            }
        }
```
comment
created
carllerche
username_2
@username_0 that wouldn't be possible because you are returning two different types. You would need to wrap that w/ `Either` *or* generate an either type.

```rust
#[derive(Response)]
#[web(either)]
enum MyResponse {
    Data1(CustomData1),
    Data2(CustomData2),
}

// Allow specifying the template
#[get("/foo")]
fn with_custom_template(&self) -> impl Future<Item = MyResponse>  {
    if some_check {
      Ok(MyResponse::Data1(CustomData1 {
          message: "custom".to_string(),
      }))
    } else {
      Ok(MyResponse::Data2(CustomData2 { ... }))
    }
}
```
comment
created
shepmaster
username_3
To chime in, I think it would be a shame to enforce that all uses of returning "text/html" in an application must be served by a single type. It's pretty common for a Rails application to have both ERB and HAML templates, for example.
comment
created
carllerche
username_2
@username_3 an option to deal w/ that would be to implement a multi-template library serializer. It should be able to use the same logic above and pick the template that is available (haml or erb).
comment
created
carllerche
username_2
I'll probably take a stab at this to get it going.
comment
created
carllerche
username_2
#115 is a first stab at it this.
comment
closed
carllerche
username_2
issue