/** * 統一的API錯誤處理工具 * 提供一致的錯誤分類、格式化和處理邏輯 */ export enum ApiErrorType { NETWORK_ERROR = 'NETWORK_ERROR', VALIDATION_ERROR = 'VALIDATION_ERROR', AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR', AUTHORIZATION_ERROR = 'AUTHORIZATION_ERROR', NOT_FOUND_ERROR = 'NOT_FOUND_ERROR', SERVER_ERROR = 'SERVER_ERROR', TIMEOUT_ERROR = 'TIMEOUT_ERROR', UNKNOWN_ERROR = 'UNKNOWN_ERROR' } export interface ApiError { type: ApiErrorType message: string details?: any statusCode?: number timestamp: string context?: string } export interface StandardApiResponse { success: boolean data?: T error?: ApiError message?: string } /** * 根據HTTP狀態碼確定錯誤類型 */ const getErrorTypeFromStatus = (status: number): ApiErrorType => { switch (true) { case status === 400: return ApiErrorType.VALIDATION_ERROR case status === 401: return ApiErrorType.AUTHENTICATION_ERROR case status === 403: return ApiErrorType.AUTHORIZATION_ERROR case status === 404: return ApiErrorType.NOT_FOUND_ERROR case status >= 500: return ApiErrorType.SERVER_ERROR case status === 0: return ApiErrorType.NETWORK_ERROR default: return ApiErrorType.UNKNOWN_ERROR } } /** * 從Response對象創建統一的錯誤 */ export const createApiErrorFromResponse = async ( response: Response, context?: string ): Promise => { let errorData: any = {} try { errorData = await response.json() } catch { errorData = { error: 'Failed to parse error response' } } const errorType = getErrorTypeFromStatus(response.status) // 統一的錯誤訊息提取邏輯 let message = '' if (errorData.error) { message = errorData.error } else if (errorData.message) { message = errorData.message } else if (errorData.title) { message = errorData.title } else if (errorData.details) { message = errorData.details } else { message = getDefaultErrorMessage(errorType, response.status) } // 處理驗證錯誤的特殊格式 if (errorType === ApiErrorType.VALIDATION_ERROR && errorData.errors) { const validationErrors = Object.values(errorData.errors).flat().join(', ') message = validationErrors || message } return { type: errorType, message, details: errorData, statusCode: response.status, timestamp: new Date().toISOString(), context } } /** * 從Exception創建統一的錯誤 */ export const createApiErrorFromException = ( error: unknown, context?: string ): ApiError => { if (error instanceof Error) { // 檢查是否為網路錯誤 if (error.name === 'TypeError' && error.message.includes('fetch')) { return { type: ApiErrorType.NETWORK_ERROR, message: '網路連線失敗,請檢查網路連線', timestamp: new Date().toISOString(), context } } // 檢查是否為超時錯誤 if (error.message.includes('timeout')) { return { type: ApiErrorType.TIMEOUT_ERROR, message: '請求超時,請稍後重試', timestamp: new Date().toISOString(), context } } return { type: ApiErrorType.UNKNOWN_ERROR, message: error.message, timestamp: new Date().toISOString(), context } } return { type: ApiErrorType.UNKNOWN_ERROR, message: '發生未知錯誤', timestamp: new Date().toISOString(), context } } /** * 獲取預設錯誤訊息 */ const getDefaultErrorMessage = (type: ApiErrorType, statusCode?: number): string => { switch (type) { case ApiErrorType.NETWORK_ERROR: return '網路連線失敗,請檢查網路連線' case ApiErrorType.VALIDATION_ERROR: return '輸入資料驗證失敗,請檢查輸入內容' case ApiErrorType.AUTHENTICATION_ERROR: return '身份驗證失敗,請重新登入' case ApiErrorType.AUTHORIZATION_ERROR: return '權限不足,無法執行此操作' case ApiErrorType.NOT_FOUND_ERROR: return '請求的資源不存在' case ApiErrorType.SERVER_ERROR: return '伺服器發生錯誤,請稍後重試' case ApiErrorType.TIMEOUT_ERROR: return '請求超時,請稍後重試' default: return `發生錯誤 (HTTP ${statusCode || 'Unknown'})` } } /** * 創建成功的API回應 */ export const createSuccessResponse = ( data: T, message?: string ): StandardApiResponse => { return { success: true, data, message } } /** * 創建錯誤的API回應 */ export const createErrorResponse = (error: ApiError): StandardApiResponse => { return { success: false, error } } /** * 檢查錯誤是否可重試 */ export const isRetryableError = (error: ApiError): boolean => { return [ ApiErrorType.NETWORK_ERROR, ApiErrorType.TIMEOUT_ERROR, ApiErrorType.SERVER_ERROR ].includes(error.type) } /** * 獲取使用者友善的錯誤訊息 */ export const getUserFriendlyErrorMessage = (error: ApiError): string => { switch (error.type) { case ApiErrorType.NETWORK_ERROR: return '網路連線有問題,請檢查網路後重試' case ApiErrorType.VALIDATION_ERROR: return `輸入資料有誤:${error.message}` case ApiErrorType.AUTHENTICATION_ERROR: return '登入已過期,請重新登入' case ApiErrorType.AUTHORIZATION_ERROR: return '您沒有權限執行此操作' case ApiErrorType.NOT_FOUND_ERROR: return '找不到請求的內容' case ApiErrorType.SERVER_ERROR: return '伺服器暫時無法處理請求,請稍後重試' case ApiErrorType.TIMEOUT_ERROR: return '請求處理時間過長,請稍後重試' default: return error.message || '發生未知錯誤' } }