{"record":{"id":"d92c596ba9b88dae","repo":"santifer/career-ops","slug":"gem-invalid-url-url","errorCode":null,"errorMessage":"gem: invalid URL: ${url}","messagePattern":"gem: invalid URL: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/gem.mjs","lineNumber":123,"sourceCode":"// to before this field list was widened.\n/** @param {any} posting */\nfunction buildJobDescriptionText(posting) {\n  const intro = htmlToText(posting?.jobPostSectionHtml?.introHtml);\n  const body = htmlToText(posting?.descriptionHtml);\n  const outro = htmlToText(posting?.jobPostSectionHtml?.outroHtml);\n  const compensation = htmlToText(posting?.compensationHtml);\n\n  const text = [intro, body, outro].filter(Boolean).join('\\n\\n');\n  return compensation ? [text, `Compensation: ${compensation}`].filter(Boolean).join('\\n\\n') : text;\n}\n\n/** @param {string} url */\nfunction assertGemUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`gem: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`gem: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_GEM_HOSTS.has(parsed.hostname))\n    throw new Error(`gem: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_GEM_HOSTS].join(', ')}`);\n  return url;\n}\n\n/** Resolve an explicitly pinned URL for Gem's documented REST job-board API. */\nfunction resolveRestApiUrl(entry) {\n  const raw = typeof entry.api === 'string' ? entry.api : '';\n  if (!raw) return null;\n  let parsed;\n  try {\n    parsed = new URL(raw);\n  } catch {\n    return null;\n  }\n  if (parsed.protocol !== 'https:' || parsed.hostname !== 'api.gem.com') return null;","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/gem.mjs#L105-L141","documentation":"assertGemUrl validates a URL before Gem provider requests. When new URL(url) cannot parse the value (throwing SyntaxError internally), the library wraps it in this domain-specific error. It exists so callers get a clear provider-prefixed message instead of a raw URL parser exception.","triggerScenarios":"Passing a URL string that is not parseable by the WHATWG URL parser to assertGemUrl or any code path that resolves a Gem board entry's api/board URL — e.g. an empty string, a value with spaces like 'https://example.com/my board', a malformed scheme ('htp://...'), or a config field that was never populated and interpolated as ''.","commonSituations":"Typo in the careers_url or api field in portals.yml; an environment-variable-substituted URL that resolved to empty; copy-pasting a URL with surrounding whitespace/quotes; hand-editing board entries and dropping the scheme.","solutions":["Log the exact url value at the throw site and fix the unparseable portion (missing scheme, spaces, stray quotes).","Trim whitespace and strip surrounding quotes from the URL before passing it.","Ensure the config entry's URL field is populated — check portals.yml / the board entry for an empty or partially filled value.","Pre-validate with new URL(url) in a try/catch at load time to fail fast with context."],"exampleFix":"// before\nassertGemUrl(process.env.GEM_BOARD_URL || '');\n// after\nconst raw = (process.env.GEM_BOARD_URL || '').trim().replace(/^['\"]|['\"]$/g, '');\nif (!raw) throw new Error('GEM_BOARD_URL is not set');\nassertGemUrl(raw);","handlingStrategy":"validation","validationCode":"function isParseableUrl(u) { try { new URL(u); return true; } catch { return false; } }\nif (!isParseableUrl(cfg.url)) throw new Error(`bad Gem URL in config: ${JSON.stringify(cfg.url)}`);","typeGuard":"const isNonEmptyString = (v) => typeof v === 'string' && v.trim().length > 0;","tryCatchPattern":"try { assertGemUrl(url); } catch (e) { if (String(e.message).includes('invalid URL')) console.error('Check the URL for missing scheme/spaces:', url); throw e; }","preventionTips":["Trim and de-quote URLs read from env/config before use","Validate all board URLs at config load time, not at fetch time","Always include the scheme (https://) in config values","Add a startup lint that runs assertGemUrl on every entry"],"tags":["url","validation","config"],"backgroundTag":"invalid-url-format","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"}