はじめに
前回はD1データベースを作成するところまでやりました。
今回は、作成したデータベースに対してWorkersからCRUD操作をするところまでやりたいと思います。
おや?様子がおかしいぞ?
まず、今回はsea-ormを使ってみたくて、ひとまず導入してDBConnectionを取得するところまでを試し書きしてみたのですが、コンパイルエラーが......
sea-ormが相性悪かったのかと思い、sqlxに切り替えてみたのですが、これも同じエラーが出ました。
error: the wasm*-unknown-unknown targets are not supported by default, you may need to enable the "js" feature. For more information see: https://docs.rs/getrandom/#webassembly-support
--> C:\Users\[秘密]\.cargo\registry\src\index.crates.io-6f17d22bba15001f\getrandom-0.2.15\src/lib.rs:342:9
|
342 | / compile_error!("the wasm*-unknown-unknown targets are not supported by \
343 | | default, you may need to enable the \"js\" feature. \
344 | | For more information see: \
345 | | https://docs.rs/getrandom/#webassembly-support");
| |________________________________________________________________________^
error[E0433]: failed to resolve: use of undeclared crate or module `imp`
--> C:\Users\[秘密]\.cargo\registry\src\index.crates.io-6f17d22bba15001f\getrandom-0.2.15\src/lib.rs:398:9
|
398 | imp::getrandom_inner(dest)?;
| ^^^ use of undeclared crate or module `imp`
For more information about this error, try `rustc --explain E0433`.
あっこれどこかで使われているgetrandomクレートでWasmのビルドターゲットが設定されてない......
そう思ってひとまずgetrondomのクレートをaddしてfeaturesにjsを設定しましたが、今度は心当たりのない未解決のインポートがいくつも......
もしかして、Workersではsqlライブラリを使えない...??ということで、急きょsqlライブラリなしでD1に接続する方法を模索し始めました。
Axumをやめる
この項目の問題は解決しました。【追記】をご覧ください。
こちらの記事を参考に実装してみました。
ただ、こちらの記事はaxumではなくWorkersのRouterを使っています。axumのRouterだとそのままコンテキストを使えなかったりなどのデメリットが......あれ?これって最初からaxumじゃない方がよかったんじゃ????
そんなこんなでこんな感じに格闘していました。
axumやめるを決断をした直前のコードがこれです。
use axum::{extract::State, http::HeaderValue, response::Json, routing::get, Router};
use serde_json::{json, Value};
use std::sync::Arc;
use tower_http::cors::CorsLayer;
use tower_service::Service;
use worker::*;
mod entities;
use entities::db_test_entity::DbTestEntity;
fn router(env: Arc<Env>) -> Router {
Router::new()
.route("/", get(root))
.route("/db-test", get(db_test))
.with_state(env)
.layer(
CorsLayer::new()
.allow_origin(
"[PagesのOrigin]"
.parse::<HeaderValue>()
.unwrap(),
)
.allow_credentials(true),
)
}
async fn db_test(State(state): State<Arc<Env>>) -> Json<Value> {
let db = state.d1("DB").expect("DB not found");
let statement = db.prepare("SELECT * FROM Customers WHERE CustomerId = ?1");
let query = statement.bind(&[1.into()]).expect("Failed to bind");
let result = query.all().await.expect("Failed to execute query");
let vec: Vec<DbTestEntity> = result.results().unwrap_or_else(|_| vec![]);
for row in &vec {
console_log!("Result: {:?}", row.CompanyName);
}
Json(json!(vec))
}
#[event(fetch)]
async fn fetch(
req: HttpRequest,
_env: Env,
_ctx: Context,
) -> Result<axum::http::Response<axum::body::Body>> {
console_error_panic_hook::set_once();
Ok(router(Arc::new(_env)).call(req).await?)
}
async fn root() -> &'static str {
"Hello Axum!"
}
エラーの内容は以下でした
the trait `Handler<_, _>` is not implemented for fn item `fn(axum::extract::State<Arc<worker::Env>>) -> impl Future<Output = axum::Json<Vec<DbTestEntity>>> {db_test}`
多分実装したdb_testがHandlerのトレイト境界を満たしていないということなんでしょう。ちゃんとRustの知識があれば解消できたのかもと思います。
しかし、これを解消できないまま数時間経過してしまったため、「素直にaxumやめた方が早いのでは?」と思ってしまったのです。
追記
寝て起きたら解決しました。やはり睡眠は大事。
RustDocsに答えがありました。
#[worker::send]
async fn db_test(State(state): State<Arc<Env>>) -> Json<Value> {
let db = state.d1("DB").expect("DB not found");
let statement = db.prepare("SELECT * FROM Customers");
let result = statement.all().await.expect("Failed to execute query");
let vec: Vec<Customers> = result.results().unwrap_or_else(|_| vec![]);
Json(json!(vec))
}
非同期なんだからsendトレイトは当然実装されてると思ってました。自分でsendトレイトを付けてやらなきゃいけないみたいです。
やり方は3種類あって、send::SendFutureでsendではないFutureをラップする。send::SendWrapperでオブジェクトにsendの他必要なトレイトを実装する(stateとやextentionを渡す場合に便利)。そして最後に今回採用した#[worker::send]マクロを使うです。これは非同期関数自体にsendトレイトを実装してくれるようです。
非同期の関数やクロージャに渡す必要がある対象に合わせて使い分けるようですね。
今回は関数を非同期にしましたが、渡したかったのはstateなのでstateに対してSendWrapperでラップするべきだったかもです。
作り直し
先述の問題が解決したので次回からはaxumでの実装に戻ります。
一度Workersのディレクトリを消して、再度生成しなおしました。
cargo generate cloudflare/workers-rs
今回はtemplateの選択でhello-worldを選択しました。
これを早速改造していきます。といってもおおよそは先述の記事の通りですが。
use worker::*;
mod entities;
use entities::db_test_entity::Customers;
use serde_json::from_str;
#[event(fetch)]
async fn fetch(_req: Request, _env: Env, _ctx: Context) -> Result<Response> {
console_error_panic_hook::set_once();
Router::new()
.get_async("/", |_, ctx| async move { get_all_users(ctx).await })
.get_async("/:id", |_, ctx| async move { get_user_by_id(ctx).await })
.post_async("/", |req, ctx| async move { insert(req, ctx).await })
.run(_req, _env)
.await
}
async fn get_all_users(ctx: RouteContext<()>) -> Result<Response> {
let d1 = ctx.env.d1("DB")?;
let statement = d1.prepare("select * from Customers");
let result = statement.all().await?;
Response::from_json(&result.results::<Customers>().unwrap())
}
async fn get_user_by_id(ctx: RouteContext<()>) -> Result<Response> {
let id = ctx.param("id").unwrap();
let d1 = ctx.env.d1("DB")?;
let statement = d1.prepare("select * from Customers where customer_id = ?1");
let query = statement.bind(&[id.into()])?;
let result = query.first::<Customers>(None).await?;
match result {
Some(user) => Response::from_json(&user),
None => Response::error("Not found", 404),
}
}
async fn insert(mut req: Request, ctx: RouteContext<()>) -> Result<Response> {
let json_text = req.text().await?;
let customers: Customers = from_str(json_text.as_str()).unwrap();
let d1 = ctx.env.d1("DB")?;
let statement = d1.prepare(
"insert into Customers (customer_id, company_name, contact_name) values (?1, ?2, ?3)",
);
let query = statement.bind(&[
customers.customer_id.into(),
customers.company_name.into(),
customers.contact_name.into(),
])?;
let result = query.run().await?;
console_log!("{:?}", result.success());
Response::ok("post ok!")
}
クロージャの中身だけ外に出していますが、これそもそもクロージャじゃなくてそのまま関数を実行すればいいのでは...?と思いましたがすんなりいきませんでした。勉強が進んだらやります。
一応DBから取得する項目はentitiesディレクトリを作ってそこに個別にentity.rsを作っています。
curlで確認した結果、part4で作成したDBのレコードを取得できることを確認しました。
おわりに
結局今回試してみたかったaxumとsea-ormを両方とも使わないことになってしまいました。かなしや。
あとクロージャ周りの扱いに関して知識が無さ過ぎる......多分クロージャで書けるということは何らかの方法で通常の関数として書けると思うのですが。
ひとまず次回はPagesでこのWorkerを呼び出す処理を実装します。最終的にデプロイした後Pages側でdbの情報を取得できれば下準備完了として本格的にアプリ制作に入って行きます。
【追記】
記事投稿前だったのですが、axumでの実装方法がわかったため次回以降もaxumで進めます。