Rust で actix-web を使って REST API を実装していく

この記事は [**セゾン情報システムズ Advent Calendar 2020**](https://qiita.com/advent-calendar/2020/sisco) 8日目の記事です。

# あらすじ
- https://qiita.com/Yoshihiro-Hirose/items/982429f74b2900963533 で Rust を始めてみた
- 今回は REST API サーバを作っていきたい(途中まで)

# actix-web
- actix-web を使って実装していきます
- https://actix.rs/
- https://crates.io/crates/actix-web

# Web サーバの hello, world
1. 適当なディレクトリでコマンドプロンプト起動
2. `cargo new web_hello` を実行
3. web_hello をディレクトリを vscode で開く
4. Cargo.toml ファイルを開き、dependencies に `actix-web = "3.3.2"` を追加
5. 以下コードを main.rs に貼り付け

	```rust
	use actix_web::{get, web, App, HttpServer, Responder};

	#[get("/{id}/{name}/index.html")]
	async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
	    format!("Hello {}! id:{}", name, id)
	}

	#[actix_web::main]
	async fn main() -> std::io::Result<()> {
	    HttpServer::new(|| App::new().service(index))
	        .bind("127.0.0.1:8080")?
	        .run()
	        .await
	}
	```
6. `cargo run` を実行
7. ブラウザを開き `http://127.0.0.1:8080/123/namae/index.html` にアクセスする
8. id と name が返される

※ URL のパスを動的に取得できることが確認できます
※ 関数に `#[get` がついていますが GET であることを表しています

# TODO 管理
TODO を取得、登録する API の雛形を作っていきます
全体コードは最後に載せます

## crate の追加
json を扱うので関連する crate を追加します

```toml:Cargo.toml
[dependencies]
actix-web = "3.3.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
```

## 構造体の定義
TODO の構造体を定義し、GET と POST で利用します。
新規登録時は id を自動生成したいので未指定を許容したく Option としています。

```rust
#[derive(Debug, Serialize, Deserialize)]
struct Todo {
    id: Option<u32>,
    content: String,
    done: bool,
}
```

## TODO の取得(GET)
構造体を json として返します。
ここで取得した ID を使ってデータベースなどにアクセスする予定です。

```rust
#[get("/todos/{id}")]
async fn get_todo(web::Path(id): web::Path<u32>) -> impl Responder {
    println!("get_todo");
    let id_option: Option<u32> = Some(id);
    HttpResponse::Ok().json(Todo {
        id: id_option,
        content: String::from("やること"),
        done: false,
    })
}
```

## TODO の登録(POST)
JSON をリクエストで受け取り、構造体に変換して扱います。

```rust
#[post("/todos")]
async fn post_todo(todo: web::Json<Todo>) -> impl Responder {
    println!("post_todo");
    println!("{:?}", todo);
    HttpResponse::Ok().body("ok")
}
```

## コード全体

```rust:main.rs
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Todo {
    id: Option<u32>,
    content: String,
    done: bool,
}

#[get("/todos/{id}")]
async fn get_todo(web::Path(id): web::Path<u32>) -> impl Responder {
    println!("get_todo");
    let id_option: Option<u32> = Some(id);
    HttpResponse::Ok().json(Todo {
        id: id_option,
        content: String::from("やること"),
        done: false,
    })
}

#[post("/todos")]
async fn post_todo(todo: web::Json<Todo>) -> impl Responder {
    println!("post_todo");
    println!("{:?}", todo);
    HttpResponse::Ok().body("ok")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(get_todo).service(post_todo))
        .bind("127.0.0.1:8080")?
        .run()
        .await
}
```

## サーバ起動
`cargo run` を実行
ブラウザで `http://localhost:8080/todos/123` にアクセスする → json が返される
HTTPクライアントなどで `http://localhost:8080/todos` に以下を POST する → サーバのコンソールに表示される

```json
{
	"content": "なにかやる",
	"done": false
}
```

# まとめ
思ったよりも少ないコードでサーバが書けそうでした
今後は Web アプリケーションや WebAssembly なども触っていきたいと思います。


明日の [**セゾン情報システムズ Advent Calendar 2020**](https://qiita.com/advent-calendar/2020/sisco) もお楽しみに!