RustでHTMLテンプレートを使ったWebアプリを作ろう

2021-12-05

はじめに

ペルソナ5を全部視聴おわりました。 あのアニメは構図とかセリフがなんというかゲームでしたね。ラスボス戦の感じとか対策RPG感あってとても良かったです。

そして、コードギアスAmazon Primeに入っていたので、見始めました。面白いですね。

テンプレートエンジン

Rustで自前で用意したHTMLを使ったアプリを作っていきましょう。 今回使用するのはこのaskama

クレートの追加

Rustでは、ライブラリとかパッケージのことをクレート(crate)って言うんですね。 厳密にはパッケージの集合がクレートっていう感じみたいですが、そういうようです。

cargo add thiserror
cargo add actix_web
cargo add askama

index.html

Rustからindex.htmlを読み出す

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>App</title>
</head>
<body>
    <div>
        {% for entry in entries %}
        <div>
            <div>id: {{ entry.id }}, text: {{ entry.text }}</div>
        </div>
        {% endfor %}
    </div>
</body>
</html>

entriesという構造体の中身を一つずつ取り出すためのfor文はこのように書きます。

{% for entry in entries %}

{% endfor %}

そこの間に、構造体の引数を指定して表示します。

<div>
    {% for entry in entries %}
    <div>
        <div>id: {{ entry.id }}, text: {{ entry.text }}</div>
    </div>
    {% endfor %}
</div>

Rustコード

HTMLで指定した部分にデータを詰め込みWebAppとして公開するコードを見せる。

コード全文

use actix_web::{get, App, HttpResponse, HttpServer, ResponseError};
use askama::Template;
use thiserror::Error;

struct Contents {
    id: u8,
    text: String,
}

#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
    entries: Vec<Contents>,
}
#[derive(Error, Debug)]
enum MyError {
    #[error("Failed to render HTML")]
    AskamaError(#[from] askama::Error),
}

impl ResponseError for MyError {}

#[get("/")]
async fn index() -> Result<HttpResponse, MyError> {
    let mut entries = Vec::new();
    entries.push(Contents {
        id: 1,
        text: "First entry".to_string(),
    });
    entries.push(Contents {
        id: 2,
        text: "Second entry".to_string(),
    });
    let html = IndexTemplate { entries };
    let response_body = html.render()?;
    Ok(HttpResponse::Ok()
        .content_type("text/html")
        .body(response_body))
}

#[actix_web::main]
async fn main() -> Result<(), actix_web::Error> {
    HttpServer::new(move || App::new().service(index))
        .bind("127.0.0.1:1002")?
        .run()
        .await?;
    Ok(())
}

詰め込む構造体はこう書きました。 Vector型に詰め込む準備をします。

struct Contens {
    id: u8,
    text: String,
}

#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
    entries: Vec<Contens>,
}

構造体にデータを詰め込みところを書きます。 Vector型に、データをプッシュしていく。

#[get("/")]
async fn index() -> Result<HttpResponse, MyError> {
    let mut entries = Vec::new();
    entries.push(Contents {
        id: 1,
        text: "First entry".to_string(),
    });
    entries.push(Contents {
        id: 2,
        text: "Second entry".to_string(),
    });
    let html = IndexTemplate { entries };
    let response_body = html.render()?;
    Ok(HttpResponse::Ok()
        .content_type("text/html")
        .body(response_body))
}

実行結果

HTMLに指定した値が出力されましたね。 まずはここまで。

error: template “index.html” not found in directories

道中出くわすであろうエラーについて記載していきます。

--> src\main.rs:10:10
   |
10 | #[derive(Template)]
   |          ^^^^^^^^
   |
   = note: this error originates in the derive macro `Template` (in Nightly builds, run with
-Z macro-backtrace for more info)

index.htmlを置いたフォルダ名が間違っていたことが原因です。 templatesという名前のフォルダ名で作ってあげると解決します。

まとめ

HTMLで動かすやり方が分かりましたね。 Vue+Rustを動かすようにしたいですね。 ここまで書くとあんまりやってる人が出てこないですからね。