From 35b61b0acd8e69948eff1fb3962d559f24c89859 Mon Sep 17 00:00:00 2001 From: Kellen Frodelius-Fujimoto Date: Mon, 10 Dec 2018 20:21:15 +0100 Subject: [PATCH 1/3] Implement `IntoResponse` for `Template`, allowing them to be used in `gotham` handlers. --- askama/Cargo.toml | 3 +++ askama/src/lib.rs | 30 ++++++++++++++++++++++++++ askama_derive/Cargo.toml | 1 + askama_derive/src/generator.rs | 19 +++++++++++++++++ testing/Cargo.toml | 4 ++++ testing/tests/gotham.rs | 39 ++++++++++++++++++++++++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 testing/tests/gotham.rs diff --git a/askama/Cargo.toml b/askama/Cargo.toml index 7c85a92e1..22d129d49 100644 --- a/askama/Cargo.toml +++ b/askama/Cargo.toml @@ -24,6 +24,7 @@ serde-json = ["askama_shared/serde_json"] with-iron = ["iron", "askama_derive/iron"] with-rocket = ["rocket", "askama_derive/rocket"] with-actix-web = ["actix-web", "askama_derive/actix-web", "mime_guess"] +with-gotham = ["gotham", "askama_derive/gotham", "hyper", "mime_guess"] [dependencies] askama_derive = { version = "0.8.0", path = "../askama_derive" } @@ -32,6 +33,8 @@ iron = { version = ">= 0.5, < 0.7", optional = true } rocket = { version = "0.4", optional = true } actix-web = { version = "0.7", optional = true } mime_guess = { version = "2.0.0-alpha", optional = true } +gotham = { version = "0.3", optional = true } +hyper = { version = "0.12", optional = true } [package.metadata.docs.rs] features = [ "serde-json" ] diff --git a/askama/src/lib.rs b/askama/src/lib.rs index 7381af7f5..e57b467bd 100644 --- a/askama/src/lib.rs +++ b/askama/src/lib.rs @@ -478,6 +478,36 @@ pub mod actix_web { } } +#[cfg(feature = "with-gotham")] +pub mod gotham { + extern crate gotham; + extern crate hyper; + extern crate mime_guess; + + pub use self::gotham::handler::IntoResponse; + use self::gotham::helpers::http::response::create_empty_response; + use self::gotham::helpers::http::response::create_response; + pub use self::gotham::state::State; + pub use self::hyper::{Body, Response, StatusCode}; + use self::mime_guess::get_mime_type; + + pub fn respond(t: &super::Template, ext: &str) -> Response { + let mime_type = get_mime_type(ext).to_string(); + + match t.render() { + Ok(body) => Response::builder() + .status(StatusCode::OK) + .header("content-type", mime_type.to_string()) + .body(body.into()) + .unwrap(), + Err(_) => Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(vec![].into()) + .unwrap(), + } + } +} + fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> io::Result<()> { if dir.is_dir() { for entry in fs::read_dir(dir)? { diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index 70965975b..461a1f82c 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -16,6 +16,7 @@ proc-macro = true iron = [] rocket = [] actix-web = [] +gotham = [] [dependencies] askama_shared = { version = "0.7.2", path = "../askama_shared" } diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 61a82bdac..b30b040dd 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -92,6 +92,9 @@ impl<'a> Generator<'a> { if cfg!(feature = "actix-web") { self.impl_actix_web_responder(&mut buf); } + if cfg!(feature = "gotham") { + self.impl_gotham_into_response(&mut buf); + } buf.buf } @@ -217,6 +220,22 @@ impl<'a> Generator<'a> { buf.writeln("}"); } + // Implement gotham's `IntoResponse`. + fn impl_gotham_into_response(&mut self, buf: &mut Buffer) { + self.write_header(buf, "::askama::gotham::IntoResponse", None); + buf.writeln( + "fn into_response(self, _state: &::askama::gotham::State)\ + -> ::askama::gotham::Response<::askama::gotham::Body> {", + ); + let ext = match self.input.path.extension() { + Some(s) => s.to_str().unwrap(), + None => "txt", + }; + buf.writeln(&format!("::askama::gotham::respond(&self, {:?})", ext)); + buf.writeln("}"); + buf.writeln("}"); + } + // Writes header for the `impl` for `TraitFromPathName` or `Template` // for the given context struct. fn write_header( diff --git a/testing/Cargo.toml b/testing/Cargo.toml index 16d2f3421..720ef2e5d 100644 --- a/testing/Cargo.toml +++ b/testing/Cargo.toml @@ -12,6 +12,7 @@ full = ["actix", "with-iron", "serde-json"] serde-json = ["serde_json", "askama/serde-json"] with-rocket = ["rocket", "askama/with-rocket"] with-iron = ["iron", "askama/with-iron"] +with-gotham = ["gotham", "askama/with-gotham", "mime", "hyper"] [dependencies] actix-web = { version = "0.7", optional = true } @@ -20,6 +21,9 @@ bytes = { version = "0.4", optional = true } iron = { version = "0.6", optional = true } rocket = { version = "0.4", optional = true } serde_json = { version = "1.0", optional = true } +gotham = { version = "0.3", optional = true } +mime = { version = "0.3", optional = true } +hyper = { version = "0.12", optional = true } [build-dependencies] askama = { path = "../askama", version = "*" } diff --git a/testing/tests/gotham.rs b/testing/tests/gotham.rs new file mode 100644 index 000000000..f127d59f9 --- /dev/null +++ b/testing/tests/gotham.rs @@ -0,0 +1,39 @@ +#![cfg(feature = "with-gotham")] + +use askama::Template; +use gotham::state::State; +use gotham::test::TestServer; +use hyper::StatusCode; + +#[derive(Template)] +#[template(path = "hello.html")] +struct HelloTemplate<'a> { + name: &'a str, +} + +fn hello(state: State) -> (State, HelloTemplate<'static>) { + (state, HelloTemplate { name: "world" }) +} + +#[test] +fn test_gotham() { + let test_server = TestServer::new(|| Ok(hello)).expect("Failed to mount test router"); + + let res = test_server + .client() + .get("http://localhost/") + .perform() + .expect("Failed to send request to gotham"); + + assert_eq!(res.status(), StatusCode::OK); + { + let headers = res.headers(); + let content_type = headers + .get("content-type") + .expect("Response did not contain content-type header"); + assert_eq!(content_type.to_str().unwrap(), mime::TEXT_HTML.to_string()); + } + + let body = res.read_utf8_body().expect("failed to read response body"); + assert_eq!(&body, "Hello, world!"); +} From 2642cf318342e971b8601d160dc8cf85aaa15a80 Mon Sep 17 00:00:00 2001 From: Kellen Frodelius-Fujimoto Date: Mon, 10 Dec 2018 20:29:43 +0100 Subject: [PATCH 2/3] Update docs and readme to reflect Gotham support --- README.md | 2 +- askama/src/lib.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8924c5e87..129ad4a28 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ in a for-profit context, please consider supporting my open source work on * Construct templates using a familiar, easy-to-use syntax * Template code is compiled into your crate for [optimal performance][benchmarks] * Benefit from the safety provided by Rust's type system -* Optional built-in support for Actix, Iron and Rocket web frameworks +* Optional built-in support for Actix, Gotham, Iron and Rocket web frameworks * Debugging features to assist you in template development * Templates must be valid UTF-8 and produce UTF-8 when rendered * Works on stable Rust diff --git a/askama/src/lib.rs b/askama/src/lib.rs index e57b467bd..6e879002b 100644 --- a/askama/src/lib.rs +++ b/askama/src/lib.rs @@ -391,6 +391,16 @@ //! `Responder` trait for each template type. This makes it easy to return a value of //! that type in an Actix-web handler. //! +//! ## Gotham integration +//! +//! Enabling the `with-gotham` feature appends an implementation of Gotham's `IntoResponse` +//! trait for each template type. This makes it easy to return a value of that type in a +//! Gotham handler. +//! +//! In case of a run-time error occurring during templating, the response will be of the same +//! signature, with a status code of `500 Internal Server Error`, mime `*/*`, and an empty `Body`. +//! This preserves the response chain if any custom error handling needs to occur. +//! //! ## The `json` filter //! //! Enabling the `serde-json` filter will enable the use of the `json` filter. From 19527d86b746565c70b5097a908337ff88fa3f7e Mon Sep 17 00:00:00 2001 From: Kellen Frodelius-Fujimoto Date: Mon, 10 Dec 2018 21:02:44 +0100 Subject: [PATCH 3/3] Remove `extern crate` statements and clean up `use` statements. --- askama/src/lib.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/askama/src/lib.rs b/askama/src/lib.rs index 6e879002b..d1bb639f2 100644 --- a/askama/src/lib.rs +++ b/askama/src/lib.rs @@ -490,16 +490,11 @@ pub mod actix_web { #[cfg(feature = "with-gotham")] pub mod gotham { - extern crate gotham; - extern crate hyper; - extern crate mime_guess; - - pub use self::gotham::handler::IntoResponse; - use self::gotham::helpers::http::response::create_empty_response; - use self::gotham::helpers::http::response::create_response; - pub use self::gotham::state::State; - pub use self::hyper::{Body, Response, StatusCode}; - use self::mime_guess::get_mime_type; + pub use gotham::handler::IntoResponse; + use gotham::helpers::http::response::{create_empty_response, create_response}; + pub use gotham::state::State; + pub use hyper::{Body, Response, StatusCode}; + use mime_guess::get_mime_type; pub fn respond(t: &super::Template, ext: &str) -> Response { let mime_type = get_mime_type(ext).to_string();