Rust axumテンプレートエンジンTera

Rustのaxumで利用できるテンプレートエンジンは幾つかある

handlerbars: JavaScriptのHandler.jsにインスパイアされて開発されたRust用のテンプレートエンジン
minijinja: PythonのJinja2の文法を採用して作られている。
Tera: Djangoのテンプレートエンジンなどの影響を強く受けたもので、フィルターやマクロなど非常に豊富な機能を持っている

### Teraの準備
Cargo.tomlのdependenciesに以下を追加

axum-template="0.14.0"
tera="1.17.1"

### テンプレート作成
template/index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>{{title}}</title>
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body class="container">
	<h1 class="display-6 my-2">{{title}}</h1>
	<p class="my-2">{{message}}</p>
</body>
</html>

### axumからTeraを利用
Teraのテンプレートパスを指定してTeraのインスタンス作成
テンプレートエンジンとの間でやりとりするデータの管理はContextを使用する
レンダリングの実行はtera.render, htmlインスタンスの返却はaxum::response::Html

#[tokio::main]
async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(handle_index));

    axum::Server::bind(&"192.168.56.10:8000".parse().unwrap())
    	.serve(app.into_make_service())
    	.await
    	.unwrap();
}

async fn handle_index()-> axum::response::Html<String> {
	let tera = tera::Tera::new("template/*").unwrap();

	let mut context = tera::Context::new();
	context.insert("title", "Index page");
	context.insert("message", "これはサンプルです。");

	let output = tera.render("index.html", &context);
	axum::response::Html(output.unwrap())
}

### フォームの送信

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>{{title}}</title>
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body class="container">
	<h1 class="display-6 my-2">{{title}}</h1>
	<div class="alert alert-primary">
		<p class="my-2">{{message}}</p>
	</div>
	<form method="post" action="/post">
		<div class="mb-3">
			<label for="name" class="form-label">
			Your name:</label>
			<input type="text" class="form-control" name="name" id="name">
		</div>
		<div class="mb-3">
			<label for="mail" class="form-label">
			Email address:</label>
			<input type="text" class="form-control" name="mail" id="mail">
		</div>
		<input type="submit" class="btn btn-primary" value="Submit">
	</form>
</body>
</html>

/postのルーティングを用意する

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Myform {
	name: String,
	mail: String,
}

#[tokio::main]
async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(handle_index))
		.route("/post", axum::routing::post(handle_post));

    axum::Server::bind(&"192.168.56.10:8000".parse().unwrap())
    	.serve(app.into_make_service())
    	.await
    	.unwrap();
}

// 省略

async fn handle_post(axum::Form(myform): axum::Form<Myform>)
	-> axum::response::Html<String> {
		let msg = format!("I am {}<{}>.", myform.name, myform.mail);
		let tera = tera::Tera::new("template/*").unwrap();

		let mut context = tera::Context::new();
		context.insert("title", "Index page");
		context.insert("message", &msg);

		let output = tera.render("index.html", &context);
		axum::response::Html(output.unwrap())
}