Rust and Cloudflare Workers

Rust で REST API を実装する方法を調べる。

業務での実践に近い形にするため、Stateful に実装する。

データの保存は、無課金でやりたいので、D1 を使う。Rust + Workers + D1。

Rust の syntax に慣れていないため、Application での利用パターンを繰り返し実装して慣れる。

今回のコードはこちら。

https://github.com/danny-yamamoto/rust-worker

## environment
Cloudflare の template を使用する。

https://developers.cloudflare.com/workers/runtime-apis/webassembly/rust/

## Stateful
D1 を準備する。
wrangler で local に D1 のデータを準備する。

```bash
wrangler d1 execute test-db \
  --local --command "CREATE TABLE IF NOT EXISTS users ( user_id INTEGER PRIMARY KEY, email_address TEXT, created_at INTEGER, deleted INTEGER, settings TEXT);"

npx wrangler d1 execute test-db \
  --local --command "INSERT INTO users (user_id, email_address, created_at, deleted, settings) VALUES (1, 'maria@example.com', 0, 0, '');"
npx wrangler d1 execute test-db \
  --local --command "INSERT INTO users (user_id, email_address, created_at, deleted, settings) VALUES (999, 'admin@example.com', 0, 0, '');"

npx wrangler d1 execute test-db \
  --local --command "select * from users;"
```

## REST API
* 実装仕様
`/`: 全件取得
`/n`: `user_id` を指定する。

```rust
    Router::new()
    .get_async("/:id", |_, ctx| async move {
        let id = match ctx.param("id") {
            Some(value) => value,
            None => {
                eprintln!("Error: id parameter not found");
                "default_id"
            }
        };
        let d1 = ctx.env.d1("DB")?;
        let statement = d1.prepare("select * from users where user_id = ?1");
        let query = statement.bind(&[id.into()])?;
        let res = query.first::<Users>(None).await;
        match res {
            Ok(Some(user)) => {
                let json = serde_json::to_string(&user)
                    .map_err(|e| Error::from(e.to_string()))?;
                Response::ok(json)
            },
            Ok(None) => Response::error("Not Found", 404),
            Err(e) => {
                eprintln!("Database error: {:?}", e);
                Response::error("Internal Server Error", 500)
            },
        }
    })
    .get_async("/", |_, ctx| async move {
        let d1 = ctx.env.d1("DB")?;
        let statement = d1.prepare("select * from users");
        let res = statement.all().await?;
        Response::from_json(&res.results::<Users>().unwrap())
    }).run(request, env).await
```

```bash
vscode ➜ /workspaces/rust-worker (main) $ curl localhost:8787
[{"user_id":1,"email_address":"maria@example.com","created_at":0,"deleted":0,"settings":""},{"user_id":999,"email_address":"admin@example.com","created_at":0,"deleted":0,"settings":"
vscode ➜ /workspaces/rust-worker (main) $ curl localhost:8787/1
{"user_id":1,"email_address":"maria@example.com","created_at":0,"deleted":0,"settings":""}
vscode ➜ /workspaces/rust-worker (main) $ 
```

## Impression
- Rust の書き方で好感を覚えるのは、`match`。
`if` で分岐させる必要はないし、簡潔に書くことが可能。
- まだ、直感的に書けるところまで来ていない。例えば、namespace の解決や関連関数へのアクセスに用いる `::` について、迷う。

## GPT-4 コード解説
> このRustのコードは、WebAssemblyのために書かれたHTTPリクエストに対応するサーバレス関数を実装しています。クラウドフレアのWorkers（あるいは同様のサーバレスプラットフォーム）で動作することを意図していると思われます。このコードは、特定のエンドポイントへのHTTPリクエストを処理し、データベース（おそらくCloudflare WorkersのD1）からユーザー情報を取得してレスポンスを返します。具体的な部分を説明します。
> 1. **モジュールとクレートのインポート**:
    `worker`クレートと`serde`クレートをインポートしています。`worker`はWebAssemblyベースのサーバレス環境用のAPIを提供し、`serde`はRustでのシリアライズ（データ構造をJSONなどの形式に変換）およびデシリアライズ（その逆の操作）に使用されます。
> 1. **`Users`構造体の定義**:
    `Users`構造体は、ユーザー情報を表すためのデータ構造です。`Deserialize`と`Serialize`トレイトが導出されており、これによりJSON形式との変換が可能になっています。`serde(rename = "name")`アトリビュートは、JSONキー名とRustフィールド名を対応させています。
> 1. **`main`関数（非同期）**:
    `main`関数は非同期であり、HTTPリクエストを受け取って処理します。この関数は`Result<Response>`を返します。
> 1. **ルーティング**:
    HTTPエンドポイントごとに異なるハンドラ関数を定義しています。
    - `GET /:id`: 指定されたIDに対応するユーザーをデータベースから検索して返します。
    - `GET /`: すべてのユーザーをデータベースから取得して返します。
> 1. **データベース処理**:
    SQLクエリを使って、D1データベース（Cloudflareのデータベースソリューション）からユーザーデータを取得しています。
> 1. **エラーハンドリング**:
    データベースクエリやデータ取得の際にエラーが発生した場合、適切なHTTPステータスコード（404や500など）とエラーメッセージをレスポンスとして返します。
>
> このコードは、RustでクラウドネイティブのWebアプリケーションを開発する際の一例を示しており、WebAssemblyとRustの組み合わせによる高性能かつセキュアなサーバレスアプリケーション開発を実現しています。