Move Actix-Web integration into separate askama_actix crate

This commit is contained in:
Dirkjan Ochtman 2020-01-28 21:26:13 +01:00
parent 9026f616e6
commit b56c11639f
10 changed files with 111 additions and 86 deletions

View File

@ -29,11 +29,10 @@ jobs:
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
with: with:
command: build command: build
args: --workspace --all-targets args: --all-targets
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --workspace
stable-integrations: stable-integrations:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -48,6 +47,20 @@ jobs:
cd testing cd testing
cargo test --features full cargo test --features full
Actix-Web:
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_actix --all-targets
cargo clippy --package askama_actix --all-targets -- -D warnings
nightly: nightly:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -71,12 +84,6 @@ jobs:
toolchain: stable toolchain: stable
override: true override: true
components: rustfmt, clippy components: rustfmt, clippy
- uses: actions-rs/cargo@v1 - run: |
with: cargo fmt --all -- --check
command: fmt cargo clippy --all-targets -- -D warnings
args: --all -- --check
- uses: actions-rs/cargo@v1
if: always()
with:
command: clippy
args: --workspace --all-targets -- -D warnings

View File

@ -1,2 +1,16 @@
[workspace] [workspace]
members = ["askama", "askama_derive", "askama_escape", "askama_shared", "testing"] members = [
"askama",
"askama_actix",
"askama_derive",
"askama_escape",
"askama_shared",
"testing",
]
default-members = [
"askama",
"askama_derive",
"askama_escape",
"askama_shared",
"testing",
]

View File

@ -26,7 +26,7 @@ serde-yaml = ["askama_shared/yaml"]
num-traits = ["askama_shared/num-traits"] num-traits = ["askama_shared/num-traits"]
with-iron = ["iron", "askama_derive/iron"] with-iron = ["iron", "askama_derive/iron"]
with-rocket = ["rocket", "askama_derive/rocket"] with-rocket = ["rocket", "askama_derive/rocket"]
with-actix-web = ["actix-web", "askama_derive/actix-web", "mime", "mime_guess", "bytes", "futures"] with-actix-web = ["askama_derive/actix-web"]
with-gotham = ["gotham", "askama_derive/gotham", "hyper", "mime", "mime_guess"] with-gotham = ["gotham", "askama_derive/gotham", "hyper", "mime", "mime_guess"]
[dependencies] [dependencies]
@ -35,13 +35,10 @@ askama_escape = { version = "0.3.0", path = "../askama_escape" }
askama_shared = { version = "0.9.0", path = "../askama_shared", default-features = false } askama_shared = { version = "0.9.0", path = "../askama_shared", default-features = false }
iron = { version = ">= 0.5, < 0.7", optional = true } iron = { version = ">= 0.5, < 0.7", optional = true }
rocket = { version = "0.4", default-features = false, optional = true } rocket = { version = "0.4", default-features = false, optional = true }
actix-web = { version = "2", optional = true }
futures = { version = "0.3", optional = true }
mime = { version = "0.3", optional = true } mime = { version = "0.3", optional = true }
mime_guess = { version = "2.0.0-alpha", optional = true } mime_guess = { version = "2.0.0-alpha", optional = true }
gotham = { version = "0.3", default-features = false, optional = true } gotham = { version = "0.3", default-features = false, optional = true }
hyper = { version = "0.12", optional = true } hyper = { version = "0.12", optional = true }
bytes = { version = "0.5", optional = true }
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["config", "humansize", "num-traits", "serde-json", "serde-yaml"] features = ["config", "humansize", "num-traits", "serde-json", "serde-yaml"]

View File

@ -542,65 +542,33 @@ pub mod rocket {
} }
} }
#[cfg(all(feature = "mime_guess", feature = "mime"))] pub mod mime {
fn get_mime_type(ext: &str) -> mime_guess::Mime { #[cfg(all(feature = "mime_guess", feature = "mime"))]
let basic_type = mime_guess::from_ext(ext).first_or_octet_stream(); pub fn extension_to_mime_type(ext: &str) -> mime_guess::Mime {
for (simple, utf_8) in &TEXT_TYPES { let basic_type = mime_guess::from_ext(ext).first_or_octet_stream();
if &basic_type == simple { for (simple, utf_8) in &TEXT_TYPES {
return utf_8.clone(); if &basic_type == simple {
return utf_8.clone();
}
} }
} basic_type
basic_type
}
#[cfg(all(feature = "mime_guess", feature = "mime"))]
const TEXT_TYPES: [(mime_guess::Mime, mime_guess::Mime); 6] = [
(mime::TEXT_PLAIN, mime::TEXT_PLAIN_UTF_8),
(mime::TEXT_HTML, mime::TEXT_HTML_UTF_8),
(mime::TEXT_CSS, mime::TEXT_CSS_UTF_8),
(mime::TEXT_CSV, mime::TEXT_CSV_UTF_8),
(
mime::TEXT_TAB_SEPARATED_VALUES,
mime::TEXT_TAB_SEPARATED_VALUES_UTF_8,
),
(
mime::APPLICATION_JAVASCRIPT,
mime::APPLICATION_JAVASCRIPT_UTF_8,
),
];
#[cfg(feature = "with-actix-web")]
pub mod actix_web {
use actix_web;
use bytes;
use mime_guess;
use std::fmt;
// actix_web technically has this as a pub fn in later versions, fs::file_extension_to_mime.
// Older versions that don't have it exposed are easier this way. If ext is empty or no
// associated type was found, then this returns `application/octet-stream`, in line with how
// actix_web handles it in newer releases.
pub use self::actix_web::{
error::ErrorInternalServerError, Error, HttpRequest, HttpResponse, Responder,
};
pub trait TemplateIntoResponse {
fn into_response(&self) -> Result<HttpResponse, Error>;
} }
impl<T: super::Template> TemplateIntoResponse for T { #[cfg(all(feature = "mime_guess", feature = "mime"))]
fn into_response(&self) -> Result<HttpResponse, Error> { const TEXT_TYPES: [(mime_guess::Mime, mime_guess::Mime); 6] = [
let mut buffer = actix_web::web::BytesMut::with_capacity(self.size_hint()); (mime::TEXT_PLAIN, mime::TEXT_PLAIN_UTF_8),
self.render_into(&mut buffer) (mime::TEXT_HTML, mime::TEXT_HTML_UTF_8),
.map_err(|_| ErrorInternalServerError("Template parsing error"))?; (mime::TEXT_CSS, mime::TEXT_CSS_UTF_8),
(mime::TEXT_CSV, mime::TEXT_CSV_UTF_8),
let ctype = super::get_mime_type(self.extension().unwrap_or("txt")).to_string(); (
Ok(HttpResponse::Ok() mime::TEXT_TAB_SEPARATED_VALUES,
.content_type(ctype.as_str()) mime::TEXT_TAB_SEPARATED_VALUES_UTF_8,
.body(buffer.freeze())) ),
} (
} mime::APPLICATION_JAVASCRIPT,
mime::APPLICATION_JAVASCRIPT_UTF_8,
),
];
} }
#[cfg(feature = "with-gotham")] #[cfg(feature = "with-gotham")]
@ -611,7 +579,7 @@ pub mod gotham {
pub use hyper::{Body, Response, StatusCode}; pub use hyper::{Body, Response, StatusCode};
pub fn respond<T: super::Template>(t: &T, ext: &str) -> Response<Body> { pub fn respond<T: super::Template>(t: &T, ext: &str) -> Response<Body> {
let mime_type = super::get_mime_type(ext).to_string(); let mime_type = super::mime::extension_to_mime_type(ext).to_string();
match t.render() { match t.render() {
Ok(body) => Response::builder() Ok(body) => Response::builder()

22
askama_actix/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "askama_actix"
version = "0.9.0"
authors = ["Dirkjan Ochtman <dirkjan@ochtman.nl>"]
description = "Actix-Web 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 = ".."
edition = "2018"
[dependencies]
actix-web = { version = "2" }
askama = { version = "0.9.0", path = "../askama", features = ["with-actix-web", "mime", "mime_guess"] }
bytes = { version = "0.5" }
futures = { version = "0.3" }
[dev-dependencies]
actix-rt = { version = "1", default-features = false }

22
askama_actix/src/lib.rs Normal file
View File

@ -0,0 +1,22 @@
pub use askama::*;
use bytes::BytesMut;
use actix_web::{error::ErrorInternalServerError, Error, HttpResponse};
pub trait TemplateIntoResponse {
fn into_response(&self) -> ::std::result::Result<HttpResponse, Error>;
}
impl<T: askama::Template> TemplateIntoResponse for T {
fn into_response(&self) -> ::std::result::Result<HttpResponse, Error> {
let mut buffer = BytesMut::with_capacity(self.size_hint());
self.render_into(&mut buffer)
.map_err(|_| ErrorInternalServerError("Template parsing error"))?;
let ctype =
askama::mime::extension_to_mime_type(self.extension().unwrap_or("txt")).to_string();
Ok(HttpResponse::Ok()
.content_type(ctype.as_str())
.body(buffer.freeze()))
}
}

View File

@ -0,0 +1 @@
Hello, {{ name }}!

View File

@ -1,8 +1,7 @@
#![cfg(feature = "actix")]
use actix_web::http::header::CONTENT_TYPE; use actix_web::http::header::CONTENT_TYPE;
use actix_web::test; use actix_web::test;
use actix_web::web; use actix_web::web;
use askama::{actix_web::TemplateIntoResponse, Template}; use askama_actix::{Template, TemplateIntoResponse};
use bytes::Bytes; use bytes::Bytes;
#[derive(Template)] #[derive(Template)]

View File

@ -241,15 +241,15 @@ impl<'a> Generator<'a> {
// Implement Actix-web's `Responder`. // Implement Actix-web's `Responder`.
fn impl_actix_web_responder(&mut self, buf: &mut Buffer) { fn impl_actix_web_responder(&mut self, buf: &mut Buffer) {
self.write_header(buf, "::askama::actix_web::Responder", None); self.write_header(buf, "::actix_web::Responder", None);
buf.writeln("type Future = ::futures::future::Ready<::std::result::Result<::askama::actix_web::HttpResponse, Self::Error>>;"); buf.writeln("type Future = ::futures::future::Ready<::std::result::Result<::actix_web::HttpResponse, Self::Error>>;");
buf.writeln("type Error = ::askama::actix_web::Error;"); buf.writeln("type Error = ::actix_web::Error;");
buf.writeln( buf.writeln(
"fn respond_to(self, _req: &::askama::actix_web::HttpRequest) \ "fn respond_to(self, _req: &::actix_web::HttpRequest) \
-> Self::Future {", -> Self::Future {",
); );
buf.writeln("use ::askama::actix_web::TemplateIntoResponse;"); buf.writeln("use ::askama_actix::TemplateIntoResponse;");
buf.writeln("::futures::future::ready(self.into_response())"); buf.writeln("::futures::future::ready(self.into_response())");
buf.writeln("}"); buf.writeln("}");

View File

@ -6,20 +6,15 @@ workspace = ".."
edition = "2018" edition = "2018"
[features] [features]
actix = ["actix-web", "actix-rt", "bytes", "askama/with-actix-web", "futures"]
default = [] default = []
full = ["actix", "with-iron", "serde-json", "with-gotham"] full = ["with-iron", "serde-json", "with-gotham"]
serde-json = ["serde_json", "askama/serde-json"] serde-json = ["serde_json", "askama/serde-json"]
with-rocket = ["rocket", "askama/with-rocket"] with-rocket = ["rocket", "askama/with-rocket"]
with-iron = ["iron", "askama/with-iron"] with-iron = ["iron", "askama/with-iron"]
with-gotham = ["gotham", "askama/with-gotham", "mime", "hyper"] with-gotham = ["gotham", "askama/with-gotham", "mime", "hyper"]
[dependencies] [dependencies]
actix-web = { version = "2", optional = true }
actix-rt = { version = "1", optional = true }
futures = { version = "0.3", optional = true }
askama = { path = "../askama", version = "*" } askama = { path = "../askama", version = "*" }
bytes = { version = "0.5", optional = true }
iron = { version = "0.6", optional = true } iron = { version = "0.6", optional = true }
rocket = { version = "0.4", default-features = false, optional = true } rocket = { version = "0.4", default-features = false, optional = true }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true }