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> using err.into_box() or
  • std::io::Error using err.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

our actix-web example web-app crates.io: 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

our axum example web-app crates.io: 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

our poem example web-app crates.io: 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

our rocket example web-app crates.io: 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

our salvo example web-app crates.io: 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

our warp example web-app crates.io: 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()
        }
    }
}