Working with web-frameworks
Askama's Template::render()
returns Result<String, askama::Error>
.
To make this result work in your preferred web-framework, you'll need to handle both cases:
converting the String
to a web-response with the correct Content-Type
,
and the Error
case to a proper error message.
While in many cases it will be enough to simply convert the Error
to
Box<dyn std::error::Error + Send + Sync>
usingerr.into_box()
orstd::io::Error
usingerr.into_io_error()
it is recommended to use a custom error type. This way you can display the error message in your app's layout, and you are better prepared for the likely case that your app grows in the future. Maybe you'll need to access a database and handle errors? Maybe you'll add multiple languages and you want to localize error messages?
The crates thiserror
and displaydoc
can be useful to implement this error type.
Simplified alternative
Alternatively, you can use #[derive(askama_web::WebTemplate)]
to automatically implement e.g. actix-web's Responder
, axum's IntoResponse
or warp's Reply
.
The library implements traits for all web-frameworks mentioned down below (and then some),
but it does not stylize error messages.
If you don't need custom / stylized error messages,
e.g. because you know that your templates won't have rendering errors, then using
askama_web
might work for you, too.
Actix-Web
To convert the String
to an HTML response, you can use
Html::new(_)
.
use actix_web::web::Html;
use actix_web::{Responder, handler};
#[handler]
fn handler() -> Result<impl Responder, AppError> {
…
Ok(Html::new(template.render()?))
}
To implement your own error type, you can use this boilerplate code:
use actix_web::{HttpResponse, Responder};
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::web::Html;
use askama::Template;
#[derive(Debug, displaydoc::Display, thiserror::Error)]
enum AppError {
/// could not render template
Render(#[from] askama::Error),
}
impl ResponseError for AppError {
fn status_code(&self) -> StatusCode {
match &self {
AppError::Render(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
impl Responder for AppError {
type Body = String;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
#[derive(Debug, Template)]
#[template(path = "error.html")]
struct Tmpl { … }
let tmpl = Tmpl { … };
if let Ok(body) = tmpl.render() {
(Html::new(body), self.status_code()).respond_to(req)
} else {
(String::new(), self.status_code()).respond_to(req)
}
}
}
Axum
To convert the String
to an HTML response, you can use
Html(_)
.
use axum::response::{Html, IntoResponse};
async fn handler() -> Result<impl IntoResponse, AppError> {
…
Ok(Html(template.render()?))
}
To implement your own error type, you can use this boilerplate code:
use axum::response::IntoResponse;
use askama::Template;
#[derive(Debug, displaydoc::Display, thiserror::Error)]
enum AppError {
/// could not render template
Render(#[from] askama::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
#[derive(Debug, Template)]
#[template(path = "error.html")]
struct Tmpl { … }
let status = match &self {
AppError::Render(_) => StatusCode::INTERNAL_SERVER_ERROR,
};
let tmpl = Tmpl { … };
if let Ok(body) = tmpl.render() {
(status, Html(body)).into_response()
} else {
(status, "Something went wrong").into_response()
}
}
}
Poem
To convert the String
to an HTML response, you can use
Html(_)
.
use poem::web::Html;
use poem::{IntoResponse, handler};
#[handler]
async fn handler() -> Result<impl IntoResponse, AppError> {
…
Ok(Html(template.render()?))
}
To implement your own error type, you can use this boilerplate code:
use poem::error::ResponseError;
use poem::http::StatusCode;
use poem::web::Html;
use poem::{IntoResponse, Response};
use askama::Template;
#[derive(Debug, displaydoc::Display, thiserror::Error)]
enum AppError {
/// could not render template
Render(#[from] askama::Error),
}
impl ResponseError for AppError {
fn status(&self) -> StatusCode {
match &self {
AppError::Render(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
#[derive(Debug, Template)]
#[template(path = "error.html")]
struct Tmpl { … }
let tmpl = Tmpl { … };
if let Ok(body) = tmpl.render() {
(self.status(), Html(body)).into_response()
} else {
(self.status(), "Something went wrong").into_response()
}
}
}
Rocket
To convert the String
to an HTML response, you can use
RawHtml(_)
.
use rocket::get;
use rocket::response::content::RawHtml;
use rocket::response::Responder;
#[get(…)]
fn handler<'r>() -> Result<impl Responder<'r, 'static>, AppError> {
…
Ok(RawHtml(template.render()?))
}
To implement your own error type, you can use this boilerplate code:
use askama::Template;
use rocket::http::Status;
use rocket::response::content::RawHtml;
use rocket::response::Responder;
use rocket::{Request, Response};
#[derive(Debug, displaydoc::Display, thiserror::Error)]
enum AppError {
/// could not render template
Render(#[from] askama::Error),
}
impl<'r> Responder<'r, 'static> for AppError {
fn respond_to(
self,
request: &'r Request<'_>,
) -> Result<Response<'static>, Status> {
#[derive(Debug, Template)]
#[template(path = "error.html")]
struct Tmpl { … }
let status = match &self {
AppError::Render(_) => Status::InternalServerError,
};
let template = Tmpl { … };
if let Ok(body) = template.render() {
(status, RawHtml(body)).respond_to(request)
} else {
(status, "Something went wrong").respond_to(request)
}
}
}
Salvo
To convert the String
to an HTML response, you can use
Text::Html(_)
.
use salvo::writing::Text;
use salvo::{Scribe, handler};
#[handler]
async fn handler() -> Result<impl Scribe, AppError> {
…
Ok(Text::Html(template.render()?))
}
To implement your own error type, you can use this boilerplate code:
use askama::Template;
use salvo::http::StatusCode;
use salvo::writing::Text;
use salvo::{Response, Scribe};
#[derive(Debug, displaydoc::Display, thiserror::Error)]
enum AppError {
/// could not render template
Render(#[from] askama::Error),
}
impl Scribe for AppError {
fn render(self, res: &mut Response) {
#[derive(Debug, Template)]
#[template(path = "error.html")]
struct Tmpl { … }
res.status_code(match &self {
AppError::Render(_) => StatusCode::INTERNAL_SERVER_ERROR,
});
let tmpl = Tmpl { … };
if let Ok(body) = tmpl.render() {
Text::Html(body).render(res);
} else {
Text::Plain("Something went wrong").render(res);
}
}
}
Warp
To convert the String
to an HTML response, you can use
html(_)
.
use warp::reply::{Reply, html};
fn handler() -> Result<impl Reply, AppError> {
…
Ok(html(template.render()?))
}
To implement your own error type, you can use this boilerplate code:
use http::StatusCode;
use warp::reply::{Reply, Response, html};
#[derive(Debug, displaydoc::Display, thiserror::Error)]
enum AppError {
/// could not render template
Render(#[from] askama::Error),
}
impl Reply for AppError {
fn into_response(self) -> Response {
#[derive(Debug, Template)]
#[template(path = "error.html")]
struct Tmpl { … }
let status = match &self {
AppError::Render(_) => StatusCode::INTERNAL_SERVER_ERROR,
};
let template = Tmpl { … };
if let Ok(body) = template.render() {
with_status(html(body), status).into_response()
} else {
status.into_response()
}
}
}