Rust Rocket v0.4.0 の入門

この記事は[Rustその2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/rust2)の23日目の記事です。
また、こちらは[自身のはてなブログ](http://vraisamis.hatenadiary.jp/entry/advent-2018-rust-2-day23)との並列投稿となります。

Rust の Web フレームワークである [Rocket](https://rocket.rs/) に入門していきます。

この記事では、公式のサンプルを参照しながら、それを解説していきます。

# 執筆環境

```bash
$ rustc --version
rustc 1.33.0-nightly (2d3e909e4 2018-12-22)

```

# インストール
## Rust のインストール
[公式のインストール手順](https://www.rust-lang.org/tools/install)に従って下さい。


執筆環境と同等の環境を用意するには、以下のようにします。

```bash
$ rustup toolchain install nightly-2018-12-23
$ rustup default nightly-2018-12-23
```

# リポジトリのダウンロード
公式のサンプルは、リポジトリに含まれています。 clone してきましょう。

```bash
$ git clone -b v0.4.0 https://github.com/SergioBenitez/Rocket.git
$ cd Rocket/examples
```

準備ができました。以下でサンプルを見ていきます。
以下では、各章開始時点では `examples` ディレクトリ直下に居る前提で話を進めます。

# hello_world

## 概要
何はともあれhello worldからやっていきましょう。

```bash
$ cd hello_world
$ cargo run
```

難なく起動してくれるはずです。
この状態で、 localhost:8000 にアクセスすれば、 `Hello, world!` とだけ表示されたページが出てきます。


## ソース
`src/main.rs` を見て下さい。
書いてある重要な内容は、大きく2つです。

### ハンドラ
1つは、ハンドラの定義です。

hello_world では、ルートパスにアクセスした際に、 `"Hello, world"` が返されました。
この処理を定義する記述が以下になります。

```rust
#[get("/")]
fn hello() -> &'static str {
    "Hello, world!"
}
```

Rocketでは、カスタム属性(Custom Attribute) を使います。 `#[get("/")]` で、この関数が パス `/` へのGETリクエストに対するハンドラであると示しています。
`get` 以外にも、 `put, post, delete, head, patch, options` が利用でき、それぞれ HTTP のリクエストメソッドと対応しています。

ハンドラ自体は、引数なし、文字列を返すだけの関数となっています。
この戻り値がそのままレンダリングされていたわけです。
ハンドラの引数が何の役目を果たすのか、戻り値の型は単純な文字列以外には何が指定できるのか、などは、別のサンプルで解説されています。


### マウント、起動
さて、やっていることのもうひとつは、マウントとアプリケーションの起動です。

```rust
fn main() {
    rocket::ignite().mount("/", routes![hello]).launch();
}
```

`rocket::ignite()` で、アプリケーションを作成します。
ここに、なんやかんや設定をして、最後に `launch()` すると、アプリケーションが開始され、アクセスできるようになります。

hello_world で設定しているのは、ルーティングがひとつだけです。
`mount("/", routes![hello])` が、hello関数をルート直下にマウントしています。


# hello_person

## 概要
この例では、パスから動的にパラメータを取得します。

```bash
$ cd hello_person
$ cargo run
```

以下のページにアクセスできます。

- localhost:8000/hello/foo
    - `foo` とだけ描画されます
    - `foo` を別の文字に変えると、描画される内容も変わります
    - `foo` に何も入れないと Not Found になります
- localhost:8000/hello/foo/100
    - `Hello, 100 year old named foo!` と描画されます
    - `100` を別の数字に変えると、描画される内容の `100` の部分が変化します
    - `100` に数字以外（`abc`, `1+1` など）を入れると Not Found になります
- それ以外は Not Found です

## ソース

`src/main.rs` に全て書かれています。

hello_world からは、以下の点が変更されています。

- ハンドラが2つになり、それぞれの内容が変更されています
- ルーティングにハンドラが2つ指定されています

### ルーティング
まずは、ルーティングに関して確認していきます。

```rust
    rocket::ignite().mount("/", routes![hello, hi]).launch();
```

`routes!` マクロの中にハンドラを与えていますが、これは複数並べることができます。

また、 `"/"` を　`"/yo"`に変更し、起動してみましょう。

localhost:8000/hello/foo/100 へはアクセスできなくなり、 localhost:8000/yo/hello/foo/100 へアクセスできるようになりました。
同時に、 localhost:8000/hello/foo へはアクセスできなくなり、 localhost:8000/yo/hello/foo へアクセスできるようになっています。

このように、 `mount()` の第一引数でルーティングをディレクトリ的単位でまとめて制御することができます。

### ハンドラの引数とパラメータ
ハンドラが2つありますが、 `hello()` だけ確認します。

```rust
#[get("/hello/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}
```

カスタム属性のパスに、 `<name>` 、 `<age>` が現れました。
対応するように、 `hello()` の引数に、 `name` 、 `age` が現れました。
これらが、パスからパラメータを受け取るための記述になります。

`<name>` の位置に対応したものが、引数 `name` に与えられ、
`<age>` の位置に対応したものが、引数 `age` に与えられ、関数が呼ばれます。

特に、 `age` は自動的に数値型にパースされます。
では、数値として読み込めなかった時はどうなるかというと、概要で示したように Not Found になります。

パースのできる/できないはどのようにして決定するのでしょうか？そもそも、パースできる型はどのように決まっているのでしょうか？
鍵は、 [FromParam](https://api.rocket.rs/v0.4/rocket/request/trait.FromParam.html) トレイトにあります。

FromParamトレイトは、プリミティブの数値型、 `String` 、 `&RawStr` 、 `Option<T>` 、 `Result<T, T::Error>` などに実装されています。
パス文字列から各型への変換は、 `from_param()` によって行われます。

この変換が成功した場合、ハンドラが実行されます。

補足としては、以下のとおりです。

- `&RawStr` : 生のパス文字列そのものを表す型で、変換等しないのでマッチは必ず成功します
- `String` : 生のパス文字列から文字列に変換します。失敗する場合があります
- `Option<T>` : 変換が成功した時は `Some(T)` 、失敗したときは `None` を返します。マッチは必ず成功します
- `Result<T, T::Error>` : 変換が成功した時は `Ok(T)` 、失敗した時は `Err(T::Error)` が返ります。マッチは必ず成功します

# tera_templates

## 概要

hello_world、hello_personでは、レスポンスは全て単純な文字列で返してきましたが、
Rocket ではテンプレートエンジンを利用してレスポンスを作成できます。

Rocketで利用できるのは、 [Tera](https://tera.netlify.com/) と [Handlebars](https://handlebarsjs.com/) です。ここでは Tera を紹介します。

```bash
$ cd tera_templates
$ cargo run
```

起動して、 localhost:8000/hello/foo にアクセスしてみましょう。HTML文章が表示されると思います。

## ソース

`src/main.rs` からみていきます。

```rust
        .attach(Template::fairing())
```

テンプレートを利用するには、 `Template::fairing()` をアタッチしておく必要があります。



```rust
#[get("/hello/<name>")]
fn get(name: String) -> Template {
    let context = TemplateContext { name, items: vec!["One", "Two", "Three"] };
    Template::render("index", &context)
}
```

ハンドラの戻り値が `Template` となっています。
`Template::render()` を返すと、テンプレートの内容をレンダリングしてくれます。

`render()` の引数には、テンプレートの名前と、テンプレートに渡すコンテキストを指定します。
テンプレートの名前は、 `"index"` を指定しているので、 `templates/index.tera` が解釈されます。

コンテキストには、Serde でシリアライズ可能なものを指定できます。
この例では、自前で以下のように定義しています。

```rust
#[derive(Serialize)]
struct TemplateContext {
    name: String,
    items: Vec<&'static str>
}
```


さて、 `templates/index.tera` の内容を確認していきます。

```html
    <h1>Hi {{name}}</h1>
```

コンテキストとして渡した、 `name` が使用されています。

```html
        {% for s in items %}
            <li>{{ s }}</li>
        {% endfor %}
```

ここでは items の内容をループで表示しています。


----

# さいごに
今回は、公式のexamplesの中から基礎の基礎である hello_world 、
動的パスを扱った hello_person 、
レスポンスをリッチにする tera_template 
の3つを紹介しました。

計画的に執筆していなかったため、あまり紹介できませんでした。
examples は30個位上あるので、余裕のある際に追加していければと思います。