RustでWeb APIを叩く

最近、個人的な興味でRustに入門したので、reqwestを使ってWebAPIを叩いてみます。
rustのインストールは済んでる前提で進めます。

## プロジェクトの作成
まずはプロジェクトを作成します。

```shell
$ cargo new api-sample
```

とりあえずrun。

```shell
$ cd api-sample
$ cargo run
Hello, world!
```

cargo newするだけでHello, Worldまで作ってくれるのですごいですね。
これでプロジェクト作成はできたので、`src/main.rs`を修正していきます。

## 依存関係を追記
cargo.tomlに必要なライブラリの情報を記載します。

```toml:cargo.toml
[dependencies]
reqwest = { version = "0.11.4", features = ["json"] }
tokio = { version = "1.11.0", features = ["full"] }
serde = { version = "^1.0.130", features = ["derive"] }
serde_json = "^1.0.67"
```

httpクライアントにはreqwestを使います。非同期処理を扱うので、tokioも追加してます。
serdeとserde_jsonはRustのstructとjsonの変換のために入れてます。

## mainをasync fn化する
APIを叩く処理は非同期のため、tokioを使ってmain関数をasync fnにします。

```rust:main.rs
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[tokio::main]
async fn main() -> Result<()> {
    println!("Hello, world!");
    Ok(())
}
```
これで、mainの中でawaitが使えるようになりました。
この後`?`演算子を使う予定なのでmainの返り値の型を`Result<()>`として最後に`Ok(())`を返しています。

## とりあえずAPIを叩く

main関数をasync fnにできたので、ひとまずAPIを投げてみます。
お試しには郵便番号検索APIを使います。 -> http://zipcloud.ibsnet.co.jp/doc/api

```rust:main.rs
use reqwest::Client;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(); // 1
    let url = "https://zipcloud.ibsnet.co.jp/api/search";
    let response = client
        .get(url)
        .query(&[("zipcode", "1000002")])
        .send()
        .await?; // 2
    let body = response.text().await?; // 3
    println!("{}", body);
    Ok(())
}
```

1. reqwestのClientのインスタンスを生成
1. `url`にgetリクエストを送りレスポンスを取得
1. `response`からレスポンスボディを`String`で取得する

```json:実行結果
{
  "message": null,
  "results": [
    {
      "address1": "東京都",
      "address2": "千代田区",
      "address3": "皇居外苑",
      "kana1": "ﾄｳｷｮｳﾄ",
      "kana2": "ﾁﾖﾀﾞｸ",
      "kana3": "ｺｳｷｮｶﾞｲｴﾝ",
      "prefcode": "13",
      "zipcode": "1000002"
    }
  ],
  "status": 200
}
```

## レスポンスボディをパースする

次はレスポンスボディをparseしてStringではなく、自作のstructの型で受け取れるようにします。

```rust:main.rs
use reqwest::Client;
use serde::Deserialize;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

#[derive(Debug, Deserialize)]
struct Address {
    address1: String,
    address2: String,
    address3: String,
    prefcode: String,
    zipcode: String,
}

#[derive(Debug, Deserialize)]
struct ZipCloudResponse {
    status: u32,
    results: Vec<Address>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new();
    let url = "https://zipcloud.ibsnet.co.jp/api/search";
    let response = client
        .get(url)
        .query(&[("zipcode", "1000002")])
        .send()
        .await?;
    let body = response.json::<ZipCloudResponse>().await?;
    println!("{:?}", body);
    Ok(())
}

```

`ZipCloudResponse`がAPIからのレスポンスボディを表す構造体です。`message`はnullが返ってくるので今回は除きました。
`results`は住所を表すデータのリストになっているので、`Address`を定義して、resultsの型を`Vec<Address>`としています。
jsonから変換したいstructは定義の際に、`#[derive(Deserialize)]`を付ける必要があります。
`Debug`はprintln!する時に必要なので付けています。

```rust
let body = response.json::<ZipCloudResponse>().await?;
```

の行の`json::<ZipCloudResponse>()`でレスポンスボディを`ZipCloudResponse`型にパースしています。

```:実行結果
ZipCloudResponse { status: 200, results: [Address { address1: "東京都", address2: "千代田区", address3: "皇居外苑", prefcode: "13", zipcode: "1000002" }] }
```

レスポンスボディをZipCloudResponse型に変換してprintすることができました！

とても簡単な例ですが、RustでWebAPIを叩いてみました。思いの外少ないコード量で書けて、型もしっかり自作のものにパースできるのでいい感じですね。
これで何かのAPIと組み合わせてCLIツールとか作っても面白そうだなと思いました。

今回は以上です。何か指摘などあればよろしくお願いします！