RustでRocketを使ってAPIを作ってみる

なんとなくの前回の続きで、だいたい自分用メモです。
ohshige.hatenablog.com

RustでAPIを実現するためのフレームワークはいくつかあるようですが、今回はRocketを使います。
基本的にはチュートリアルの通りにやっています。
rocket.rs

やりたいことは前回作ったDBからデータを取得するメソッドをAPI化して、JSONを返却するようにするというものです。

以降は、rustc 1.42.0-nightly で動作確認済みです。

Cargo.toml

[package]
name = "demo"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rusqlite = "*"
rocket = "*"
rocket_contrib = { version = "*", features = ["json"] }
serde = "*"
serde_derive = "*"

main.rs

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] extern crate rocket;
#[macro_use] extern crate serde_derive;

extern crate rusqlite;
extern crate rocket_contrib;

use rusqlite::{Connection, NO_PARAMS};
use rocket_contrib::json::Json;

#[derive(Debug, Serialize)]
struct User {
    user_id: usize,
    name: String,
}

#[get("/users")]
fn get_users() -> Json<Vec<User>> {
    let conn = Connection::open("my.db").unwrap();
    let mut stmt = conn.prepare("
        SELECT
            user_id, name
        FROM
            users
        ORDER BY
            user_id ASC
    ",
    ).unwrap();

    let users = stmt.query_map(NO_PARAMS, |row| {
        Ok(User {
            user_id: row.get::<_, i64>(0).unwrap() as usize,
            name: row.get::<_, String>(1).unwrap(),
        })
    }).unwrap();

    let mut result: Vec<User> = vec![];
    for user in users {
        result.push(user.unwrap());
    }

    Json(result)
}

#[get("/users/<user_id>")]
fn get_user(user_id: usize) -> Option<Json<User>> {
    let conn = Connection::open("my.db").unwrap();
    let mut stmt = conn.prepare("
        SELECT
            user_id, name
        FROM
            users
        WHERE
            user_id = ?
    ",
    ).unwrap();

    let id = user_id as i64;
    let mut users = stmt.query_map(&[&id], |row| {
        Ok(User {
            user_id: row.get::<_, i64>(0).unwrap() as usize,
            name: row.get::<_, String>(1).unwrap(),
        })
    }).unwrap();

    let user = users.next();
    match user {
        Some(user) => Some(Json(user.unwrap())),
        None => None,
    }
}

fn main() {
  rocket::ignite().mount("/", routes![get_users, get_user]).launch();
}

メインの処理はほぼ変わっておらず、User構造体をシリアライズ可能にしたのとレスポンスをJSONにしたくらいです。

上記を実行すると以下のようになります。

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/demo`
🔧 Configured for development.
    => address: localhost
    => port: 8000
    => log: normal
    => workers: 32
    => secret key: generated
    => limits: forms = 32KiB
    => keep-alive: 5s
    => tls: disabled
🛰  Mounting /:
    => GET /users (get_users)
    => GET /users/<user_id> (get_user)
🚀 Rocket has launched from http://localhost:8000

curlで確認してみると、問題なく動いています。

$ curl http://localhost:8000/users
[{"user_id":1,"name":"田中太郎"},{"user_id":2,"name":"鈴木次郎"}]
$ curl http://localhost:8000/users/1
{"user_id":1,"name":"田中太郎"}
$ curl http://localhost:8000/users/999
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>404 Not Found</title>
</head>
<body align="center">
    <div align="center">
        <h1>404: Not Found</h1>
        <p>The requested resource could not be found.</p>
        <hr />
        <small>Rocket</small>
    </div>
</body>
</html>

現状だと、存在しないユーザIDを指定した場合でも404は返りますが、デフォルトのままHTMLになってしまっているので、しっかりやるならエラーハンドリングが必要です。