aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/rust.yml13
-rw-r--r--Cargo.toml1
-rw-r--r--README.md2
-rw-r--r--askama/Cargo.toml1
-rw-r--r--askama_axum/Cargo.toml23
l---------askama_axum/LICENSE-APACHE1
l---------askama_axum/LICENSE-MIT1
-rw-r--r--askama_axum/README.md9
-rw-r--r--askama_axum/src/lib.rs25
-rw-r--r--askama_axum/templates/hello.html1
-rw-r--r--askama_axum/tests/basic.rs35
-rw-r--r--askama_derive/Cargo.toml1
-rw-r--r--askama_derive/src/lib.rs1
-rw-r--r--askama_shared/src/generator.rs22
-rw-r--r--askama_shared/src/lib.rs1
-rw-r--r--askama_shared/src/parser.rs35
-rw-r--r--book/src/integrations.md12
-rw-r--r--testing/templates/raw-ws.html2
-rw-r--r--testing/tests/simple.rs10
19 files changed, 177 insertions, 19 deletions
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 398cb46..b62ae5d 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -48,6 +48,19 @@ jobs:
- run: cargo test --package askama_actix --all-targets
- run: cargo clippy --package askama_actix --all-targets -- -D warnings
+ Axum:
+ 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_axum --all-targets
+ - run: cargo clippy --package askama_axum --all-targets -- -D warnings
+
Gotham:
runs-on: ubuntu-latest
steps:
diff --git a/Cargo.toml b/Cargo.toml
index 2971a14..fd273c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
members = [
"askama",
"askama_actix",
+ "askama_axum",
"askama_gotham",
"askama_derive",
"askama_escape",
diff --git a/README.md b/README.md
index a1b0156..5e077c1 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ in a for-profit context, please consider supporting my open source work on
* Construct templates using a familiar, easy-to-use syntax
* Benefit from the safety provided by Rust's type system
* Template code is compiled into your crate for [optimal performance][benchmarks]
-* Optional built-in support for Actix, Gotham, Iron, Rocket, tide, and warp web frameworks
+* Optional built-in support for Actix, Axum, Gotham, Iron, Rocket, tide, and warp web frameworks
* Debugging features to assist you in template development
* Templates must be valid UTF-8 and produce UTF-8 when rendered
* IDE support available in [JetBrains products](https://plugins.jetbrains.com/plugin/16591-askama-template-support)
diff --git a/askama/Cargo.toml b/askama/Cargo.toml
index 2e67892..f42e7d8 100644
--- a/askama/Cargo.toml
+++ b/askama/Cargo.toml
@@ -24,6 +24,7 @@ serde-json = ["askama_shared/json"]
serde-yaml = ["askama_shared/yaml"]
num-traits = ["askama_shared/num-traits"]
with-actix-web = ["askama_derive/actix-web"]
+with-axum = ["askama_derive/axum"]
with-gotham = ["askama_derive/gotham"]
with-iron = ["askama_derive/iron"]
with-mendes = ["askama_derive/mendes"]
diff --git a/askama_axum/Cargo.toml b/askama_axum/Cargo.toml
new file mode 100644
index 0000000..7d3045d
--- /dev/null
+++ b/askama_axum/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "askama_axum"
+version = "0.10.0"
+authors = ["Michael Alyn Miller <malyn@strangeGizmo.com>"]
+edition = "2018"
+description = "Axum integration for Askama templates"
+keywords = ["markup", "template", "jinja2", "html", "axum"]
+categories = ["template-engine"]
+homepage = "https://github.com/djc/askama"
+repository = "https://github.com/djc/askama"
+documentation = "https://docs.rs/askama"
+license = "MIT OR Apache-2.0"
+workspace = ".."
+readme = "README.md"
+
+[dependencies]
+askama = { version = "0.11.0-beta.1", path = "../askama", default-features = false, features = ["with-axum", "mime", "mime_guess"] }
+axum = { version = "0.3", default-features = false }
+
+[dev-dependencies]
+hyper = { version = "0.14", features = ["full"] }
+tokio = { version = "1.0", features = ["full"] }
+tower = { version = "0.4", features = ["util"] } \ No newline at end of file
diff --git a/askama_axum/LICENSE-APACHE b/askama_axum/LICENSE-APACHE
new file mode 120000
index 0000000..965b606
--- /dev/null
+++ b/askama_axum/LICENSE-APACHE
@@ -0,0 +1 @@
+../LICENSE-APACHE \ No newline at end of file
diff --git a/askama_axum/LICENSE-MIT b/askama_axum/LICENSE-MIT
new file mode 120000
index 0000000..76219eb
--- /dev/null
+++ b/askama_axum/LICENSE-MIT
@@ -0,0 +1 @@
+../LICENSE-MIT \ No newline at end of file
diff --git a/askama_axum/README.md b/askama_axum/README.md
new file mode 100644
index 0000000..584dbd9
--- /dev/null
+++ b/askama_axum/README.md
@@ -0,0 +1,9 @@
+# askama_axum: Askama integration with Axum
+
+[![Documentation](https://docs.rs/askama_axum/badge.svg)](https://docs.rs/askama_axum/)
+[![Latest version](https://img.shields.io/crates/v/askama_axum.svg)](https://crates.io/crates/askama_axum)
+[![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 Axum web framework.
diff --git a/askama_axum/src/lib.rs b/askama_axum/src/lib.rs
new file mode 100644
index 0000000..0c21464
--- /dev/null
+++ b/askama_axum/src/lib.rs
@@ -0,0 +1,25 @@
+#![deny(elided_lifetimes_in_paths)]
+
+pub use askama::*;
+use axum::{
+ self,
+ body::{Bytes, Full},
+ http::{Response, StatusCode},
+};
+
+pub fn into_response<T: Template>(t: &T, ext: &str) -> axum::http::Response<Full<Bytes>> {
+ match t.render() {
+ Ok(body) => Response::builder()
+ .status(StatusCode::OK)
+ .header(
+ "content-type",
+ askama::mime::extension_to_mime_type(ext).to_string(),
+ )
+ .body(body.into())
+ .unwrap(),
+ Err(_) => Response::builder()
+ .status(StatusCode::INTERNAL_SERVER_ERROR)
+ .body(vec![].into())
+ .unwrap(),
+ }
+}
diff --git a/askama_axum/templates/hello.html b/askama_axum/templates/hello.html
new file mode 100644
index 0000000..8149be7
--- /dev/null
+++ b/askama_axum/templates/hello.html
@@ -0,0 +1 @@
+Hello, {{ name }}!
diff --git a/askama_axum/tests/basic.rs b/askama_axum/tests/basic.rs
new file mode 100644
index 0000000..acd53b7
--- /dev/null
+++ b/askama_axum/tests/basic.rs
@@ -0,0 +1,35 @@
+use askama::Template;
+use axum::{
+ body::Body,
+ http::{Request, StatusCode},
+ routing::get,
+ Router,
+};
+use tower::util::ServiceExt;
+
+#[derive(Template)]
+#[template(path = "hello.html")]
+struct HelloTemplate<'a> {
+ name: &'a str,
+}
+
+async fn hello() -> HelloTemplate<'static> {
+ HelloTemplate { name: "world" }
+}
+
+#[tokio::test]
+async fn template_to_response() {
+ let app = Router::new().route("/", get(hello));
+
+ let res = app
+ .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
+ .await
+ .unwrap();
+ assert_eq!(res.status(), StatusCode::OK);
+
+ let headers = res.headers();
+ assert_eq!(headers["Content-Type"], "text/html; charset=utf-8");
+
+ let body = hyper::body::to_bytes(res.into_body()).await.unwrap();
+ assert_eq!(&body[..], b"Hello, world!");
+}
diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml
index a30aef4..60ad48d 100644
--- a/askama_derive/Cargo.toml
+++ b/askama_derive/Cargo.toml
@@ -16,6 +16,7 @@ proc-macro = true
config = ["askama_shared/config"]
actix-web = []
+axum = []
gotham = []
iron = []
mendes = []
diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs
index f2bee66..35070fe 100644
--- a/askama_derive/src/lib.rs
+++ b/askama_derive/src/lib.rs
@@ -111,6 +111,7 @@ fn find_used_templates(
const INTEGRATIONS: Integrations = Integrations {
actix: cfg!(feature = "actix-web"),
+ axum: cfg!(feature = "axum"),
gotham: cfg!(feature = "gotham"),
iron: cfg!(feature = "iron"),
mendes: cfg!(feature = "mendes"),
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs
index 78ada4b..cb395d4 100644
--- a/askama_shared/src/generator.rs
+++ b/askama_shared/src/generator.rs
@@ -96,6 +96,9 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
if self.integrations.actix {
self.impl_actix_web_responder(&mut buf)?;
}
+ if self.integrations.axum {
+ self.impl_axum_into_response(&mut buf)?;
+ }
if self.integrations.gotham {
self.impl_gotham_into_response(&mut buf)?;
}
@@ -222,6 +225,21 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
buf.writeln("}")
}
+ // Implement Axum's `IntoResponse`.
+ fn impl_axum_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
+ self.write_header(buf, "::axum::response::IntoResponse", None)?;
+ buf.writeln(
+ "type Body = ::axum::body::Full<::axum::body::Bytes>;\n\
+ type BodyError = std::convert::Infallible;\n\
+ fn into_response(self)\
+ -> ::axum::http::Response<Self::Body> {",
+ )?;
+ let ext = self.input.extension().unwrap_or("txt");
+ buf.writeln(&format!("::askama_axum::into_response(&self, {:?})", ext))?;
+ buf.writeln("}")?;
+ buf.writeln("}")
+ }
+
// Implement gotham's `IntoResponse`.
fn impl_gotham_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
self.write_header(buf, "::askama_gotham::IntoResponse", None)?;
@@ -439,9 +457,9 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
self.flush_ws(m.ws1);
self.prepare_ws(m.ws2);
}
- Node::Raw(ws1, contents, ws2) => {
+ Node::Raw(ws1, lws, val, rws, ws2) => {
self.handle_ws(ws1);
- self.buf_writable.push(Writable::Lit(contents));
+ self.visit_lit(lws, val, rws);
self.handle_ws(ws2);
}
Node::Import(ws, _, _) => {
diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs
index f217cfb..8d64e7b 100644
--- a/askama_shared/src/lib.rs
+++ b/askama_shared/src/lib.rs
@@ -274,6 +274,7 @@ pub fn get_template_source(tpl_path: &Path) -> std::result::Result<String, Compi
#[derive(Clone, Copy, Debug)]
pub struct Integrations {
pub actix: bool,
+ pub axum: bool,
pub gotham: bool,
pub iron: bool,
pub mendes: bool,
diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs
index 44902d0..535c7f2 100644
--- a/askama_shared/src/parser.rs
+++ b/askama_shared/src/parser.rs
@@ -4,7 +4,7 @@ use std::str;
use nom::branch::alt;
use nom::bytes::complete::{escaped, is_not, tag, take_till, take_until};
use nom::character::complete::{anychar, char, digit1};
-use nom::combinator::{complete, cut, eof, map, not, opt, recognize, value};
+use nom::combinator::{complete, consumed, cut, eof, map, not, opt, peek, recognize, value};
use nom::error::{Error, ErrorKind};
use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1};
use nom::sequence::{delimited, pair, preceded, terminated, tuple};
@@ -28,7 +28,7 @@ pub enum Node<'a> {
Include(Ws, &'a str),
Import(Ws, &'a str, &'a str),
Macro(&'a str, Macro<'a>),
- Raw(Ws, &'a str, Ws),
+ Raw(Ws, &'a str, &'a str, &'a str, Ws),
Break(Ws),
Continue(Ws),
}
@@ -1048,29 +1048,32 @@ fn block_macro<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
}
fn block_raw<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
+ let endraw = tuple((
+ |i| tag_block_start(i, s),
+ opt(char('-')),
+ ws(tag("endraw")),
+ opt(char('-')),
+ peek(|i| tag_block_end(i, s)),
+ ));
+
let mut p = tuple((
opt(char('-')),
ws(tag("raw")),
cut(tuple((
opt(char('-')),
|i| tag_block_end(i, s),
- take_until("{% endraw %}"),
- |i| tag_block_start(i, s),
- opt(char('-')),
- ws(tag("endraw")),
- opt(char('-')),
+ consumed(skip_till(endraw)),
))),
));
- let (i, (pws1, _, (nws1, _, contents, _, pws2, _, nws2))) = p(i)?;
- Ok((
- i,
- Node::Raw(
- Ws(pws1.is_some(), nws1.is_some()),
- contents,
- Ws(pws2.is_some(), nws2.is_some()),
- ),
- ))
+ let (_, (pws1, _, (nws1, _, (contents, (i, (_, pws2, _, nws2, _)))))) = p(i)?;
+ let (lws, val, rws) = match split_ws_parts(contents) {
+ Node::Lit(lws, val, rws) => (lws, val, rws),
+ _ => unreachable!(),
+ };
+ let ws1 = Ws(pws1.is_some(), nws1.is_some());
+ let ws2 = Ws(pws2.is_some(), nws2.is_some());
+ Ok((i, Node::Raw(ws1, lws, val, rws, ws2)))
}
fn break_statement<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> {
diff --git a/book/src/integrations.md b/book/src/integrations.md
index a972e4d..918b887 100644
--- a/book/src/integrations.md
+++ b/book/src/integrations.md
@@ -20,6 +20,18 @@ a value of that type in an Actix-web handler. See
[the example](https://github.com/djc/askama/blob/main/askama_actix/tests/basic.rs)
from the Askama test suite for more on how to integrate.
+## Axum integration
+
+Enabling the `with-axum` feature appends an implementation of Axum's
+`IntoResponse` trait for each template type. This makes it easy to trivially
+return a value of that type in a Axum handler. See
+[the example](https://github.com/djc/askama/blob/main/askama_axum/tests/basic.rs)
+from the Askama test suite for more on how to integrate.
+
+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.
+
## Gotham integration
Enabling the `with-gotham` feature appends an implementation of Gotham's
diff --git a/testing/templates/raw-ws.html b/testing/templates/raw-ws.html
new file mode 100644
index 0000000..d590be7
--- /dev/null
+++ b/testing/templates/raw-ws.html
@@ -0,0 +1,2 @@
+<{% raw -%} {{hello}} {%- endraw %}>
+< {%- raw %}{{bye}}{% endraw -%} >
diff --git a/testing/tests/simple.rs b/testing/tests/simple.rs
index c712900..e66485a 100644
--- a/testing/tests/simple.rs
+++ b/testing/tests/simple.rs
@@ -433,6 +433,16 @@ fn test_raw_complex() {
);
}
+#[derive(Template)]
+#[template(path = "raw-ws.html")]
+struct RawTemplateWs;
+
+#[test]
+fn test_raw_ws() {
+ let template = RawTemplateWs;
+ assert_eq!(template.render().unwrap(), "<{{hello}}>\n<{{bye}}>");
+}
+
mod without_import_on_derive {
#[derive(askama::Template)]
#[template(source = "foo", ext = "txt")]