生産性のない時間 is プライスレス

axumのルーティングで複数の型のレスポンスを返す

公開日時:

というわけで、ゲームじゃないほうの Rust に若干興味が惹かれている錦です。

色々あって Rust の Web フレームワークを使ってみたくなり、axumというクレートで遊んでいました。

Wtb Nishiki / Soboku CS · GitLab

今回は、その際に割と本気でどうすればいいか悩んだ部分を解決したので、今後のために残しておこうと思います。

いや、公式ドキュメントに全て記載していたんですけどね!(英語力が貧弱)

何がしたかったか

アプリケーションが起動したディレクトリを基底ディレクトリとして、ファイルを配信するアプリケーションを作っていました。

具体的には以下のようなものです。清書をしてないのでめちゃくちゃなのは許してください。 今回、この内容自体は重要じゃないので雰囲気だけつかんでもらえれば。

#[tokio::main]
async fn main() {
    let app = Router::new().nest("/", get(get_file_or_filelist));
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn get_file_or_filelist(uri: Uri) -> Result<impl IntoResponse, StatusCode> {
    let repo = TextFileRepositoryImpl::new();
    match repo.get(uri.path().to_owned()[1..].to_string()) {
        Ok(o) => match o {
            Some(tf) => Ok(tf.text),
            None => match repo.get_paths(&uri.path()[1..]) {
                Ok(ps) => Ok(Json(ps)),
                Err(_) => Err(StatusCode::NOT_FOUND),
            },
        },
        Err(_) => match repo.get_paths(&uri.path()[1..]) {
            Ok(ps) => Ok(Json(ps)),
            Err(_) => Err(StatusCode::NOT_FOUND),
        },
    }
}

さて、このコードをエディタで分析にかければ分かるのですが、コンパイルできません。

具体的に言うと、String 型(テキスト)を返す時と、Json 型を返す時が存在しています。 当然ですが Rust は静的型付き言語ですので、こういうコードはコンパイルを通すことができません。 (Java とかなら継承云々でワンちゃん返すことが出来るかも知れませんがそれはいったん忘れて…。)

こういうレスポンスを返すメソッドをコンパイル通したいよね、というのが本題です。

結論

結論ですが、以下に全てが書いてあります。

Returning different response types < axum::response - Rust

まとめると、Response型を返り値にして、返り値にする変数は.into_response()を使用してResponse型に変換してあげます。

async fn get_file_or_filelist(uri: Uri) -> Response {
    let repo = TextFileRepositoryImpl::new();
    match repo.get(uri.path().to_owned()[1..].to_string()) {
        Ok(o) => match o {
            Some(tf) => tf.text.into_response(),
            None => match repo.get_paths(&uri.path()[1..]) {
                Ok(ps) => Json(ps).into_response(),
                Err(_) => StatusCode::NOT_FOUND.into_response(),
            },
        },
        Err(_) => match repo.get_paths(&uri.path()[1..]) {
            Ok(ps) => Json(ps).into_response(),
            Err(_) => StatusCode::NOT_FOUND.into_response(),
        },
    }
}

これで無事コンパイルが通るようになります!

あとがたり

Rust、複雑なプログラミングをするときにしっかり悪いコード(設計?)を怒ってくれるのがとてもいいですね。逆に言えばコンパイラに怒られるのが辛い人にはきつそうです。

Rust を Web システムのバックエンド開発に使っている日本語記事も見かけますし、クレート周りが充実してくると、メモリ消費量なども相まってより選択肢にあがりやすくなるんじゃないかなぁと、素人ながらそんな願望混じりなことを想ったりしました。