最近、個人的な興味で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に必要なライブラリの情報を記載します。
[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にします。
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
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(())
}
- reqwestのClientのインスタンスを生成
-
url
にgetリクエストを送りレスポンスを取得 -
response
からレスポンスボディをString
で取得する
{
"message": null,
"results": [
{
"address1": "東京都",
"address2": "千代田区",
"address3": "皇居外苑",
"kana1": "トウキョウト",
"kana2": "チヨダク",
"kana3": "コウキョガイエン",
"prefcode": "13",
"zipcode": "1000002"
}
],
"status": 200
}
レスポンスボディをパースする
次はレスポンスボディをparseしてStringではなく、自作のstructの型で受け取れるようにします。
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ツールとか作っても面白そうだなと思いました。
今回は以上です。何か指摘などあればよろしくお願いします!