Skip to main content
Glama
apiError.ts6.36 kB
/** * API 错误处理工具 */ export interface APIError { message: string; code: string; status?: number; duration?: number; timestamp?: string; requestId?: string; originalError?: any; } export interface APIErrorOptions { showNotification?: boolean; logError?: boolean; retryable?: boolean; } /** * 标准化 API 错误 */ export function normalizeAPIError(error: any): APIError { // 如果已经是标准化错误,直接返回 if (error.code && error.message) { return error as APIError; } let message = "请求失败"; let code = "UNKNOWN_ERROR"; let status: number | undefined; if (error.response) { // HTTP 响应错误 status = error.response.status; const data = error.response.data; switch (status) { case 400: message = data?.message || "请求参数错误"; code = "BAD_REQUEST"; break; case 401: message = "认证失败,请重新登录"; code = "UNAUTHORIZED"; break; case 403: message = "权限不足"; code = "FORBIDDEN"; break; case 404: message = "请求的资源不存在"; code = "NOT_FOUND"; break; case 409: message = data?.message || "资源冲突"; code = "CONFLICT"; break; case 422: message = data?.message || "数据验证失败"; code = "VALIDATION_ERROR"; break; case 429: message = "请求过于频繁,请稍后再试"; code = "RATE_LIMITED"; break; case 500: message = "服务器内部错误"; code = "INTERNAL_ERROR"; break; case 502: case 503: case 504: message = "服务暂时不可用,请稍后再试"; code = "SERVICE_UNAVAILABLE"; break; default: message = data?.message || `请求失败 (${status})`; code = `HTTP_${status}`; } } else if (error.request) { // 网络错误 if (error.code === "ECONNABORTED") { message = "请求超时,请检查网络连接"; code = "TIMEOUT"; } else if (error.code === "ERR_NETWORK") { message = "网络连接失败,请检查网络设置"; code = "NETWORK_ERROR"; } else { message = "网络请求失败"; code = "REQUEST_ERROR"; } } else { // 其他错误 message = error.message || "未知错误"; code = "UNKNOWN_ERROR"; } return { message, code, status, timestamp: new Date().toISOString(), originalError: error, }; } /** * 检查错误是否可重试 */ export function isRetryableError(error: APIError): boolean { const retryableCodes = [ "TIMEOUT", "NETWORK_ERROR", "SERVICE_UNAVAILABLE", "HTTP_502", "HTTP_503", "HTTP_504", ]; return retryableCodes.includes(error.code); } /** * 检查错误是否需要认证 */ export function isAuthError(error: APIError): boolean { return error.code === "UNAUTHORIZED"; } /** * 检查错误是否是客户端错误 */ export function isClientError(error: APIError): boolean { return error.status ? error.status >= 400 && error.status < 500 : false; } /** * 检查错误是否是服务器错误 */ export function isServerError(error: APIError): boolean { return error.status ? error.status >= 500 : false; } /** * 获取用户友好的错误消息 */ export function getUserFriendlyMessage(error: APIError): string { const friendlyMessages: Record<string, string> = { NETWORK_ERROR: "网络连接异常,请检查您的网络设置", TIMEOUT: "请求超时,请稍后重试", UNAUTHORIZED: "登录已过期,请重新登录", FORBIDDEN: "您没有权限执行此操作", NOT_FOUND: "请求的内容不存在", VALIDATION_ERROR: "输入的数据格式不正确", RATE_LIMITED: "操作过于频繁,请稍后再试", SERVICE_UNAVAILABLE: "服务暂时不可用,请稍后重试", INTERNAL_ERROR: "系统出现问题,请联系管理员", }; return friendlyMessages[error.code] || error.message; } /** * 错误重试配置 */ export interface RetryConfig { maxAttempts: number; delay: number; backoff: "linear" | "exponential"; retryCondition?: (error: APIError) => boolean; } /** * 默认重试配置 */ export const DEFAULT_RETRY_CONFIG: RetryConfig = { maxAttempts: 3, delay: 1000, backoff: "exponential", retryCondition: isRetryableError, }; /** * 计算重试延迟 */ export function calculateRetryDelay( attempt: number, config: RetryConfig, ): number { const { delay, backoff } = config; switch (backoff) { case "exponential": return delay * Math.pow(2, attempt - 1); case "linear": default: return delay * attempt; } } /** * 创建重试函数 */ export function createRetryFunction<T>( fn: () => Promise<T>, config: RetryConfig = DEFAULT_RETRY_CONFIG, ): () => Promise<T> { return async (): Promise<T> => { let lastError: APIError; for (let attempt = 1; attempt <= config.maxAttempts; attempt++) { try { return await fn(); } catch (error) { lastError = normalizeAPIError(error); // 如果不满足重试条件,直接抛出错误 if (config.retryCondition && !config.retryCondition(lastError)) { throw lastError; } // 如果是最后一次尝试,抛出错误 if (attempt === config.maxAttempts) { throw lastError; } // 等待后重试 const delay = calculateRetryDelay(attempt, config); await new Promise((resolve) => setTimeout(resolve, delay)); console.warn( `API request failed, retrying (${attempt}/${config.maxAttempts}) after ${delay}ms:`, lastError.message, ); } } throw lastError!; }; } /** * API 错误日志记录 */ export function logAPIError(error: APIError, context?: string): void { const logData = { message: error.message, code: error.code, status: error.status, timestamp: error.timestamp, requestId: error.requestId, context, }; if (isServerError(error)) { console.error("Server Error:", logData); } else if (isClientError(error)) { console.warn("Client Error:", logData); } else { console.error("API Error:", logData); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zaizaizhao/mcp-swagger-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server