{"record":{"id":"cd2da10f94172377","repo":"santifer/career-ops","slug":"itviec-invalid-url-url","errorCode":null,"errorMessage":"itviec: invalid URL: ${url}","messagePattern":"itviec: invalid URL: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/itviec.mjs","lineNumber":82,"sourceCode":"const LOCATION_RE = /#map-pin\"?><\\/use><\\/svg>\\s*<div[^>]*title=[\"']([^\"']*)[\"']/i;\n\n/** Relative publication label: English \"Posted … ago\" or Vietnamese \"Đăng … trước\". */\nconst POSTED_LABEL_RE =\n  /(?:Posted|Đăng)\\s*:?\\s*(?:<\\/\\w+>\\s*)?([^.<]{0,40}?(?:ago|trước)|today|hôm nay)/i;\n\n/** @param {any} ctx @param {number} ms */\nfunction sleep(ctx, ms) {\n  if (typeof ctx?.sleep === 'function') return ctx.sleep(ms);\n  return new Promise((r) => setTimeout(r, ms));\n}\n\n/** @param {string} url */\nfunction assertItviecUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`itviec: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`itviec: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== TRUSTED_HOST) {\n    throw new Error(`itviec: untrusted hostname \"${parsed.hostname}\" — must be ${TRUSTED_HOST}`);\n  }\n  return url;\n}\n\n/**\n * Collapse a markup fragment to its visible text.\n * @param {string} fragment\n * @returns {string}\n */\nexport function visibleText(fragment) {\n  return decodeEntities(\n    String(fragment ?? '')\n      .replace(/<!--[\\s\\S]*?-->/g, ' ')\n      .replace(/<[^>]+>/g, ' '),","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/itviec.mjs#L64-L100","documentation":"assertItviecUrl parses a URL and throws this error when new URL() fails, i.e. the string is not an absolute, well-formed URL. The itviec provider runs this check before every request so malformed config can never reach the fetch layer.","triggerScenarios":"new URL(url) throws for the given string — relative path like '/it-jobs', missing scheme ('itviec.com/it-jobs'), stray whitespace, or a non-string coerced to garbage — and it is passed to assertItviecUrl.","commonSituations":"portals.yml careers_url written without https://; a template placeholder left unsubstituted; the entry stores a path instead of a full URL; trailing spaces/quotes around the value in YAML.","solutions":["Set the URL to a full absolute https URL, e.g. https://www.itviec.com/it-jobs","Trim/strip surrounding whitespace and quotes from the config value","Validate all careers_url values in portals.yml are absolute URLs before running scan.mjs","If the value comes from an env var or CLI arg, check it is defined and well-formed before passing it in"],"exampleFix":"// before\nassertItviecUrl('itviec.com/it-jobs');\n// after\nassertItviecUrl('https://www.itviec.com/it-jobs');","handlingStrategy":"validation","validationCode":"function isValidAbsoluteUrl(url) {\n  if (typeof url !== 'string') return false;\n  try { new URL(url.trim()); return true; } catch { return false; }\n}\n// pre-validate config: if (!isValidAbsoluteUrl(entry.careers_url)) skip;","typeGuard":"const isUrl = (v) => { try { return typeof v === 'string' && !!new URL(v); } catch { return false; } };","tryCatchPattern":"try {\n  assertItviecUrl(url);\n} catch (err) {\n  if (/invalid URL/.test(err.message)) {\n    console.warn(`itviec entry has malformed URL, skipping: ${url}`);\n    return null;\n  }\n  throw err;\n}","preventionTips":["Always store full absolute URLs with scheme in config files","Trim whitespace/quotes when reading YAML string values","Validate portals.yml at load time, before any fetch","Watch for unsubstituted template placeholders in config"],"tags":["url-validation","config","validation"],"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"}