{"record":{"id":"09a0d02758939192","repo":"santifer/career-ops","slug":"rippling-invalid-url-url","errorCode":null,"errorMessage":"rippling: invalid URL: ${url}","messagePattern":"rippling: invalid URL: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/rippling.mjs","lineNumber":54,"sourceCode":"  if (parsed.protocol !== 'https:') return null;\n  if (parsed.hostname !== CAREERS_HOST) return null;\n  const segment = parsed.pathname.split('/').filter(Boolean)[0] || '';\n  if (!SLUG_RE.test(segment)) return null;\n  return segment;\n}\n\n/** Build the board API URL for a validated slug. */\nfunction apiUrlForSlug(slug) {\n  return `${API_BASE}/${encodeURIComponent(slug)}/jobs`;\n}\n\n/** @param {string} url */\nfunction assertRipplingApiUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`rippling: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`rippling: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== API_HOST) {\n    throw new Error(`rippling: untrusted hostname \"${parsed.hostname}\" — must be ${API_HOST}`);\n  }\n  return url;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'rippling',\n\n  detect(entry) {\n    const slug = resolveSlug(entry);\n    return slug ? { url: apiUrlForSlug(slug) } : null;\n  },\n\n  async fetch(entry, ctx) {","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/rippling.mjs#L36-L72","documentation":"assertRipplingApiUrl throws this when new URL(url) raises — the string is not a parseable absolute URL. rippling builds its API URL from constants (API_BASE + slug), so this guard would only fire if the slug interpolation corrupted the URL (e.g. injecting illegal characters that escaped encodeURIComponent) or if the constant itself was malformed.","triggerScenarios":"The URL passed to assertRipplingApiUrl lacks a scheme, contains characters the URL constructor rejects, or the API_BASE constant was broken. Because encodeURIComponent sanitizes the slug, a raw throw here points at the constant or at a non-standard call path.","commonSituations":"API_BASE constant was edited to drop the https:// scheme; someone called assertRipplingApiUrl with a hand-built URL string that is malformed; a code refactor broke the URL assembly.","solutions":["Log the exact URL string passed into the guard to see what is malformed.","Verify API_BASE is 'https://api.rippling.com/platform/api/ats/v1/board'.","Confirm the slug was passed through encodeURIComponent and contains no raw slashes or spaces.","Revert changes to the URL-building helpers (apiUrlForSlug / API_BASE)."],"exampleFix":"// before — slug injected raw, breaking the URL\nconst apiUrl = `${API_BASE}/${slug}/jobs`;\n// after — slug encoded\nconst apiUrl = `${API_BASE}/${encodeURIComponent(slug)}/jobs`;","handlingStrategy":"validation","validationCode":"function isParseableUrl(s) {\n  try { new URL(s); return true; } catch { return false; }\n}\n// rippling builds URLs via apiUrlForSlug — unit-test the builder\nconst apiUrl = apiUrlForSlug('test-slug');\nif (!isParseableUrl(apiUrl)) throw new Error('rippling: apiUrlForSlug produces an invalid URL');","typeGuard":"null","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (e) {\n  if (/rippling: invalid URL/.test(e.message)) {\n    console.error('[bug] rippling URL construction broken — check apiUrlForSlug/API_BASE');\n  } else throw e;\n}","preventionTips":["Always build API URLs via apiUrlForSlug(slug), never by hand.","Unit-test apiUrlForSlug to ensure it produces parseable https URLs.","Keep API_BASE set to the documented https://api.rippling.com base."],"tags":["url-validation","provider","rippling","malformed-url","internal-bug"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}