5
2

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でreqwestを触ったら微ハマりしたけど、エラーメッセージに惚れた

Posted at

せっかくなので、社内ツールのためにRUST使ってみた

まあ、新しい言語を使う時には定番ですが、社内ツール(自分用)のために使うと「ギリ業務使用経験」+「まあまあ危険も冒さずに済む」ということで、RUSTを社内ツールで使ってみています。まあ、今更エンジニアに戻ろうと思っているわけでもないのですが、エンジニアをmanageすることはあるでしょうし、そんな時、手に技があるかどうかで説得力と判断力が変わります。オードリーたんにはなれないけど、爪のあかくらいは見習いたいよね。

でもってRUSTです。Pythonで書けば一瞬なんだけど、Pythonだと先々Python環境がない人のマシンで動かせないし、pyinstallerは悪い思い出もあるので、別の選択肢を持っておきたいですよね。っていうか、Windowsで実行できるものを作るとき、がっつり度合いを不等号で表すと、私の場合、 bat < PowerShell < VBA < Python with pyinstaller <<< VC++ って感じになるのですが、VC++だと、私、ADHDでミスが多いので、デバッグに無限の時間がかかるわけですよ。そんな時、その「<<<」を埋めつつVC++を代替できるかな、という期待があるわけです。

そんなわけで、とりあえず某ツールのAPIをたたくことにしたのですが、こんなの書きました。

use reqwest::Response;
use reqwest::header;
use serde_json::Value;

const DOMAIN: &str = "https://api.example.com";
const PASSCODE: &str = "foo123bar456baz789";

async fn query() -> Result<(), reqwest::Error> {
    let mut headers = header::HeaderMap::new();
    let mut strpass = String::new();
    strpass.push_str(&"Bearer ");
    strpass.push_str(&PASSCODE);
    headers.insert("AUTHORIZATION", header::HeaderValue::from_str(&strpass).unwrap());

    let mut domain = String::new();
    domain.push_str(&DOMAIN);
    domain.push_str(&"/api/v1/hogehoge?page=1");
    let client = reqwest::Client::builder()
        .default_headers(headers)
        .build()?;

    let res: Response = client
        .get(domain)
        .send().await?;

    dbg!(&res.text().await?); // どんなレスポンスが返ってきたかな?

    Ok(())
}


# [tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    query().await.unwrap_or(());

    Ok(())
}

やったぜ、ぜんぜん非同期な使い方してないけどtokioも使ってみたぜ、よゆー!とかぶっこいていたわけです。Hello Worldの次のステップとしてはまあ上々です。

なお、内容の詳しい説明はここではしません。あとで追記するかもだけど、意味、分かるよね? ヘッダに認証情報を仕込んでAPIたたいているだけです。

でもって、じゃあそれを意気揚々とparseしてみたら…… query()の中身だけ示しますと……

async fn query() -> Result<(), reqwest::Error> {
    let mut headers = header::HeaderMap::new();
    let mut strpass = String::new();
    strpass.push_str(&"Bearer ");
    strpass.push_str(&PASSCODE);
    headers.insert("AUTHORIZATION", header::HeaderValue::from_str(&strpass).unwrap());

    let mut domain = String::new();
    domain.push_str(&DOMAIN);
    domain.push_str(&"/api/v1/hogehoge?page=1");
    let client = reqwest::Client::builder()
        .default_headers(headers)
        .build()?;

    let res: Response = client
        .get(domain)
        .send().await?;

    dbg!(&res.text().await?);

    let resp: Value = res
        .json().await?; // これをjsonとしてparseしてみよう!

    Ok(())
}
// 下記のエラーになります
// error[E0382]: use of moved value: `res`
//    --> src\main.rs:30:23
//     |
// 24  |     let res: Response = client
//     |         --- move occurs because `res` has type `reqwest::Response`, which does not implement the `Copy` trait
// ...
// 28  |     dbg!(&res.text().await?);
//     |               ------ `res` moved due to this method call
// 29  |
// 30  |     let resp: Value = res
//     |                       ^^^ value used here after move
//     |
// note: this function takes ownership of the receiver `self`, which moves `res`
//    --> C:\foo\bar\reqwest-0.11.4\src\async_impl\response.rs:146:23
//     |
// 146 |     pub async fn text(self) -> crate::Result<String> {
//     |                       ^^^^
// 
// error: aborting due to previous error; 1 warning emitted
// 
// For more information about this error, try `rustc --explain E0382`.
// error: could not compile `import_test`

どぼん。コンパイルエラー食らいました。res.text()は、resをもってっちゃうみたいです。よく考えたら、これってストリームなんですよね。text()メソッドは、強制的に最後までストリームを消費させて状態を変えてしまうので、resをもってっちゃうのです。

しかし、RUSTの丁寧なエラーメッセージ、いいですね。初心者にやさしいです。たぶん、初心者じゃなくなったらあんまり見なくなる前提で、初心者向けにしてくれているんじゃない? 知らんけど。

use reqwest::Response;
use reqwest::header;
use serde_json::Value;


const DOMAIN: &str = "https://api.example.com";
const PASSCODE: &str = "foo123bar456baz789";

async fn query() -> Result<(), reqwest::Error> {
    let mut headers = header::HeaderMap::new();
    let mut strpass = String::new();
    strpass.push_str(&"Bearer ");
    strpass.push_str(&PASSCODE);
    headers.insert("AUTHORIZATION", header::HeaderValue::from_str(&strpass).unwrap());

    let mut domain = String::new();
    domain.push_str(&DOMAIN);
    domain.push_str(&"/api/v1/hogehoge?page=1");
    let client = reqwest::Client::builder()
        .default_headers(headers)
        .build()?;

    let resp: Value = client
        .get(domain)
        .send().await?
        .json().await?;

    dbg!(&resp);

    let strparam = resp
        .get(0).unwrap()
        .get("hogehoge").unwrap();

    dbg!(&strparam);

    Ok(())
}


# [tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    query().await.unwrap_or(());

    Ok(())
}

そんなわけで、そのままの解決ではなく、json化したやつをdbg!で出力すればレスポンスの全文が確認できることが分かったので、解決です。

とはいえreqwestは本当に必要になるまで置いておくことにして、当面ureq使います

追記

RUST、いいですね。タイプ量は多いけど、Javaや古いコンパイル言語ほどではないし、所有権の考え方も、早期に勘違いを正してくれるので好きです。こまめに物差しで叩かれながら躾けられている感じです。エラーメッセージの日本語化がなされたら、もっと国内で流行るかもしれませんね。

5
2
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
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?