All files / src/utils errors.ts

100% Statements 22/22
95.23% Branches 20/21
100% Functions 7/7
100% Lines 22/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121                      38x 38x 38x 38x                         9x                   11x                         2x                     10x                         4x                         5x   18x     18x   12x 11x 11x     1x     1x         6x   18x       2x 2x       4x    
import type { Response } from "express";
 
/**
 * アプリケーション内で使用するカスタムエラーの基底クラス。
 * HTTPステータスコードを保持する `statusCode` プロパティを追加し、
 * APIのレスポンスで適切なステータスを返せるように設計されています。
 */
export class CustomError extends Error {
  statusCode: number;
 
  constructor(message: string, statusCode: number = 500) {
    super(message);
    this.name = this.constructor.name;
    this.statusCode = statusCode;
    Error.captureStackTrace(this, this.constructor);
  }
}
 
/**
 * 認証関連のエラー(例: トークン無効、権限不足)を示すクラス。
 * HTTPステータスコードは `401 Unauthorized` がデフォルトです。
 */
export class AuthenticationError extends CustomError {
  constructor(
    message: string = "Authentication failed",
    statusCode: number = 401,
  ) {
    super(message, statusCode);
  }
}
 
/**
 * リクエストの入力値検証エラー(例: 必須パラメータの欠如、フォーマット不正)を示すクラス。
 * HTTPステータスコードは `400 Bad Request` がデフォルトです。
 */
export class ValidationError extends CustomError {
  constructor(message: string = "Validation failed", statusCode: number = 400) {
    super(message, statusCode);
  }
}
 
/**
 * 要求されたリソースが見つからない場合のエラーを示すクラス。
 * HTTPステータスコードは `404 Not Found` がデフォルトです。
 */
export class NotFoundError extends CustomError {
  constructor(
    message: string = "Resource not found",
    statusCode: number = 404,
  ) {
    super(message, statusCode);
  }
}
 
/**
 * Fitbit APIとの通信中に発生したエラーを示すクラス。
 * 外部API起因の問題であることを明確にします。
 * デフォルトのステータスコードは `500 Internal Server Error` とし、クライアント側には詳細を伝えないようにします。
 */
export class FitbitApiError extends CustomError {
  constructor(message: string = "Fitbit API error", statusCode: number = 500) {
    super(message, statusCode);
  }
}
 
/**
 * サポートされていないHTTPメソッドでエンドポイントが呼び出されたことを示すエラー。
 * HTTPステータスコードは `405 Method Not Allowed` がデフォルトです。
 */
export class MethodNotAllowedError extends CustomError {
  constructor(
    message: string = "Method Not Allowed",
    statusCode: number = 405,
  ) {
    super(message, statusCode);
  }
}
 
/**
 * 統一的なエラーハンドリングを行うユーティリティ関数。
 * 発生したエラーをログに出力し、クライアントに適切なHTTPステータスコードとメッセージを返します。
 * セキュリティ上の理由から、500以上のエラーや予期せぬエラーの詳細はクライアントに開示せず、
 * 汎用的なメッセージを返します。
 *
 * @param res Expressのレスポンスオブジェクト
 * @param error 発生したエラーオブジェクト
 */
export const handleError = (res: Response, error: unknown): void => {
  // エラーの詳細をサーバーログに出力 (重要)
  console.error("Error caught in handler:", error);
 
  // カスタムエラーの場合
  if (error instanceof CustomError) {
    // 400番台のエラー(クライアントエラー)はメッセージを返しても安全
    if (error.statusCode < 500) {
      res.status(error.statusCode).json({ error: error.message });
      return;
    }
    // 500番台のエラーは詳細を隠蔽
    res
      .status(error.statusCode)
      .json({ error: "An internal server error occurred." });
    return;
  }
 
  // 特定のエラーメッセージパターンに対する互換性維持
  // errorがErrorオブジェクトの場合はmessageを取得、それ以外(文字列など)はString変換
  const errorMessage = error instanceof Error ? error.message : String(error);
 
  if (
    errorMessage.includes("ID token") ||
    errorMessage.includes("Unauthorized")
  ) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }
 
  // その他の予期せぬエラー
  res.status(500).json({ error: "An internal server error occurred." });
};