{"record":{"id":"d24e7339607bb252","repo":"jackwener/OpenCLI","slug":"label-returned-a-malformed-article-id","errorCode":null,"errorMessage":"${label} returned a malformed article id","messagePattern":"(.+?) returned a malformed article id","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/juejin/utils.js","lineNumber":144,"sourceCode":"}\n\nexport function readDataArray(payload, label) {\n    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'data')) {\n        throw new CommandExecutionError(`${label} returned a malformed payload`);\n    }\n    if (!Array.isArray(payload.data)) {\n        throw new CommandExecutionError(`${label} returned a non-array data field`);\n    }\n    if (payload.data.length === 0) {\n        throw new EmptyResultError(label, `${label} returned no articles.`);\n    }\n    return payload.data;\n}\n\nfunction readArticleId(value, label) {\n    const id = String(value ?? '').trim();\n    if (!JUEJIN_ID.test(id)) {\n        throw new CommandExecutionError(`${label} returned a malformed article id`);\n    }\n    return id;\n}\n\nfunction readOptionalNumber(value, label) {\n    if (value == null) return null;\n    const n = Number(value);\n    if (!Number.isFinite(n)) {\n        throw new CommandExecutionError(`${label} returned a malformed numeric field`);\n    }\n    return n;\n}\n\n/** Map a recommend-feed row (`item_info.article_info` / `author_user_info`) to a flat shape. */\nexport function mapFeedItem(row, rank) {\n    const info = row?.item_info ?? {};\n    const article = info.article_info ?? {};\n    const author = info.author_user_info ?? {};","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/juejin/utils.js#L126-L162","documentation":"readArticleId in clis/juejin/utils.js:144 coerces a value to a trimmed string and validates it against JUEJIN_ID (/^\\d{16,20}$/) because Juejin content IDs are 19-digit numeric strings. If the article_id from the API row is missing, null, or not a 16-20 digit number, this CommandExecutionError is thrown to prevent building broken https://juejin.cn/post/<id> links.","triggerScenarios":"A feed or hot-list row where item_info.article_info.article_id (recommend) or content.content_id (hot) is absent, null, non-numeric, or has the wrong digit count — e.g. an empty row, a promoted/non-article item in the feed, or an API shape change moving the id field.","commonSituations":"Juejin inserting ad or special cards into the recommend feed lacking article_info; the hot-list endpoint returning entries with a different content type whose content_id is not a 19-digit article id; an API contract change relocating article_id.","solutions":["Dump the offending row (item_info / content object) to see where the id actually lives after an API change.","Filter out non-article rows before mapping: skip rows without item_info.article_info (feed) or with non-numeric content_id (hot).","Update mapFeedItem/mapHotItem field paths if Juejin renamed or moved the id field.","Wrap per-row mapping in try/catch and skip (or log) rows that fail id validation instead of failing the whole command."],"exampleFix":"// before: one bad row aborts the whole listing\nconst items = rows.map((row, i) => mapFeedItem(row, i + 1));\n// after: skip rows without a valid article id\nconst items = rows\n    .filter(row => /^\\d{16,20}$/.test(String(row?.item_info?.article_info?.article_id ?? '')))\n    .map((row, i) => mapFeedItem(row, i + 1));","handlingStrategy":"validation","validationCode":"const JUEJIN_ID = /^\\d{16,20}$/;\nrows = rows.filter(r =>\n  JUEJIN_ID.test(String(r?.item_info?.article_info?.article_id ?? r?.content?.content_id ?? '').trim())\n);","typeGuard":"function hasValidArticleId(row) {\n  const id = String(row?.item_info?.article_info?.article_id\n    ?? row?.content?.content_id ?? '').trim();\n  return /^\\d{16,20}$/.test(id);\n}","tryCatchPattern":"try {\n  const items = rows.map((row, i) => mapFeedItem(row, i + 1));\n} catch (err) {\n  if (err instanceof CommandExecutionError && err.message.includes('malformed article id')) {\n    console.error('Skipping feed row without a valid article id (ad/special card?).');\n    return;\n  }\n  throw err;\n}","preventionTips":["Filter feed rows for a numeric 16-20 digit article_id before mapping.","Expect ad/special cards in the recommend feed lacking article_info and skip them.","Validate ids with JUEJIN_ID (/^\\d{16,20}$/) at every ingestion boundary, not just display time.","Re-check field paths (article_info.article_id vs content.content_id) whenever Juejin changes an endpoint."],"tags":["validation","schema-validation","juejin","response-shape"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}