LoginSignup
2
1

Rust + Actix-web でのHTMLのテンプレートエンジンについて

Last updated at Posted at 2023-11-30

前書き

RUST + Actix-web の開発において、何らかのHTMLテンプレートをレンダーするテンプレートエンジンを探していたのですが、Actix-webの実装例をまとめた公式サイト(actix/examples)を見ても複数出てきて情報が過多だったので、ざっくり情報を集めてみた際の備忘録 (2023/11/30時点での情報) になります。

比較対象

actix/examples のリポジトリの /templating/ 配下にあるものを候補としてます。

比較対象にしなかったもの

下記はactix/examples のリポジトリの /templating/ 配下になかったので今回は非対象にしました。

Github のリポジトリベースでの比較 (確認日: 2023/11/30)

Githubのリポジトリの情報でざっくり比較すると下記になりました。
各列の上位2件まで色を緑に変更しております。

名称 Star Fork Issue
(Open)
Issue
(Close)
最新Tag 1つ前のマイナーTag URL
Askama 2.7k 191 97 249 0.12.0
(2023/03/06)
0.11.0
(2021/12/22)
https://github.com/djc/askama
Fluent Templates 122 17 12 22 v0.8.0
(2022/08/08)
v0.7.1
(2022/03/16)
https://github.com/XAMPPRocky/fluent-templates
handlebars-rust 1.1k 132 25 278 v4.5.0
(2023/11/09)
v4.4.0
(2023/09/01)
https://github.com/sunng87/handlebars-rust
MiniJinja 1.1k 72 4 114 1.0.10
(2023/11/10)
0.34.0
(2023/06/02)
https://github.com/mitsuhiko/minijinja
SailFish 624 50 30 49 v0.8.1
(2023/10/10)
v0.7.0
(2023/06/23)
https://github.com/rust-sailfish/sailfish
Tera 3k 254 141 378 v1.18.1
(2023/03/16)
v1.17.1
(2022/09/19)
https://github.com/Keats/tera
TinyTemplate 176 42 12 13 1.2.1
(2021/03/04)
1.1.0
(2020/06/01)
https://github.com/bheisler/TinyTemplate
Yarte 265 16 29 69 yarte-v0.15.7
(2022/09/07)
yarte-v0.15.6
(2022/09/07) ※1
https://github.com/zzau13/yarte

※1 直近のマイナーバージョンのTagが存在しないので、もっとも古いTagを記載。

実装の例

actix/examples のリポジトリの /templating/ 配下にある main.rs を転記しています。
詳細な情報についてはactix/examples のリポジトリの /templating/ 配下をご確認ください。

Askama

Askamaのコード例(※クリックで展開)
use std::collections::HashMap;

use actix_web::{middleware, web, App, HttpServer, Responder, Result};
use actix_web_lab::respond::Html;
use askama::Template;

#[derive(Template)]
#[template(path = "user.html")]
struct UserTemplate<'a> {
    name: &'a str,
    text: &'a str,
}

#[derive(Template)]
#[template(path = "index.html")]
struct Index;

async fn index(query: web::Query<HashMap<String, String>>) -> Result<impl Responder> {
    let html = if let Some(name) = query.get("name") {
        UserTemplate {
            name,
            text: "Welcome!",
        }
        .render()
        .expect("template should be valid")
    } else {
        Index.render().expect("template should be valid")
    };

    Ok(Html(html))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

    log::info!("starting HTTP server at http://localhost:8080");

    HttpServer::new(move || {
        App::new()
            .wrap(middleware::Logger::default())
            .service(web::resource("/").route(web::get().to(index)))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

source: https://github.com/actix/examples/blob/master/templating/askama/src/main.rs

Fluent Templates

Fluent Templatesのコード例(※クリックで展開)
use std::io;

use actix_web::{
    body::BoxBody,
    dev::ServiceResponse,
    get,
    http::{header::ContentType, StatusCode},
    middleware::{ErrorHandlerResponse, ErrorHandlers},
    web, App, HttpResponse, HttpServer, Responder, Result,
};
use actix_web_lab::{extract::Path, respond::Html};
use fluent_templates::{static_loader, FluentLoader, Loader as _};
use handlebars::Handlebars;
use serde_json::json;

mod lang_choice;
use self::lang_choice::LangChoice;

static_loader! {
    static LOCALES = {
        locales: "./locales",
        fallback_language: "en",

        // removes unicode isolating marks around arguments
        // you typically should only set to false when testing.
        customise: |bundle| bundle.set_use_isolating(false),
    };
}

#[get("/")]
async fn index(hb: web::Data<Handlebars<'_>>, lang: LangChoice) -> impl Responder {
    let data = json!({ "lang": lang });
    let body = hb.render("index", &data).unwrap();
    Html(body)
}

#[get("/{user}/{data}")]
async fn user(
    hb: web::Data<Handlebars<'_>>,
    Path(info): Path<(String, String)>,
    lang: LangChoice,
) -> impl Responder {
    let data = json!({
        "lang": lang,
        "user": info.0,
        "data": info.1
    });
    let body = hb.render("user", &data).unwrap();
    Html(body)
}

#[actix_web::main]
async fn main() -> io::Result<()> {
    // Handlebars uses a repository for the compiled templates. This object must be shared between
    // the application threads, and is therefore passed to the App in an Arc.
    let mut handlebars = Handlebars::new();

    // register template dir with Handlebars registry
    handlebars
        .register_templates_directory(".html", "./templates")
        .unwrap();

    // register Fluent helper with Handlebars registry
    handlebars.register_helper("fluent", Box::new(FluentLoader::new(&*LOCALES)));

    let handlebars = web::Data::new(handlebars);

    HttpServer::new(move || {
        App::new()
            .wrap(error_handlers())
            .app_data(web::Data::clone(&handlebars))
            .service(index)
            .service(user)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

// Custom error handlers, to return HTML responses when an error occurs.
fn error_handlers() -> ErrorHandlers<BoxBody> {
    ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
}

// Error handler for a 404 Page not found error.
fn not_found<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<BoxBody>> {
    let lang = LangChoice::from_req(res.request()).lang_id();
    let error = LOCALES.lookup(&lang, "error-not-found").unwrap();

    let response = get_error_response(&res, &error);

    Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
        res.into_parts().0,
        response.map_into_left_body(),
    )))
}

// Generic error handler.
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse<BoxBody> {
    let req = res.request();
    let lang = LangChoice::from_req(req);

    // Provide a fallback to a simple plain text response in case an error occurs during the
    // rendering of the error page.

    let hb = req
        .app_data::<web::Data<Handlebars>>()
        .expect("correctly set up handlebars in app data");

    let data = json!({
        "lang": lang,
        "error": error,
        "status_code": res.status().as_str()
    });

    let body = hb.render("error", &data);

    match body {
        Ok(body) => HttpResponse::build(res.status())
            .content_type(ContentType::html())
            .body(body),

        Err(_) => HttpResponse::build(res.status())
            .content_type(ContentType::plaintext())
            .body(error.to_string()),
    }
}

source: https://github.com/actix/examples/blob/master/templating/fluent/src/main.rs

handlebars-rust

handlebars-rustのコード例(※クリックで展開)
use std::io;

use actix_web::{
    body::BoxBody,
    dev::ServiceResponse,
    get,
    http::{header::ContentType, StatusCode},
    middleware::{ErrorHandlerResponse, ErrorHandlers},
    web, App, HttpResponse, HttpServer, Result,
};
use handlebars::Handlebars;
use serde_json::json;

// Macro documentation can be found in the actix_web_codegen crate
#[get("/")]
async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
    let data = json!({
        "name": "Handlebars"
    });
    let body = hb.render("index", &data).unwrap();

    HttpResponse::Ok().body(body)
}

#[get("/{user}/{data}")]
async fn user(hb: web::Data<Handlebars<'_>>, path: web::Path<(String, String)>) -> HttpResponse {
    let info = path.into_inner();
    let data = json!({
        "user": info.0,
        "data": info.1
    });
    let body = hb.render("user", &data).unwrap();

    HttpResponse::Ok().body(body)
}

#[actix_web::main]
async fn main() -> io::Result<()> {
    // Handlebars uses a repository for the compiled templates. This object must be
    // shared between the application threads, and is therefore passed to the
    // Application Builder as an atomic reference-counted pointer.
    let mut handlebars = Handlebars::new();
    handlebars
        .register_templates_directory(".html", "./static/templates")
        .unwrap();
    let handlebars_ref = web::Data::new(handlebars);

    HttpServer::new(move || {
        App::new()
            .wrap(error_handlers())
            .app_data(handlebars_ref.clone())
            .service(index)
            .service(user)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

// Custom error handlers, to return HTML responses when an error occurs.
fn error_handlers() -> ErrorHandlers<BoxBody> {
    ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
}

// Error handler for a 404 Page not found error.
fn not_found<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<BoxBody>> {
    let response = get_error_response(&res, "Page not found");
    Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
        res.into_parts().0,
        response.map_into_left_body(),
    )))
}

// Generic error handler.
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse<BoxBody> {
    let request = res.request();

    // Provide a fallback to a simple plain text response in case an error occurs during the
    // rendering of the error page.
    let fallback = |err: &str| {
        HttpResponse::build(res.status())
            .content_type(ContentType::plaintext())
            .body(err.to_string())
    };

    let hb = request
        .app_data::<web::Data<Handlebars>>()
        .map(|t| t.get_ref());
    match hb {
        Some(hb) => {
            let data = json!({
                "error": error,
                "status_code": res.status().as_str()
            });
            let body = hb.render("error", &data);

            match body {
                Ok(body) => HttpResponse::build(res.status())
                    .content_type(ContentType::html())
                    .body(body),
                Err(_) => fallback(error),
            }
        }
        None => fallback(error),
    }
}

source: https://github.com/actix/examples/blob/master/templating/handlebars/src/main.rs

MiniJinja

MiniJinjaのコード例(※クリックで展開)
use std::{collections::HashMap, env, path::PathBuf};

use actix_utils::future::{ready, Ready};
use actix_web::{
    dev::{self, ServiceResponse},
    error,
    http::{header::ContentType, StatusCode},
    middleware::{ErrorHandlerResponse, ErrorHandlers, Logger},
    web, App, FromRequest, HttpRequest, HttpResponse, HttpServer, Responder, Result,
};
use actix_web_lab::respond::Html;
use minijinja::path_loader;
use minijinja_autoreload::AutoReloader;

struct MiniJinjaRenderer {
    tmpl_env: web::Data<minijinja_autoreload::AutoReloader>,
}

impl MiniJinjaRenderer {
    fn render(
        &self,
        tmpl: &str,
        ctx: impl Into<minijinja::value::Value>,
    ) -> actix_web::Result<Html> {
        self.tmpl_env
            .acquire_env()
            .map_err(|_| error::ErrorInternalServerError("could not acquire template env"))?
            .get_template(tmpl)
            .map_err(|_| error::ErrorInternalServerError("could not find template"))?
            .render(ctx.into())
            .map(Html)
            .map_err(|err| {
                log::error!("{err}");
                error::ErrorInternalServerError("template error")
            })
    }
}

impl FromRequest for MiniJinjaRenderer {
    type Error = actix_web::Error;
    type Future = Ready<Result<Self, Self::Error>>;

    fn from_request(req: &HttpRequest, _pl: &mut dev::Payload) -> Self::Future {
        let tmpl_env = <web::Data<minijinja_autoreload::AutoReloader>>::extract(req)
            .into_inner()
            .unwrap();

        ready(Ok(Self { tmpl_env }))
    }
}

async fn index(
    tmpl_env: MiniJinjaRenderer,
    query: web::Query<HashMap<String, String>>,
) -> actix_web::Result<impl Responder> {
    if let Some(name) = query.get("name") {
        tmpl_env.render(
            "user.html",
            minijinja::context! {
                name,
                text => "Welcome!",
            },
        )
    } else {
        tmpl_env.render("index.html", ())
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

    // If TEMPLATE_AUTORELOAD is set, then the path tracking is enabled.
    let enable_template_autoreload = env::var("TEMPLATE_AUTORELOAD").as_deref() == Ok("true");

    if enable_template_autoreload {
        log::info!("template auto-reloading is enabled");
    } else {
        log::info!(
            "template auto-reloading is disabled; run with TEMPLATE_AUTORELOAD=true to enable"
        );
    }

    // The closure is invoked every time the environment is outdated to recreate it.
    let tmpl_reloader = AutoReloader::new(move |notifier| {
        let mut env: minijinja::Environment<'static> = minijinja::Environment::new();

        let tmpl_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates");

        // if watch_path is never called, no fs watcher is created
        if enable_template_autoreload {
            notifier.watch_path(&tmpl_path, true);
        }

        env.set_loader(path_loader(tmpl_path));

        Ok(env)
    });

    let tmpl_reloader = web::Data::new(tmpl_reloader);

    log::info!("starting HTTP server at http://localhost:8080");

    HttpServer::new(move || {
        App::new()
            .app_data(tmpl_reloader.clone())
            .service(web::resource("/").route(web::get().to(index)))
            .wrap(ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found))
            .wrap(Logger::default())
    })
    .workers(2)
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

/// Error handler for a 404 Page not found error.
fn not_found<B>(svc_res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
    let res = get_error_response(&svc_res, "Page not found");

    Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
        svc_res.into_parts().0,
        res.map_into_right_body(),
    )))
}

/// Generic error handler.
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse {
    let req = res.request();

    let tmpl_env = MiniJinjaRenderer::extract(req).into_inner().unwrap();

    // Provide a fallback to a simple plain text response in case an error occurs during the
    // rendering of the error page.
    let fallback = |err: &str| {
        HttpResponse::build(res.status())
            .content_type(ContentType::plaintext())
            .body(err.to_string())
    };

    let ctx = minijinja::context! {
        error => error,
        status_code => res.status().as_str(),
    };

    match tmpl_env.render("error.html", ctx) {
        Ok(body) => body
            .customize()
            .with_status(res.status())
            .respond_to(req)
            .map_into_boxed_body(),

        Err(_) => fallback(error),
    }
}

source: https://github.com/actix/examples/blob/master/templating/minijinja/src/main.rs

SailFish

SailFishのコード例(※クリックで展開)
use actix_web::{
    error, get,
    middleware::{Compress, Logger},
    web, App, HttpServer, Responder,
};
use actix_web_lab::respond::Html;
use sailfish::TemplateOnce;

#[derive(TemplateOnce)]
#[template(path = "actix.stpl")]
struct Greet<'a> {
    name: &'a str,
}

#[derive(TemplateOnce)]
#[template(path = "page.stpl")]
struct Page<'a> {
    id: &'a i32,
}

#[get("/{name}")]
async fn greet(params: web::Path<(String,)>) -> actix_web::Result<impl Responder> {
    let body = Greet { name: &params.0 }
        .render_once()
        .map_err(error::ErrorInternalServerError)?;

    Ok(Html(body))
}

#[get("/page-{id:\\d+}")]
async fn page(params: web::Path<(i32,)>) -> actix_web::Result<impl Responder> {
    let body = Page { id: &params.0 }
        .render_once()
        .map_err(error::ErrorInternalServerError)?;

    Ok(Html(body))
}

#[get("/")]
async fn hello() -> impl Responder {
    Html("<p>Hello world!</p>".to_owned())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

    log::info!("starting HTTP server at http://localhost:8080");

    HttpServer::new(|| {
        App::new()
            .service(hello)
            .service(page)
            .service(greet)
            .wrap(Compress::default())
            .wrap(Logger::default())
    })
    .bind(("127.0.0.1", 8080))?
    .workers(1)
    .run()
    .await
}

source: https://github.com/actix/examples/blob/master/templating/sailfish/src/main.rs

Tera

Teraのコード例(※クリックで展開)
use std::collections::HashMap;

use actix_web::{
    body::BoxBody,
    dev::ServiceResponse,
    error,
    http::{header::ContentType, StatusCode},
    middleware::{self, ErrorHandlerResponse, ErrorHandlers},
    web, App, Error, HttpResponse, HttpServer, Responder, Result,
};
use actix_web_lab::respond::Html;
use tera::Tera;

// store tera template in application state
async fn index(
    tmpl: web::Data<tera::Tera>,
    query: web::Query<HashMap<String, String>>,
) -> Result<impl Responder, Error> {
    let s = if let Some(name) = query.get("name") {
        // submitted form
        let mut ctx = tera::Context::new();
        ctx.insert("name", name);
        ctx.insert("text", "Welcome!");
        tmpl.render("user.html", &ctx)
            .map_err(|_| error::ErrorInternalServerError("Template error"))?
    } else {
        tmpl.render("index.html", &tera::Context::new())
            .map_err(|_| error::ErrorInternalServerError("Template error"))?
    };

    Ok(Html(s))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

    log::info!("starting HTTP server at http://localhost:8080");

    HttpServer::new(|| {
        let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();

        App::new()
            .app_data(web::Data::new(tera))
            .wrap(middleware::Logger::default())
            .service(web::resource("/").route(web::get().to(index)))
            .service(web::scope("").wrap(error_handlers()))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

// Custom error handlers, to return HTML responses when an error occurs.
fn error_handlers() -> ErrorHandlers<BoxBody> {
    ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
}

// Error handler for a 404 Page not found error.
fn not_found<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<BoxBody>> {
    let response = get_error_response(&res, "Page not found");
    Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
        res.into_parts().0,
        response.map_into_left_body(),
    )))
}

// Generic error handler.
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse {
    let request = res.request();

    // Provide a fallback to a simple plain text response in case an error occurs during the
    // rendering of the error page.
    let fallback = |err: &str| {
        HttpResponse::build(res.status())
            .content_type(ContentType::plaintext())
            .body(err.to_string())
    };

    let tera = request.app_data::<web::Data<Tera>>().map(|t| t.get_ref());
    match tera {
        Some(tera) => {
            let mut context = tera::Context::new();
            context.insert("error", error);
            context.insert("status_code", res.status().as_str());
            let body = tera.render("error.html", &context);

            match body {
                Ok(body) => HttpResponse::build(res.status())
                    .content_type(ContentType::html())
                    .body(body),
                Err(_) => fallback(error),
            }
        }
        None => fallback(error),
    }
}

source: https://github.com/actix/examples/blob/master/templating/tera/src/main.rs

TinyTemplate

TinyTemplateのコード例(※クリックで展開)
use std::collections::HashMap;

use actix_web::{
    body::BoxBody,
    dev::ServiceResponse,
    error,
    http::{header::ContentType, StatusCode},
    middleware,
    middleware::{ErrorHandlerResponse, ErrorHandlers},
    web, App, Error, HttpResponse, HttpServer, Result,
};
use serde_json::json;
use tinytemplate::TinyTemplate;

// store tiny_template in application state
async fn index(
    tmpl: web::Data<TinyTemplate<'_>>,
    query: web::Query<HashMap<String, String>>,
) -> Result<HttpResponse, Error> {
    let s = if let Some(name) = query.get("name") {
        // submitted form
        let ctx = json!({
          "name" : name.to_owned(),
          "text" : "Welcome!".to_owned()
        });
        tmpl.render("user.html", &ctx)
            .map_err(|_| error::ErrorInternalServerError("Template error"))?
    } else {
        tmpl.render("index.html", &serde_json::Value::Null)
            .map_err(|_| error::ErrorInternalServerError("Template error"))?
    };
    Ok(HttpResponse::Ok().content_type("text/html").body(s))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

    log::info!("starting HTTP server at http://localhost:8080");

    HttpServer::new(|| {
        let mut tt = TinyTemplate::new();
        tt.add_template("index.html", INDEX).unwrap();
        tt.add_template("user.html", USER).unwrap();
        tt.add_template("error.html", ERROR).unwrap();

        App::new()
            .app_data(web::Data::new(tt))
            .wrap(middleware::Logger::default())
            .service(web::resource("/").route(web::get().to(index)))
            .service(web::scope("").wrap(error_handlers()))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

// Custom error handlers, to return HTML responses when an error occurs.
fn error_handlers() -> ErrorHandlers<BoxBody> {
    ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
}

// Error handler for a 404 Page not found error.
fn not_found<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<BoxBody>> {
    let response = get_error_response(&res, "Page not found");
    Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
        res.into_parts().0,
        response.map_into_left_body(),
    )))
}

// Generic error handler.
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse {
    let request = res.request();

    // Provide a fallback to a simple plain text response in case an error occurs during the
    // rendering of the error page.
    let fallback = |err: &str| {
        HttpResponse::build(res.status())
            .content_type(ContentType::plaintext())
            .body(err.to_string())
    };

    let tt = request
        .app_data::<web::Data<TinyTemplate<'_>>>()
        .map(|t| t.get_ref());
    match tt {
        Some(tt) => {
            let mut context = std::collections::HashMap::new();
            context.insert("error", error.to_owned());
            context.insert("status_code", res.status().as_str().to_owned());
            let body = tt.render("error.html", &context);

            match body {
                Ok(body) => HttpResponse::build(res.status())
                    .content_type(ContentType::html())
                    .body(body),
                Err(_) => fallback(error),
            }
        }
        None => fallback(error),
    }
}

static ERROR: &str = include_str!("../templates/error.html");
static INDEX: &str = include_str!("../templates/index.html");
static USER: &str = include_str!("../templates/user.html");

source: https://github.com/actix/examples/blob/master/templating/tinytemplate/src/main.rs

Yarte

Yarteのコード例(※クリックで展開)
use std::collections::HashMap;

use actix_web::{
    get, middleware::Logger, web, App, Error, HttpResponse, HttpServer, ResponseError,
};
use derive_more::Display;
use yarte::{auto, ywrite_min};

#[derive(Debug, Display)]
struct MyErr(pub &'static str);

impl ResponseError for MyErr {}

#[allow(unused_must_use)] // ywrite_min causes warning: unused borrow that must be used
#[get("/")]
async fn index(query: web::Query<HashMap<String, String>>) -> Result<HttpResponse, Error> {
    // `ywrite_min` is work in progress check your templates before put in production
    // or use `ywrite_html`
    Ok(HttpResponse::Ok()
        .content_type("text/html; charset=utf-8")
        .body(auto!(ywrite_min!(String, "{{> index }}"))))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

    log::info!("starting HTTP server at http://localhost:8080");

    HttpServer::new(move || App::new().wrap(Logger::default()).service(index))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

#[cfg(test)]
mod test {
    use actix_web::{http, test as atest, web::Bytes};

    use super::*;

    #[actix_web::test]
    async fn test() {
        let app = atest::init_service(App::new().service(index)).await;

        let req = atest::TestRequest::with_uri("/").to_request();
        let resp = atest::call_service(&app, req).await;

        assert!(resp.status().is_success());

        assert_eq!(
            resp.headers().get(http::header::CONTENT_TYPE).unwrap(),
            "text/html; charset=utf-8"
        );

        let bytes = atest::read_body(resp).await;
        assert_eq!(
            bytes,
            Bytes::from_static(
                "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Actix \
                 Web</title></head><body><h1 id=\"welcome\" \
                 class=\"welcome\">Welcome!</h1><div><h3>What is your name?</h3><form>Name: \
                 <input type=\"text\" name=\"name\"><br>Last name: <input type=\"text\" \
                 name=\"lastname\"><br><p><input type=\"submit\"></p></form></div></body></html>"
                    .as_ref()
            )
        );

        let req = atest::TestRequest::with_uri("/?name=foo&lastname=bar").to_request();
        let resp = atest::call_service(&app, req).await;

        assert!(resp.status().is_success());

        assert_eq!(
            resp.headers().get(http::header::CONTENT_TYPE).unwrap(),
            "text/html; charset=utf-8"
        );

        let bytes = atest::read_body(resp).await;
        assert_eq!(
            bytes,
            Bytes::from_static(
                "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Actix \
                 Web</title></head><body><h1>Hi, foo bar!</h1><p id=\"hi\" \
                 class=\"welcome\">Welcome</p></body></html>"
                    .as_ref()
            )
        );

        let req = atest::TestRequest::with_uri("/?name=foo").to_request();
        let resp = atest::call_service(&app, req).await;

        assert!(resp.status().is_server_error());

        let bytes = atest::read_body(resp).await;

        assert_eq!(bytes, Bytes::from_static("Bad query".as_ref()));

        let req = atest::TestRequest::with_uri("/?lastname=bar").to_request();
        let resp = atest::call_service(&app, req).await;

        assert!(resp.status().is_success());

        assert_eq!(
            resp.headers().get(http::header::CONTENT_TYPE).unwrap(),
            "text/html; charset=utf-8"
        );

        let bytes = atest::read_body(resp).await;
        assert_eq!(
            bytes,
            Bytes::from_static(
                "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Actix \
                 Web</title></head><body><h1 id=\"welcome\" \
                 class=\"welcome\">Welcome!</h1><div><h3>What is your name?</h3><form>Name: \
                 <input type=\"text\" name=\"name\"><br>Last name: <input type=\"text\" \
                 name=\"lastname\"><br><p><input type=\"submit\"></p></form></div></body></html>"
                    .as_ref()
            )
        );
    }
}

source: https://github.com/actix/examples/blob/master/templating/yarte/src/main.rs

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1