{"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/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/rippling.mjs#L36-L72","documentation":"The rippling provider validates URLs via assertRipplingApiUrl. If the URL string cannot be parsed by the URL constructor (malformed scheme/host, whitespace, missing protocol in a context that requires it), it throws this error before any network call.","triggerScenarios":"Passing a non-parseable string to assertRipplingApiUrl — e.g. 'rippling.com/api/jobs' without a scheme, a URL with spaces or invalid characters, or an empty/undefined value coerced into the URL constructor path where it fails.","commonSituations":"Config or code building the API URL from a slug where a base was omitted, env var interpolation producing an empty or garbage string, or copy-pasting a browser-omitted https:// prefix.","solutions":["Ensure the URL is absolute and well-formed, e.g. https://api.rippling.com/... .","Log the exact url value at the call site; fix whatever produced the malformed string (usually a missing base or empty slug).","Normalize input: prepend https:// when the string lacks a scheme before validation."],"exampleFix":"// before\nconst url = 'api.rippling.com/jobs?slug=' + slug; // throws\n// after\nconst url = 'https://api.rippling.com/jobs?slug=' + slug;","handlingStrategy":"validation","validationCode":"function isParseableUrl(u) { try { new URL(u); return true; } catch { return false; } }\nif (!isParseableUrl(candidate)) throw new Error(`skip: malformed URL ${candidate}`);","typeGuard":"const isUrl = (u) => { try { new URL(u); return typeof u === 'string'; } catch { return false; } };","tryCatchPattern":"try { await provider.fetch(entry, ctx); } catch (e) { if (e.message.startsWith('rippling: invalid URL')) { console.error(`Malformed URL for ${entry.name}`); return null; } throw e; }","preventionTips":["Always construct URLs with new URL(base, path) instead of string concat.","Validate config URLs at startup, before any fetch.","Log candidate URL strings when assembly involves env vars or slugs."],"tags":["url-validation","url-parse","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"}