{"version":3,"file":"error-normalizer.mjs","sources":["../../../src/lib/api/error-normalizer.ts"],"sourcesContent":["/**\n * @file Error Normalizer\n * @description Error normalization utilities for converting various error formats\n * into consistent ApiError objects with proper categorization and metadata.\n */\n\nimport type {\n  ApiError,\n  ErrorCategory,\n  ErrorSeverity,\n  FieldError,\n  ServerErrorResponse,\n} from './types';\n\n/**\n * Error normalization configuration\n */\nexport interface ErrorNormalizationConfig {\n  /** Map custom error codes to categories */\n  codeMapping?: Record<string, ErrorCategory>;\n  /** Extract field errors from response */\n  extractFieldErrors?: (response: unknown) => FieldError[] | undefined;\n  /** Custom message generator */\n  generateMessage?: (status: number, response: unknown) => string;\n  /** Include response body in error */\n  includeResponseBody?: boolean;\n}\n\n/**\n * Default error code to category mapping\n */\nconst DEFAULT_CODE_MAPPING: Record<string, ErrorCategory> = {\n  UNAUTHENTICATED: 'authentication',\n  UNAUTHORIZED: 'authorization',\n  FORBIDDEN: 'authorization',\n  NOT_FOUND: 'not_found',\n  CONFLICT: 'conflict',\n  VALIDATION_ERROR: 'validation',\n  INVALID_INPUT: 'validation',\n  RATE_LIMITED: 'rate_limit',\n  TIMEOUT: 'timeout',\n  INTERNAL_ERROR: 'server',\n  SERVICE_UNAVAILABLE: 'server',\n};\n\n/**\n * Normalize various error formats into a consistent ApiError\n *\n * @param error - Error from API or network\n * @param config - Normalization configuration\n * @returns Normalized ApiError\n *\n * @example\n * ```typescript\n * try {\n *   await apiClient.get('/users');\n * } catch (error) {\n *   const normalizedError = normalizeError(error);\n *   console.log(normalizedError.category); // 'authentication', 'validation', etc.\n *   console.log(normalizedError.fieldErrors); // Field-level errors\n * }\n * ```\n */\nexport function normalizeError(error: unknown, config: ErrorNormalizationConfig = {}): ApiError {\n  // Already an ApiError\n  if (isApiError(error)) {\n    return error;\n  }\n\n  // Network error\n  if (error instanceof TypeError && error.message.includes('fetch')) {\n    return createNetworkError(error.message, error);\n  }\n\n  // Abort error\n  if (error instanceof DOMException && error.name === 'AbortError') {\n    return createAbortError();\n  }\n\n  // HTTP error response\n  if (isErrorResponse(error)) {\n    return normalizeHttpError(error, config);\n  }\n\n  // Generic Error\n  if (error instanceof Error) {\n    return createUnknownError(error.message, error);\n  }\n\n  // String error\n  if (typeof error === 'string') {\n    return createUnknownError(error);\n  }\n\n  // Fallback\n  return createUnknownError('An unexpected error occurred');\n}\n\n/**\n * Normalize HTTP error response\n */\nfunction normalizeHttpError(\n  error: { status: number; response?: unknown; message?: string },\n  config: ErrorNormalizationConfig\n): ApiError {\n  const { status, response, message: errorMessage } = error;\n  const category = getErrorCategory(status);\n  const severity = getErrorSeverity(category);\n\n  let message = errorMessage ?? `HTTP ${status}`;\n  let code = `HTTP_${status}`;\n  let fieldErrors: FieldError[] | undefined;\n\n  // Parse response body\n  if (response !== null && response !== undefined && typeof response === 'object') {\n    const serverError = response as ServerErrorResponse;\n\n    // Extract message\n    if (\n      serverError.message !== undefined &&\n      serverError.message !== null &&\n      serverError.message !== ''\n    ) {\n      ({ message } = serverError);\n    } else if (serverError.error !== undefined && serverError.error !== null) {\n      message =\n        typeof serverError.error === 'string'\n          ? serverError.error\n          : (serverError.error.message ?? message);\n    }\n\n    // Extract code\n    if (serverError.code !== undefined && serverError.code !== null && serverError.code !== '') {\n      ({ code } = serverError);\n    } else if (\n      serverError.error !== undefined &&\n      serverError.error !== null &&\n      typeof serverError.error === 'object' &&\n      serverError.error.code !== undefined &&\n      serverError.error.code !== null &&\n      serverError.error.code !== ''\n    ) {\n      ({ code } = serverError.error);\n    }\n\n    // Extract field errors\n    if (config.extractFieldErrors) {\n      fieldErrors = config.extractFieldErrors(response);\n    } else if (serverError.errors && Array.isArray(serverError.errors)) {\n      fieldErrors = serverError.errors.map((e) => ({\n        field: e.field,\n        message: e.message,\n      }));\n    }\n\n    // Custom message generation\n    if (config.generateMessage) {\n      message = config.generateMessage(status, response);\n    }\n  }\n\n  // Map code to category\n  const codeMapping = { ...DEFAULT_CODE_MAPPING, ...config.codeMapping };\n  const mappedCategory = codeMapping[code] ?? category;\n\n  return createApiError({\n    status,\n    code,\n    message,\n    category: mappedCategory,\n    severity,\n    fieldErrors,\n    response: config.includeResponseBody === true ? response : undefined,\n    retryable: isRetryableStatus(status),\n  });\n}\n\n/**\n * Get error category from HTTP status\n */\nfunction getErrorCategory(status: number): ErrorCategory {\n  if (status === 401) return 'authentication';\n  if (status === 403) return 'authorization';\n  if (status === 404) return 'not_found';\n  if (status === 409) return 'conflict';\n  if (status === 422 || status === 400) return 'validation';\n  if (status === 429) return 'rate_limit';\n  if (status === 408) return 'timeout';\n  if (status >= 500) return 'server';\n  return 'unknown';\n}\n\n/**\n * Get error severity from category\n */\nfunction getErrorSeverity(category: ErrorCategory): ErrorSeverity {\n  switch (category) {\n    case 'authentication':\n    case 'authorization':\n    case 'server':\n      return 'error';\n    case 'validation':\n    case 'conflict':\n    case 'not_found':\n      return 'warning';\n    case 'rate_limit':\n    case 'timeout':\n      return 'info';\n    default:\n      return 'error';\n  }\n}\n\n/**\n * Check if status is retryable\n */\nfunction isRetryableStatus(status: number): boolean {\n  return [408, 429, 500, 502, 503, 504].includes(status);\n}\n\n/**\n * Type guard for API errors\n */\nexport function isApiError(error: unknown): error is ApiError {\n  return (\n    error !== null &&\n    typeof error === 'object' &&\n    'name' in error &&\n    (error as ApiError).name === 'ApiError'\n  );\n}\n\n/**\n * Type guard for error responses\n */\nfunction isErrorResponse(\n  error: unknown\n): error is { status: number; response?: unknown; message?: string } {\n  return (\n    error !== null &&\n    typeof error === 'object' &&\n    'status' in error &&\n    typeof (error as { status: unknown }).status === 'number'\n  );\n}\n\n/**\n * Create API error factory functions\n */\nfunction createApiError(params: {\n  status: number;\n  code: string;\n  message: string;\n  category: ErrorCategory;\n  severity: ErrorSeverity;\n  fieldErrors?: FieldError[];\n  response?: unknown;\n  cause?: Error;\n  retryable: boolean;\n}): ApiError {\n  const error = new Error(params.message) as ApiError;\n  error.name = 'ApiError';\n  error.status = params.status;\n  error.code = params.code;\n  error.message = params.message;\n  error.category = params.category;\n  error.severity = params.severity;\n  error.fieldErrors = params.fieldErrors;\n  error.response = params.response;\n  error.cause = params.cause;\n  error.retryable = params.retryable;\n  error.timestamp = Date.now();\n  return error;\n}\n\nfunction createNetworkError(message: string, cause?: Error): ApiError {\n  return createApiError({\n    status: 0,\n    code: 'NETWORK_ERROR',\n    message: message ?? 'Network error occurred',\n    category: 'network',\n    severity: 'error',\n    cause,\n    retryable: true,\n  });\n}\n\nfunction createAbortError(): ApiError {\n  return createApiError({\n    status: 0,\n    code: 'ABORTED',\n    message: 'Request was aborted',\n    category: 'cancelled',\n    severity: 'info',\n    retryable: false,\n  });\n}\n\nfunction createUnknownError(message: string, cause?: Error): ApiError {\n  return createApiError({\n    status: 0,\n    code: 'UNKNOWN_ERROR',\n    message,\n    category: 'unknown',\n    severity: 'error',\n    cause,\n    retryable: false,\n  });\n}\n"],"names":["DEFAULT_CODE_MAPPING","normalizeError","error","config","isApiError","createNetworkError","createAbortError","isErrorResponse","normalizeHttpError","createUnknownError","status","response","errorMessage","category","getErrorCategory","severity","getErrorSeverity","message","code","fieldErrors","serverError","e","mappedCategory","createApiError","isRetryableStatus","params","cause"],"mappings":"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"}