diff options
-rw-r--r-- | .github/workflows/rust.yml | 2 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | askama/Cargo.toml | 1 | ||||
-rw-r--r-- | askama_derive/Cargo.toml | 1 | ||||
-rw-r--r-- | askama_derive/src/generator.rs | 27 | ||||
-rw-r--r-- | askama_hyper/Cargo.toml | 33 | ||||
l--------- | askama_hyper/LICENSE-APACHE | 1 | ||||
l--------- | askama_hyper/LICENSE-MIT | 1 | ||||
-rw-r--r-- | askama_hyper/README.md | 9 | ||||
-rw-r--r-- | askama_hyper/src/lib.rs | 29 | ||||
-rw-r--r-- | askama_hyper/templates/hello.html | 1 | ||||
-rw-r--r-- | askama_hyper/tests/basic.rs | 70 |
12 files changed, 175 insertions, 1 deletions
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9102b42..5438379 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -33,7 +33,7 @@ jobs: matrix: package: [ askama, askama_derive, testing, - askama_actix, askama_axum, askama_escape, askama_gotham, + askama_actix, askama_axum, askama_escape, askama_gotham, askama_hyper, askama_mendes, askama_rocket, askama_tide, askama_warp, ] runs-on: ubuntu-latest @@ -10,6 +10,7 @@ members = [ "askama_rocket", "askama_tide", "askama_warp", + "askama_hyper", "testing", ] diff --git a/askama/Cargo.toml b/askama/Cargo.toml index f421935..c6ca613 100644 --- a/askama/Cargo.toml +++ b/askama/Cargo.toml @@ -27,6 +27,7 @@ urlencode = ["askama_derive/urlencode", "percent-encoding"] with-actix-web = ["askama_derive/with-actix-web"] with-axum = ["askama_derive/with-axum"] with-gotham = ["askama_derive/with-gotham"] +with-hyper = ["askama_derive/with-hyper"] with-mendes = ["askama_derive/with-mendes"] with-rocket = ["askama_derive/with-rocket"] with-tide = ["askama_derive/with-tide"] diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index ce7abfa..1d176b7 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -23,6 +23,7 @@ num-traits = [] with-actix-web = [] with-axum = [] with-gotham = [] +with-hyper = [] with-mendes = [] with-rocket = [] with-tide = [] diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 142ff7f..5b8c19e 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -308,6 +308,8 @@ impl<'a> Generator<'a> { self.impl_axum_into_response(&mut buf)?; #[cfg(feature = "with-gotham")] self.impl_gotham_into_response(&mut buf)?; + #[cfg(feature = "with-hyper")] + self.impl_hyper_into_response(&mut buf)?; #[cfg(feature = "with-mendes")] self.impl_mendes_responder(&mut buf)?; #[cfg(feature = "with-rocket")] @@ -449,6 +451,31 @@ impl<'a> Generator<'a> { buf.writeln("}") } + // Implement `From<Template> for hyper::Response<Body>`. + #[cfg(feature = "with-hyper")] + fn impl_hyper_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { + let (impl_generics, orig_ty_generics, where_clause) = + self.input.ast.generics.split_for_impl(); + let ident = &self.input.ast.ident; + buf.writeln(&format!( + "{} {{", + quote!( + impl #impl_generics ::core::convert::From<&#ident #orig_ty_generics> + for ::askama_hyper::hyper::Response<::askama_hyper::hyper::Body> + #where_clause + ) + ))?; + buf.writeln("#[inline]")?; + buf.writeln(&format!( + "{} {{", + quote!(fn from(value: &#ident #orig_ty_generics) -> Self) + ))?; + let ext = self.input.extension().unwrap_or("txt"); + buf.writeln(&format!("::askama_hyper::respond(value, {:?})", ext))?; + buf.writeln("}")?; + buf.writeln("}") + } + // Implement mendes' `Responder`. #[cfg(feature = "with-mendes")] fn impl_mendes_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { diff --git a/askama_hyper/Cargo.toml b/askama_hyper/Cargo.toml new file mode 100644 index 0000000..6a7a213 --- /dev/null +++ b/askama_hyper/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "askama_hyper" +version = "0.1.0" +description = "Hyper integration for Askama templates" +documentation = "https://docs.rs/askama" +keywords = ["markup", "template", "jinja2", "html"] +categories = ["template-engine"] +homepage = "https://github.com/djc/askama" +repository = "https://github.com/djc/askama" +license = "MIT OR Apache-2.0" +workspace = ".." +readme = "README.md" +edition = "2018" + +[dependencies] +askama = { version = "0.11.2", path = "../askama", default-features = false, features = ["with-hyper"] } +hyper = { version = "0.14", default-features = false } + +[dev-dependencies] +hyper = { version = "0.14", features = ["client"] } +mime = "0.3" +routerify = "3" +tokio = { version = "1", default-features = false, features = ["net", "macros", "rt"] } + +[features] +default = ["askama/default"] +config = ["askama/config"] +humansize = ["askama/humansize"] +markdown = ["askama/markdown"] +num-traits = ["askama/num-traits"] +serde-json = ["askama/serde-json"] +serde-yaml = ["askama/serde-yaml"] +urlencode = ["askama/urlencode"] diff --git a/askama_hyper/LICENSE-APACHE b/askama_hyper/LICENSE-APACHE new file mode 120000 index 0000000..965b606 --- /dev/null +++ b/askama_hyper/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE
\ No newline at end of file diff --git a/askama_hyper/LICENSE-MIT b/askama_hyper/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/askama_hyper/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT
\ No newline at end of file diff --git a/askama_hyper/README.md b/askama_hyper/README.md new file mode 100644 index 0000000..024665a --- /dev/null +++ b/askama_hyper/README.md @@ -0,0 +1,9 @@ +# askama_hyper: Askama integration with Hyper + +[![Documentation](https://docs.rs/askama_hyper/badge.svg)](https://docs.rs/askama_hyper/) +[![Latest version](https://img.shields.io/crates/v/askama_hyper.svg)](https://crates.io/crates/askama_hyper) +[![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) +[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) + +Integration of the [Askama](https://github.com/djc/askama) templating engine in +code building on the [hyper](https://crates.io/crates/hyper)-based web servers. diff --git a/askama_hyper/src/lib.rs b/askama_hyper/src/lib.rs new file mode 100644 index 0000000..07ede88 --- /dev/null +++ b/askama_hyper/src/lib.rs @@ -0,0 +1,29 @@ +#![forbid(unsafe_code)] +#![deny(elided_lifetimes_in_paths)] +#![deny(unreachable_pub)] + +pub use askama::*; +#[doc(hidden)] +pub use hyper; +use hyper::{header, Body, Response, StatusCode}; + +pub fn try_respond<T: Template>(t: &T, _ext: &str) -> Result<Response<Body>> { + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static(T::MIME_TYPE), + ) + .body(t.render()?.into()) + .map_err(|err| Error::Custom(Box::new(err))) +} + +pub fn respond<T: Template>(t: &T, _ext: &str) -> Response<Body> { + match try_respond(t, _ext) { + Ok(response) => response, + Err(_) => Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap(), + } +} diff --git a/askama_hyper/templates/hello.html b/askama_hyper/templates/hello.html new file mode 100644 index 0000000..8149be7 --- /dev/null +++ b/askama_hyper/templates/hello.html @@ -0,0 +1 @@ +Hello, {{ name }}! diff --git a/askama_hyper/tests/basic.rs b/askama_hyper/tests/basic.rs new file mode 100644 index 0000000..d0038c1 --- /dev/null +++ b/askama_hyper/tests/basic.rs @@ -0,0 +1,70 @@ +use std::convert::Infallible; + +use askama::Template; +use hyper::body::to_bytes; +use hyper::{Body, Client, Request, Response, Server}; +use routerify::ext::RequestExt; +use routerify::{Router, RouterService}; + +#[derive(Template)] +#[template(path = "hello.html")] +struct HelloTemplate<'a, N> +where + N: std::fmt::Display, +{ + name: &'a N, +} + +async fn hello_handler(req: Request<Body>) -> Result<Response<Body>, Infallible> { + let name = req.param("name").unwrap(); + let template = &HelloTemplate { name: &name }; + Ok(template.into()) +} + +fn router() -> Router<Body, Infallible> { + Router::builder() + .get("/hello/:name", hello_handler) + .build() + .unwrap() +} + +#[tokio::test] +async fn test_hyper() { + let addr = ([127, 0, 0, 1], 0).into(); + let service = RouterService::new(router()).expect("Could not create service"); + let server = Server::bind(&addr).serve(service); + let local_addr = server.local_addr(); + + let (tx, rx) = tokio::sync::oneshot::channel::<()>(); + let serve = async move { + let server = server.with_graceful_shutdown(async { + rx.await.expect("Could not await signal to stop"); + }); + server.await.expect("Could not serve"); + }; + let query = async move { + let uri = format!("http://{}/hello/world", local_addr) + .parse() + .expect("Could not format URI"); + let client = Client::new(); + + let res = client.get(uri).await.expect("Could not query client"); + assert_eq!(res.status(), hyper::StatusCode::OK); + + let content_type = res + .headers() + .get("content-type") + .expect("Response did not contain content-type header") + .to_str() + .expect("Content-type was not a UTF-8 string"); + assert_eq!(content_type, mime::TEXT_HTML_UTF_8.to_string()); + + let body = to_bytes(res).await.expect("No body returned"); + let body = std::str::from_utf8(&body).expect("Body was not UTF-8"); + assert_eq!(body, "Hello, world!"); + + tx.send(()).unwrap(); + }; + + tokio::join!(serve, query); +} |