diff options
-rw-r--r-- | .github/workflows/rust.yml | 13 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | askama_mendes/Cargo.toml | 23 | ||||
-rw-r--r-- | askama_mendes/README.md | 9 | ||||
-rw-r--r-- | askama_mendes/src/lib.rs | 39 | ||||
-rw-r--r-- | askama_mendes/templates/hello.txt | 1 | ||||
-rw-r--r-- | askama_mendes/tests/basic.rs | 96 | ||||
-rw-r--r-- | askama_shared/src/generator.rs | 4 |
8 files changed, 184 insertions, 2 deletions
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b62ae5d..26ca174 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -113,6 +113,19 @@ jobs: - run: cargo test --package askama_tide --all-targets - run: cargo clippy --package askama_tide --all-targets -- -D warnings + Mendes: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: clippy + - run: cargo test --package askama_mendes --all-targets + - run: cargo clippy --package askama_mendes --all-targets -- -D warnings + Lint: runs-on: ubuntu-latest steps: @@ -6,6 +6,7 @@ members = [ "askama_gotham", "askama_derive", "askama_escape", + "askama_mendes", "askama_rocket", "askama_shared", "askama_tide", diff --git a/askama_mendes/Cargo.toml b/askama_mendes/Cargo.toml new file mode 100644 index 0000000..34eeb11 --- /dev/null +++ b/askama_mendes/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "askama_mendes" +version = "0.1.0" +description = "Mendes 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.0-beta.1", path = "../askama", default-features = false, features = ["with-mendes", "mime", "mime_guess"] } +mendes = "0.0.59" +mime_guess = "2.0.3" + +[dev-dependencies] +async-trait = "0.1.51" +hyper = "0.14.15" +tokio = { version = "1.12", features = ["macros", "rt-multi-thread"] } diff --git a/askama_mendes/README.md b/askama_mendes/README.md new file mode 100644 index 0000000..f91ffbd --- /dev/null +++ b/askama_mendes/README.md @@ -0,0 +1,9 @@ +# askama_mendes: Askama integration with Mendes + +[![Documentation](https://docs.rs/askama_mendes/badge.svg)](https://docs.rs/askama_mendes/) +[![Latest version](https://img.shields.io/crates/v/askama_mendes.svg)](https://crates.io/crates/askama_mendes) +[![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 [Mendes](https://github.com/djc/mendes) framework. diff --git a/askama_mendes/src/lib.rs b/askama_mendes/src/lib.rs new file mode 100644 index 0000000..8f32269 --- /dev/null +++ b/askama_mendes/src/lib.rs @@ -0,0 +1,39 @@ +#![deny(elided_lifetimes_in_paths)] + +use std::convert::TryFrom; + +use mendes::application::{Application, Responder}; +use mendes::http::header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}; +use mendes::http::request::Parts; +use mendes::http::Response; +use mime_guess::MimeGuess; + +pub use askama::*; + +pub fn into_response<A, T>( + app: &A, + req: &Parts, + t: &T, + ext: Option<&str>, +) -> Response<A::ResponseBody> +where + A: Application, + T: Template, + A::ResponseBody: From<String>, + A::Error: From<askama::Error>, +{ + let content = match t.render() { + Ok(content) => content, + Err(e) => return <A::Error as From<_>>::from(e).into_response(app, req), + }; + + let mut builder = Response::builder(); + builder = builder.header(CONTENT_LENGTH, content.len()); + if let Some(ext) = ext { + if let Some(ty) = MimeGuess::from_ext(ext).first() { + builder = builder.header(CONTENT_TYPE, HeaderValue::try_from(ty.as_ref()).unwrap()); + } + } + + builder.body(content.into()).unwrap() +} diff --git a/askama_mendes/templates/hello.txt b/askama_mendes/templates/hello.txt new file mode 100644 index 0000000..8149be7 --- /dev/null +++ b/askama_mendes/templates/hello.txt @@ -0,0 +1 @@ +Hello, {{ name }}! diff --git a/askama_mendes/tests/basic.rs b/askama_mendes/tests/basic.rs new file mode 100644 index 0000000..2a07c13 --- /dev/null +++ b/askama_mendes/tests/basic.rs @@ -0,0 +1,96 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use hyper::body::to_bytes; +use hyper::{Body, Request}; +use mendes::application::Responder; +use mendes::http::request::Parts; +use mendes::http::{Response, StatusCode}; +use mendes::{handler, route, Application, Context}; + +use askama::Template; + +#[tokio::test] +async fn test() { + let req = Request::builder().body(()).unwrap(); + let rsp = App::handle(Context::new(Arc::new(App), req)).await; + let (rsp, body) = rsp.into_parts(); + assert_eq!( + rsp.headers + .get("content-type") + .and_then(|hv| hv.to_str().ok()), + Some("text/plain") + ); + assert_eq!(to_bytes(body).await.unwrap(), &b"Hello, world!"[..]); +} + +#[handler(GET)] +async fn hello(_: &App) -> Result<HelloTemplate<'static>, Error> { + Ok(HelloTemplate { name: "world" }) +} + +#[derive(Template)] +#[template(path = "hello.txt")] +struct HelloTemplate<'a> { + name: &'a str, +} + +struct App; + +#[async_trait] +impl Application for App { + type RequestBody = (); + type ResponseBody = Body; + type Error = Error; + + async fn handle(mut cx: Context<Self>) -> Response<Body> { + route!(match cx.path() { + _ => hello, + }) + } +} + +#[derive(Debug)] +enum Error { + Askama(askama::Error), + Mendes(mendes::Error), +} + +impl From<askama::Error> for Error { + fn from(e: askama::Error) -> Error { + Error::Askama(e) + } +} + +impl From<mendes::Error> for Error { + fn from(e: mendes::Error) -> Error { + Error::Mendes(e) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Askama(e) => write!(f, "{}", e), + Error::Mendes(e) => write!(f, "{}", e), + } + } +} + +impl Responder<App> for Error { + fn into_response(self, _: &App, _: &Parts) -> Response<Body> { + Response::builder() + .status(StatusCode::from(&self)) + .body(self.to_string().into()) + .unwrap() + } +} + +impl From<&Error> for StatusCode { + fn from(e: &Error) -> StatusCode { + match e { + Error::Mendes(e) => StatusCode::from(e), + Error::Askama(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index cb395d4..368db9f 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -298,7 +298,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { .push(syn::parse_str("A::ResponseBody: From<String>").unwrap()); where_clause .predicates - .push(syn::parse_str("A::Error: From<::mendes::askama::Error>").unwrap()); + .push(syn::parse_str("A::Error: From<::askama_mendes::Error>").unwrap()); buf.writeln( format!( @@ -317,7 +317,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { )?; buf.writeln(&format!( - "::mendes::askama::into_response(app, req, &self, {:?})", + "::askama_mendes::into_response(app, req, &self, {:?})", self.input.extension() ))?; buf.writeln("}")?; |