{"record":{"id":"0fac13375b024c4a","repo":"jackwener/OpenCLI","slug":"eastmoney-convertible-returned-malformed-diff-data","errorCode":null,"errorMessage":"eastmoney convertible returned malformed diff data","messagePattern":"eastmoney convertible returned malformed diff data","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/eastmoney/convertible.js","lineNumber":68,"sourceCode":"    throw new ArgumentError('eastmoney convertible --limit must be an integer between 1 and 100');\n  }\n  const raw = String(value).trim();\n  if (!/^\\d+$/.test(raw)) throw new ArgumentError('eastmoney convertible --limit must be an integer between 1 and 100');\n  const parsed = Number(raw);\n  if (parsed < 1 || parsed > 100) throw new ArgumentError('eastmoney convertible --limit must be an integer between 1 and 100');\n  return parsed;\n}\n\nexport function extractConvertibleDiff(data) {\n  if (!data || typeof data !== 'object' || Array.isArray(data)) {\n    throw new CommandExecutionError('eastmoney convertible returned a malformed response envelope');\n  }\n  if (!data.data || typeof data.data !== 'object' || Array.isArray(data.data)) {\n    throw new CommandExecutionError('eastmoney convertible returned a malformed data envelope');\n  }\n  const diff = data.data.diff;\n  if (!Array.isArray(diff)) {\n    throw new CommandExecutionError('eastmoney convertible returned malformed diff data');\n  }\n  if (diff.length === 0) {\n    throw new EmptyResultError('eastmoney convertible');\n  }\n  return diff;\n}\n\n// Map a raw eastmoney clist `diff` item to an output row.\n//\n// #2109: f238 / f239 were previously emitted as `remainingYears` / `ytm`, but\n// cross-verification (12/12 fingerprint hits) shows f239 is the putback trigger\n// price (= convPrice × 0.7) and f238 is the pure-bond premium %. Real YTM /\n// remaining term are not in this response's `fields`; adding the correct f-codes\n// is a follow-up that needs a live push2 field dump cross-checked against jisilu.\nexport function mapConvertibleRow(it, rank) {\n  if (!it || typeof it !== 'object' || Array.isArray(it)) {\n    throw new CommandExecutionError(`eastmoney convertible returned malformed row at rank ${rank}`);\n  }","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/eastmoney/convertible.js#L50-L86","documentation":"Thrown by extractConvertibleDiff (clis/eastmoney/convertible.js:68) when `data.data` exists but `data.data.diff` is not an array. The push2 clist contract guarantees `diff` is the array of quote rows; if it is absent or another type, the CLI cannot proceed. This is a CommandExecutionError signalling the nested response shape does not match the documented eastmoney clist format.","triggerScenarios":"The API returns `{ data: { ... } }` without `diff` (e.g. np/fltt params rejected, field-set change), or returns `diff` as an object/null; also occurs when stale mocks or an intercepting layer reshapes the payload before extractConvertibleDiff runs.","commonSituations":"Eastmoney silently changing clist response fields or query-param behavior; passing hand-written test fixtures missing the `diff` key; middleware/JSON transforms mangling the payload; calling with unsupported sort fid causing a different response structure.","solutions":["Compare the live response (curl the full URL with fs=b:MK0354 and current ut token) against the expected `{data:{diff:[...]}}` shape.","Ensure query params (np=1, fltt=2, invt=2, fid, fields) are sent unchanged — removing any can change the shape.","Fix test fixtures to include a non-empty `diff` array.","If eastmoney renamed `diff`, update extractConvertibleDiff accordingly.","Validate `Array.isArray(json?.data?.diff)` in caller code before mapping."],"exampleFix":"// before\nconst diff = extractConvertibleDiff(json);\n// after\nconst raw = json?.data?.diff;\nif (!Array.isArray(raw)) {\n  throw new Error('expected data.diff array, got: ' + typeof raw);\n}\nconst diff = extractConvertibleDiff(json);","handlingStrategy":"type-guard","validationCode":"if (!Array.isArray(json?.data?.diff)) {\n  throw new Error('expected data.data.diff to be an array');\n}","typeGuard":"function hasDiffArray(v) {\n  return Array.isArray(v?.data?.diff);\n}","tryCatchPattern":"try {\n  const diff = extractConvertibleDiff(json);\n} catch (e) {\n  if (String(e.message).includes('malformed diff data')) {\n    console.error('eastmoney clist shape changed — inspect raw payload');\n    return null;\n  }\n  throw e;\n}","preventionTips":["Send all query params (np=1, fltt=2, invt=2) — omitting them changes the response shape.","Snapshot real responses in fixtures so shape drift is caught in CI.","Never hand-edit fixtures without the diff key.","Diff the live response against the expected schema after eastmoney API announcements."],"tags":["api","response-shape","eastmoney","schema-validation"],"backgroundTag":"malformed-api-response-envelope","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}