はじめに
前回はPagesからHTTPリクエストを送り、WorkersでD1から情報を取得してレスポンスを返すところまでを作りました。
今回は、Workersのエラーハンドリングを作っていこうと思います。
(毎回cloudflareタグをつけているけどRustの記事になりつつある......)
その前に
今回のWorkersにはRustのaxumを使用しているので、それに則ったエラーハンドリングをしていきます。
axumのエラーハンドリングについては別記事にまとめたのでこちらを参照してください。
現状確認
まず、現状エラーが発生して早期リターンしたときはどんなレスポンスが返却されるのかを確認してみます。
一例として、前回実装した1件取得メソッドです。以前は.expectを書いていましたが、これだと失敗時に内部でパニックしレスポンスを返しません。.expectを?に置き換えていきましょう。
#[worker::send]
async fn db_test_with_params(
State(state): State<Arc<Env>>,
Path(customer_id): Path<f64>,
) -> Json<Value> {
let db = state.d1("DB")?;
let statement = db.prepare("SELECT * FROM Customers WHERE customer_id = ?1");
let statement = statement.bind(&[wasm_bindgen::JsValue::from_f64(customer_id)])?;
let result = statement.all().await?;
let vec: Vec<Customers> = result.results().unwrap_or_else(|_| vec![]);
Json(json!(vec))
}
このようにすると、?でコンパイルエラーが発生します。
内容は以下の通りです。
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src\lib.rs:61:28
|
56 | #[worker::send]
| --------------- this function should return `Result` or `Option` to accept `?`
...
61 | let db = state.d1("DB")?;
| ^ cannot use the `?` operator in an async block that returns `axum::Json<Value>`
|
= help: the trait `FromResidual<std::result::Result<std::convert::Infallible, worker::Error>>` is not implemented for `axum::Json<Value>`
ResultかOptionを返すものにのみ?を付けられるというエラーです。
現状この関数はJsonを返すため、ResultやOptionを返すことができません。
まずはこの関数がresponseを返すようにしましょう。
エラー処理実装
とりあえず、InternalServerErrorの構造体を作成しました。anyhowクレートを利用して、渡されたエラーからIntoResponseトレイトを実装したInternalServerErrorに変換できます。
関数の方は、Errが発生する可能性のある箇所で、その原因を知らせるメッセージを入れるようにしました。
struct InternalServerError(anyhow::Error);
impl<E> From<E> for InternalServerError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
InternalServerError(err.into())
}
}
impl axum::response::IntoResponse for InternalServerError {
fn into_response(self) -> axum::response::Response {
(StatusCode::INTERNAL_SERVER_ERROR, self.0.to_string()).into_response()
}
}
#[worker::send]
async fn db_test_with_params(
State(state): State<Arc<Env>>,
Path(customer_id): Path<f64>,
) -> std::result::Result<Json<Value>, impl axum::response::IntoResponse> {
let db = state
.d1("DB")
.map_err(|_| InternalServerError(anyhow::anyhow!("DB not found")))?;
let statement = db.prepare("SELECT * FROM Customers WHERE customer_id = ?1");
let statement = statement
.bind(&[wasm_bindgen::JsValue::from_f64(customer_id)])
.map_err(|_| InternalServerError(anyhow::anyhow!("Failed to bind params")))?;
let result = statement
.all()
.await
.map_err(|_| InternalServerError(anyhow::anyhow!("Failed to execute query")))?;
let vec: Vec<Customers> = result.results().unwrap_or_else(|_| vec![]);
Ok::<Json<Value>, InternalServerError>(Json(json!(vec)))
}
これにより、エラーが発生してもレスポンスが返されるようになったはずです。
(同じ記法を繰り返しているので次はここをマクロ化とかしてみたいですね。というかthiserror使った方がいいか......)
動作確認
ほんとはRustのユニットテストで戻り値を確認とかしたいのですが、まだそこは勉強できていないので......
ひとまず、wrangler.tomlにてbindingの値を変更して、Pagesからリクエストを送信します。
その結果、返されたレスポンスは以下の通りです。
{
"data": "DB not found",
"status": 500,
"statusText": "Internal Server Error",
"headers": {
"content-type": "text/plain; charset=utf-8"
},
"config": {
"transitional": {
"silentJSONParsing": true,
"forcedJSONParsing": true,
"clarifyTimeoutError": false
},
"adapter": [
"xhr",
"http",
"fetch"
],
"transformRequest": [
null
],
"transformResponse": [
null
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1,
"env": {},
"headers": {
"Accept": "application/json, text/plain, */*"
},
"method": "get",
"url": "http://localhost:8787/db-test-with-params/50"
},
"request": {}
}
ちゃんと設定したステータスコードとメッセージが返却されています。
おわりに
今回はシリーズ内で作ってきたWorkerにエラー処理を実装し、エラー時でもレスポンスを返せるようにしました。
エラー処理についてはもう少しいい書き方がありそうな気はしますが......こちらは自分への課題とします。
次回は考え中ですが、認証、認可あたりに触っていこうかなとうっすら思っています。
その前にRustのマクロやユニットテストの勉強を番外編で挟もうと思っています。
気が向いたらご一読いただけると嬉しいです。