{"record":{"id":"a5fda50d0e9ea42e","repo":"santifer/career-ops","slug":"gem-unsupported-rest-response-envelope-expected-an-array-or-a5fda5","errorCode":null,"errorMessage":"gem: unsupported REST response envelope — expected an array or {job_posts: [...]}, got ${typeof json}","messagePattern":"gem: unsupported REST response envelope — expected an array or (.+?), got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/gem.mjs","lineNumber":289,"sourceCode":" * appeared both as a bare array and wrapped in `job_posts`; accepting both\n * keeps the provider tolerant. `[]`/`{}`/`null` are legitimately contentless\n * (an empty board), so they resolve to no rows — but any OTHER nonempty\n * object shape is undocumented and gets rejected loudly rather than silently\n * read as \"zero jobs,\" which would make a changed Gem response look like an\n * empty board and drop every posting without a trace.\n * @param {any} json\n */\nfunction extractRestRows(json) {\n  if (Array.isArray(json)) return json;\n  if (json === null || json === undefined) return [];\n  if (typeof json === 'object') {\n    if (Array.isArray(json.job_posts)) return json.job_posts;\n    if (Object.keys(json).length === 0) return [];\n    throw new Error(\n      `gem: unsupported REST response envelope — expected an array or {job_posts: [...]}, got an object with keys: ${Object.keys(json).join(', ')}`\n    );\n  }\n  throw new Error(`gem: unsupported REST response envelope — expected an array or {job_posts: [...]}, got ${typeof json}`);\n}\n\n/**\n * Parse Gem's documented GET response.\n * @param {any} json\n * @param {string} companyName\n */\nexport function parseRestResponse(json, companyName) {\n  const rows = extractRestRows(json);\n  return rows.filter(j => j && typeof j.title === 'string' && j.title.trim() && typeof j.absolute_url === 'string')\n    .map(j => {\n      let url;\n      try {\n        const parsed = new URL(j.absolute_url);\n        if (parsed.protocol !== 'https:' || parsed.hostname !== 'jobs.gem.com' || !GEM_POSTING_PATH_RE.test(parsed.pathname)) return null;\n        url = parsed.href;\n      } catch {\n        return null;","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/gem.mjs#L271-L307","documentation":"The non-object branch of extractRestRows: after ruling out arrays, null/undefined, and objects, the parsed JSON body was some other JSON type (string, number, boolean). The provider cannot treat that as a postings collection and throws the same envelope message with the actual typeof value.","triggerScenarios":"The endpoint returned a JSON scalar — e.g. a bare string body (\"OK\"), a quoted error message, or an API version returning a count/number instead of a list; or ctx.fetchJson returning a pre-decoded primitive.","commonSituations":"Hitting a health-check or index endpoint instead of the postings endpoint; Gem returning a JSON-encoded string wrapper; a misconfigured api URL pointing at a non-API resource.","solutions":["Check typeof in the error message: a string usually means the wrong URL was hit — verify the REST endpoint path.","Ensure fetchJson is pointed at the documented job-board postings endpoint.","If Gem now wraps postings as a JSON-encoded string, JSON.parse the inner string before extractRestRows.","Log the raw response body once to confirm what the server actually returns."],"exampleFix":"// before\nconst rows = extractRestRows(await ctx.fetchJson(url));\n// after\nlet body = await ctx.fetchJson(url);\nif (typeof body === 'string') body = JSON.parse(body);\nconst rows = extractRestRows(body);","handlingStrategy":"type-guard","validationCode":"if (typeof body !== 'object' && typeof body !== 'undefined') throw new Error(`Gem REST returned ${typeof body}, expected object/array`);","typeGuard":"const isJsonContainer = (v) => v === null || v === undefined || typeof v === 'object';","tryCatchPattern":"try { rows = extractRestRows(json); } catch (e) { if (/got (string|number|boolean)/.test(e.message)) console.error('Non-object JSON body — likely wrong endpoint URL'); throw e; }","preventionTips":["Verify the api URL points at the postings endpoint, not an index/health route","Log the raw body once per board when the shape is unexpected","Ensure fetchJson (not a plain text fetch) decodes the response","Add an integration test per board asserting a container-shaped response"],"tags":["api","response-shape","rest"],"backgroundTag":"unexpected-response-shape","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}