{"record":{"id":"8f4c9e0c56b3216a","repo":"santifer/career-ops","slug":"landingjobs-invalid-url-url","errorCode":null,"errorMessage":"landingjobs: invalid URL: ${url}","messagePattern":"landingjobs: invalid URL: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/landingjobs.mjs","lineNumber":26,"sourceCode":"//     created_at, type, tags, gross_salary_low, gross_salary_high, ... }\n//\n// NOTE: the v1 feed carries no company-name field — the employer slug only\n// appears in the posting URL path (`https://landing.jobs/at/<slug>/<job>`), so\n// `company` is derived best-effort from that slug (humanized) and falls back to\n// the portal entry name. The whole active set is returned in one call.\n//\n// Wire in via a `job_boards:` entry with `provider: landingjobs`.\n\nconst FEED_URL = 'https://landing.jobs/api/v1/jobs';\nconst TRUSTED_HOST = 'landing.jobs';\n\n/** @param {string} url */\nfunction assertLandingUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`landingjobs: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`landingjobs: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== TRUSTED_HOST) {\n    throw new Error(`landingjobs: untrusted hostname \"${parsed.hostname}\" — must be ${TRUSTED_HOST}`);\n  }\n  return url;\n}\n\n// NaN-safe Date.parse.\nfunction toEpochMs(value) {\n  if (typeof value !== 'string' || !value) return undefined;\n  const parsed = Date.parse(value);\n  return Number.isNaN(parsed) ? undefined : parsed;\n}\n\n/**\n * Derive a best-effort company name from a Landing.jobs posting URL.\n * Posting URLs are `https://landing.jobs/at/<slug>/<job>`; the `<slug>` is","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/landingjobs.mjs#L8-L44","documentation":"Thrown by assertLandingUrl() when new URL(url) throws — the input is not a parseable URL. This is the first guard in the landing.jobs URL validator (protocol and hostname checks follow). The validator is the chokepoint for any landing.jobs URL the provider builds or accepts.","triggerScenarios":"A careers_url or api field that is empty, whitespace-only, lacks a scheme, or contains characters that break the URL parser (spaces, unbalanced brackets); a non-string value passed where a URL string is expected.","commonSituations":"A portals.yml entry with a blank or unquoted url; a copy-paste that dropped the https:// prefix; a YAML parsing quirk that produced a non-string.","solutions":["Set the url field to a full https://landing.jobs/... URL.","Verify the YAML entry is quoted correctly and is a non-empty string.","Confirm the value is a string before it reaches the validator."],"exampleFix":"# before\nacme:\n  provider: landingjobs\n  api: landing.jobs/api/v1/jobs\n\n# after\nacme:\n  provider: landingjobs\n  api: https://landing.jobs/api/v1/jobs","handlingStrategy":"validation","validationCode":"function isParseableUrl(url) {\n  try { new URL(url); return true; } catch { return false; }\n}\nfor (const e of landingjobsEntries) {\n  const u = e.api || e.careers_url;\n  if (typeof u !== 'string' || !u.trim() || !isParseableUrl(u)) {\n    console.warn(`landingjobs ${e.name}: url missing or unparseable`);\n  }\n}","typeGuard":"/** @param {string} url @returns {boolean} */\nfunction isParseableUrl(url) {\n  try { new URL(url); return true; } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Require a full https:// URL for every landingjobs entry.","Lint config entries for empty or scheme-less URLs.","Quote YAML string values to avoid type-coercion issues."],"tags":["landingjobs","url-validation","ssrf-guard","config"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}