{"version":3,"sources":["../src/client-sdk/services/_shared/format-api-error.ts"],"sourcesContent":["/**\n * Extracts the most informative, user-facing message from an API error body.\n *\n * Errors from the LangWatch API follow the shape `{ error: string, message?: string }`\n * per `errorSchema` in the server. In production the middleware may return a\n * generic `{ error: \"Internal server error\", message: \"Internal server error\" }`\n * which is useless to a user — this helper at least falls back to stringifying\n * the raw body so no diagnostic information is lost.\n *\n * Priority (first non-generic, non-empty wins):\n *   1. `body.message` (descriptive sentence from the server)\n *   2. `body.error`   (error kind — \"NotFoundError\", \"Conflict\", …)\n *   3. Any other string fields on the body object (e.g. `detail`, `reason`)\n *   4. JSON stringification of the entire body\n *   5. `Error#message` if the input is a thrown Error\n *   6. A status-code-derived fallback, if available\n */\nconst GENERIC_MESSAGES = new Set([\n  \"\",\n  \"internal server error\",\n  \"unknown error\",\n  \"unknown error occurred\",\n]);\n\nfunction isGeneric(s: string): boolean {\n  return GENERIC_MESSAGES.has(s.trim().toLowerCase());\n}\n\nfunction firstMeaningful(...candidates: Array<unknown>): string | undefined {\n  for (const c of candidates) {\n    if (typeof c === \"string\" && !isGeneric(c)) return c;\n  }\n  return undefined;\n}\n\nfunction collectAllOwnPropertyNames(value: unknown, seen = new Set<unknown>()): string[] {\n  if (!value || typeof value !== \"object\" || seen.has(value)) return [];\n  seen.add(value);\n  const names = new Set<string>();\n  for (const name of Object.getOwnPropertyNames(value)) {\n    names.add(name);\n    try {\n      const child = (value as Record<string, unknown>)[name];\n      for (const nested of collectAllOwnPropertyNames(child, seen)) {\n        names.add(nested);\n      }\n    } catch {\n      // Ignore getter side effects.\n    }\n  }\n  return Array.from(names);\n}\n\ninterface ZodIssue {\n  path?: unknown;\n  message?: unknown;\n}\n\n/**\n * Renders a Zod validation error body into a user-readable string. Returns\n * undefined if `body` is not a Zod error shape.\n *\n * Examples:\n *   { name: \"ZodError\", issues: [{ path: [\"format\"], message: \"Invalid enum value...\" }] }\n *   → \"Validation failed: format — Invalid enum value...\"\n *   { name: \"ZodError\", issues: [<issue1>, <issue2>] }\n *   → \"Validation failed: a.b — msg1; c — msg2\"\n */\nfunction formatZodIssues(body: Record<string, unknown>): string | undefined {\n  const isZod =\n    body.name === \"ZodError\" ||\n    (Array.isArray(body.issues) && body.issues.length > 0);\n  if (!isZod || !Array.isArray(body.issues)) return undefined;\n\n  const rendered = (body.issues as ZodIssue[])\n    .map((issue) => {\n      const pathArr = Array.isArray(issue.path) ? issue.path : [];\n      const path = pathArr\n        .filter((p) => typeof p === \"string\" || typeof p === \"number\")\n        .join(\".\");\n      const msg = typeof issue.message === \"string\" ? issue.message : \"\";\n      if (path && msg) return `${path} — ${msg}`;\n      if (msg) return msg;\n      if (path) return path;\n      return undefined;\n    })\n    .filter((s): s is string => typeof s === \"string\" && s.length > 0);\n\n  if (rendered.length === 0) return undefined;\n  return `Validation failed: ${rendered.join(\"; \")}`;\n}\n\nfunction stringifyBody(body: unknown): string {\n  try {\n    // Use all own property names (including non-enumerable ones, common on\n    // native Error instances and fetch errors) so we preserve every field the\n    // server sent, at any nesting depth.\n    if (body && typeof body === \"object\") {\n      return JSON.stringify(body, collectAllOwnPropertyNames(body));\n    }\n    return JSON.stringify(body);\n  } catch {\n    return String(body);\n  }\n}\n\nexport interface FormatApiErrorOptions {\n  /**\n   * HTTP status code from the response, when known. Used as part of the\n   * fallback output so that the user has at least some actionable signal.\n   */\n  status?: number;\n}\n\nexport interface FormatApiErrorMessageParams {\n  error: unknown;\n  /** Currently only `status` — kept as an object for forward extension. */\n  options?: FormatApiErrorOptions;\n}\n\nexport function formatApiErrorMessage({\n  error,\n  options = {},\n}: FormatApiErrorMessageParams): string {\n  if (error == null) {\n    return options.status\n      ? `Request failed with status ${options.status}`\n      : \"Unknown error occurred\";\n  }\n\n  if (typeof error === \"string\") {\n    return isGeneric(error) && options.status\n      ? `${error} (status ${options.status})`\n      : error;\n  }\n\n  if (error instanceof Error) {\n    // Node's fetch wraps transport failures as `TypeError: fetch failed` with\n    // the real reason (ECONNREFUSED, ENOTFOUND, timeout, etc.) on `.cause`.\n    // Surface that so the user can tell whether the endpoint is wrong, the\n    // server is down, or DNS can't resolve the host.\n    const base = error.message || \"Unknown error occurred\";\n    const cause = (error as { cause?: unknown }).cause;\n    const causeMsg =\n      cause instanceof Error\n        ? cause.message\n        : cause && typeof cause === \"object\" &&\n            typeof (cause as { message?: unknown }).message === \"string\"\n          ? (cause as { message: string }).message\n          : undefined;\n    const causeCode =\n      cause && typeof cause === \"object\" &&\n      typeof (cause as { code?: unknown }).code === \"string\"\n        ? (cause as { code: string }).code\n        : undefined;\n\n    const detail = [causeCode, causeMsg && causeMsg !== base ? causeMsg : undefined]\n      .filter((s): s is string => typeof s === \"string\" && s.length > 0)\n      .join(\": \");\n    const formatted = detail ? `${base} (${detail})` : base;\n\n    // Node fetch emits \"TypeError: fetch failed\" with `cause.message =\n    // \"unknown scheme\"` when the URL has no/invalid scheme (e.g. the user\n    // set LANGWATCH_ENDPOINT=localhost:5570 instead of http://localhost:5570).\n    // Add a hint — the raw phrase tells the user nothing actionable.\n    const combined = `${base} ${causeMsg ?? \"\"} ${causeCode ?? \"\"}`.toLowerCase();\n    if (\n      combined.includes(\"unknown scheme\") ||\n      combined.includes(\"err_invalid_url\") ||\n      combined.includes(\"failed to parse url\")\n    ) {\n      return `${formatted} — check your LANGWATCH_ENDPOINT (must start with http:// or https://)`;\n    }\n    return formatted;\n  }\n\n  if (typeof error === \"object\") {\n    const body = error as Record<string, unknown>;\n\n    // Zod validation errors: `{ name: \"ZodError\", issues: [{ path, message }] }`.\n    // Without this they render as unreadable raw JSON to the user.\n    const zod = formatZodIssues(body);\n    if (zod) return zod;\n\n    // Most specific fields first.\n    const fromMessage = typeof body.message === \"string\" ? body.message : undefined;\n    const fromError = typeof body.error === \"string\" ? body.error : undefined;\n    const fromDetail = typeof body.detail === \"string\" ? body.detail : undefined;\n    const fromReason = typeof body.reason === \"string\" ? body.reason : undefined;\n\n    // 1. Top-level meaningful fields take priority. If the server gave us a\n    //    descriptive `message`/`error`/`detail`/`reason`, use that — even if\n    //    `body.error` is an object with its own (potentially generic) message.\n    const meaningful = firstMeaningful(fromMessage, fromError, fromDetail, fromReason);\n    if (meaningful) {\n      if (\n        fromError &&\n        fromMessage &&\n        fromMessage !== fromError &&\n        !isGeneric(fromError) &&\n        !isGeneric(fromMessage)\n      ) {\n        return `${fromError}: ${fromMessage}`;\n      }\n      return meaningful;\n    }\n\n    // 2. Nested `body.error` — only used as a fallback when no top-level\n    //    field carried a useful message. Native Error instances and\n    //    tRPC-style envelopes both fit here.\n    if (body.error && typeof body.error === \"object\") {\n      const nested = body.error as Record<string, unknown>;\n\n      if (body.error instanceof Error && body.error.message) {\n        return body.error.message;\n      }\n\n      // Zod validation envelopes: `{ success: false, error: { name: \"ZodError\", issues: [...] } }`\n      const nestedZod = formatZodIssues(nested);\n      if (nestedZod) return nestedZod;\n\n      const fromNestedMsg = typeof nested.message === \"string\" ? nested.message : undefined;\n      const fromNestedErr = typeof nested.error === \"string\" ? nested.error : undefined;\n      const nestedMeaningful = firstMeaningful(fromNestedMsg, fromNestedErr);\n      if (nestedMeaningful) {\n        return nestedMeaningful;\n      }\n\n      // Stringify the nested object so identifiers like `{ code, status }`\n      // still reach the user.\n      const nestedRaw = stringifyBody(nested);\n      if (nestedRaw && nestedRaw !== \"{}\") {\n        return nestedRaw;\n      }\n    }\n\n    // 3. No meaningful top-level or nested fields — dump the raw JSON so\n    //    the user at least sees the server payload. Attach status for\n    //    context.\n    const raw = stringifyBody(body);\n\n    // Collapse empty / near-empty payloads to a friendlier message — there's\n    // nothing for the user to see in `{}` anyway.\n    if (!raw || raw === \"{}\" || raw === '\"\"' || raw === \"null\") {\n      return options.status\n        ? `Request failed with status ${options.status}`\n        : \"Unknown error occurred\";\n    }\n\n    const withStatus = options.status ? `status ${options.status} ${raw}` : raw;\n    return `server returned ${withStatus}`;\n  }\n\n  // Primitive types (number, boolean, bigint, symbol) — coerce safely.\n  // We've already handled string, null/undefined, Error, and object above.\n  try {\n    return String(error as number | boolean | bigint);\n  } catch {\n    return \"Unknown error occurred\";\n  }\n}\n\nexport interface FormatApiErrorForOperationParams {\n  operation: string;\n  error: unknown;\n  options?: FormatApiErrorOptions;\n}\n\n/**\n * Builds a fully-qualified error message for a specific operation, including\n * the operation name and the extracted server-side message.\n */\nexport function formatApiErrorForOperation({\n  operation,\n  error,\n  options = {},\n}: FormatApiErrorForOperationParams): string {\n  const message = formatApiErrorMessage({ error, options });\n  return `Failed to ${operation}: ${message}`;\n}\n\n/**\n * Attempts to read a status code from common response-shaped wrappers without\n * assuming a particular SDK. Returns undefined if none is found.\n */\nexport function extractStatusFromResponse(value: unknown): number | undefined {\n  if (!value || typeof value !== \"object\") return undefined;\n  const obj = value as Record<string, unknown>;\n  if (typeof obj.status === \"number\") return obj.status;\n  if (typeof obj.statusCode === \"number\") return obj.statusCode;\n  if (obj.response && typeof obj.response === \"object\") {\n    const resp = obj.response as Record<string, unknown>;\n    if (typeof resp.status === \"number\") return resp.status;\n  }\n  return undefined;\n}\n"],"mappings":"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","names":[]}