aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar René Kijewski <kijewski@library.vetmed.fu-berlin.de>2022-07-20 21:18:18 +0200
committerLibravatar Dirkjan Ochtman <dirkjan@ochtman.nl>2022-07-25 11:39:58 +0200
commitbd8d2f334ed2a9fd0fb1bb6f9d87b4ab45556918 (patch)
treefdca7dca4591f62fca30d798b37779f6fadae00a
parentc5fbd2ebfb0ce84a49db47f09caa12a048ea61d0 (diff)
downloadaskama-bd8d2f334ed2a9fd0fb1bb6f9d87b4ab45556918.tar.gz
askama-bd8d2f334ed2a9fd0fb1bb6f9d87b4ab45556918.tar.bz2
askama-bd8d2f334ed2a9fd0fb1bb6f9d87b4ab45556918.zip
Implement basic hyper integration
The integration is based on askama_gotham. There is no specific trait to convert an arbitrary T to `hyper::Response`, so I used `From<Template> for hyper::Response`.
-rw-r--r--.github/workflows/rust.yml2
-rw-r--r--Cargo.toml1
-rw-r--r--askama/Cargo.toml1
-rw-r--r--askama_derive/Cargo.toml1
-rw-r--r--askama_derive/src/generator.rs27
-rw-r--r--askama_hyper/Cargo.toml33
l---------askama_hyper/LICENSE-APACHE1
l---------askama_hyper/LICENSE-MIT1
-rw-r--r--askama_hyper/README.md9
-rw-r--r--askama_hyper/src/lib.rs29
-rw-r--r--askama_hyper/templates/hello.html1
-rw-r--r--askama_hyper/tests/basic.rs70
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
diff --git a/Cargo.toml b/Cargo.toml
index 13f45d8..bd8b8da 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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);
+}