12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RustでWeb APIを叩く

Last updated at Posted at 2021-09-18

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

プロジェクトの作成

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

$ cargo new api-sample

とりあえずrun。

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

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

依存関係を追記

cargo.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にします。

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

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のインスタンスを生成
  2. urlにgetリクエストを送りレスポンスを取得
  3. responseからレスポンスボディをStringで取得する
実行結果
{
  "message": null,
  "results": [
    {
      "address1": "東京都",
      "address2": "千代田区",
      "address3": "皇居外苑",
      "kana1": "トウキョウト",
      "kana2": "チヨダク",
      "kana3": "コウキョガイエン",
      "prefcode": "13",
      "zipcode": "1000002"
    }
  ],
  "status": 200
}

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

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

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!する時に必要なので付けています。

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ツールとか作っても面白そうだなと思いました。

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

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?