8
5

More than 3 years have passed since last update.

Firestore から発生するエラーを HttpsError に変換する

Posted at

概要

firestore thrown -> HttpsError への変換器を作って
ある程度汎用的に firestore のエラーをハンドリングしてしまおう。

背景

あるプロジェクトのサーバーサイドでは、
想定内のエラーが起きた時に HttpsError を throw する決まりとしている。
(throw された HttpsError は独自の共通処理により、内容に応じたステータスコードをレスポンスしている。)

firestore では、例えば存在しない document を update しようとした場合などにエラーを throw してくる。
しかし、 firestore にふれるすべての部分で、
想定内の firestore thrown -> HttpsError への変換を担うのはとてもコストが高く現実的ではない。
(本日時点では firestore thrown の型の定義が参照できないこともその一因だ。)

実装

firestore thrown ハンドラ

firestore-helper.ts
import { HttpsError } from "firebase-functions/lib/providers/https";

export class FirestoreHelper {
  /**
   * 2020/06/10 時点では、firestore が throw するエラーの型情報にアクセスできないため、
   * HttpsError に生まれ変わらせるハンドラを作った。
   */
  public static errorHandler(error: any) {
    const isFirestoreThrown =
      error.code != null || error.details != null || error.metadata != null;

    if (!isFirestoreThrown) {
      throw error;
    }

    // Code は FirebaseFirestore.GrpcStatus に基づいているが、ここからは型参照できない
    switch (error.code) {
      case 0: //OK = 0,
        throw new HttpsError("ok", error.details);
      case 1: //CANCELLED = 1,
        throw new HttpsError("cancelled", error.details);
      case 2: //UNKNOWN = 2,
        throw new HttpsError("unknown", error.details);
      case 3: //INVALID_ARGUMENT = 3,
        throw new HttpsError("invalid-argument", error.details);
      case 4: //DEADLINE_EXCEEDED = 4,
        throw new HttpsError("deadline-exceeded", error.details);
      case 5: //NOT_FOUND = 5,
        throw new HttpsError("not-found", error.details);
      case 6: //ALREADY_EXISTS = 6,
        throw new HttpsError("already-exists", error.details);
      case 7: //PERMISSION_DENIED = 7,
        throw new HttpsError("permission-denied", error.details);
      case 8: //RESOURCE_EXHAUSTED = 8,
        throw new HttpsError("resource-exhausted", error.details);
      case 9: //FAILED_PRECONDITION = 9,
        throw new HttpsError("failed-precondition", error.details);
      case 10: //ABORTED = 10,
        throw new HttpsError("aborted", error.details);
      case 11: //OUT_OF_RANGE = 11,
        throw new HttpsError("out-of-range", error.details);
      case 12: //UNIMPLEMENTED = 12,
        throw new HttpsError("unimplemented", error.details);
      case 13: //INTERNAL = 13,
        throw new HttpsError("internal", error.details);
      case 14: //UNAVAILABLE = 14,
        throw new HttpsError("unavailable", error.details);
      case 15: //DATA_LOSS = 15,
        throw new HttpsError("data-loss", error.details);
      case 16: //UNAUTHENTICATED = 16,
        throw new HttpsError("unauthenticated", error.details);
      default:
        throw error;
    }
  }
}

ハンドラの利用例

dragon.service.ts
    await firestore.runTransaction(async (tx) => {
        // do something...
    }).catch(FirestoreHelper.errorHandler); // ⭐ here!

dragon.service.ts
    await firestore.collection("dragons")
      .doc(dragonId)
      .update(updates)
      .catch(FirestoreHelper.errorHandler); // ⭐ here!

変換後の HttpsError 例

{
  "message": "No document to update: projects/<YOUR_PROJECT_NAME>/databases/(default)/documents/dragons/drag_01eac5sjgnxacznztyjs2m8fzw1",
  "status": "NOT_FOUND"
}

結論

  • 強く意識しなくとも、 firestore エラーを汎用的に処理できるようになったぞ
  • しかし、エラーメッセージに実装内容が滲み出てしまっているので注意だぞ
  • firestore には早くエラー型の定義をしてほしいぞ
8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5