{"record":{"id":"af51f6dbfd146e46","repo":"jackwener/OpenCLI","slug":"invalid-argument-af51f6","errorCode":"INVALID_ARGUMENT","errorMessage":"${err instanceof Error ? err.message : err}","messagePattern":"\\$\\{err instanceof Error \\? err\\.message : err\\}","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/eastmoney/holders.js","lineNumber":45,"sourceCode":"\ncli({\n  site: 'eastmoney',\n  name: 'holders',\n    access: 'read',\n  description: '十大流通股东（A股 F10 数据）',\n  domain: 'datacenter-web.eastmoney.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'symbol', required: true, positional: true, help: 'A股代码（600519 / sh600519 等）' },\n    { name: 'limit',  type: 'int',    default: 10,      help: '返回股东数（默认十大流通股东）' },\n  ],\n  columns: ['rank', 'reportDate', 'name', 'holdNum', 'floatRatio', 'change'],\n  func: async (args) => {\n    /** @type {string} */\n    let secucode;\n    try { secucode = toSecucode(args.symbol); }\n    catch (err) { throw new CliError('INVALID_ARGUMENT', `${err instanceof Error ? err.message : err}`); }\n    const limit = Math.max(1, Math.min(Number(args.limit) || 10, 50));\n\n    const url = new URL('https://datacenter-web.eastmoney.com/api/data/v1/get');\n    url.searchParams.set('sortColumns', 'END_DATE,HOLDER_RANK');\n    url.searchParams.set('sortTypes', '-1,1');\n    url.searchParams.set('pageSize', String(Math.max(limit, 10)));\n    url.searchParams.set('pageNumber', '1');\n    url.searchParams.set('reportName', 'RPT_F10_EH_FREEHOLDERS');\n    url.searchParams.set('columns', 'SECUCODE,SECURITY_CODE,END_DATE,HOLDER_RANK,HOLDER_NAME,HOLD_NUM,FREE_HOLDNUM_RATIO,HOLD_NUM_CHANGE');\n    url.searchParams.set('source', 'HSF10');\n    url.searchParams.set('client', 'PC');\n    url.searchParams.set('filter', `(SECUCODE=\"${secucode}\")`);\n\n    const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });\n    if (!resp.ok) throw new CliError('HTTP_ERROR', `holders failed: HTTP ${resp.status}`);\n    const data = await resp.json();\n    const rows = Array.isArray(data?.result?.data) ? data.result.data : [];\n    if (rows.length === 0) throw new CliError('NO_DATA', `No shareholder data for ${secucode}`);","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/eastmoney/holders.js#L27-L63","documentation":"CliError('INVALID_ARGUMENT') thrown in clis/eastmoney/holders.js when toSecucode(args.symbol) rejects the user-supplied symbol. The CLI wraps the plain Error into a typed CliError so callers can distinguish bad input from network failures. This is the holders command's input-validation gate before any HTTP request is made.","triggerScenarios":"Invoking the holders CLI with a --symbol that toSecucode cannot normalize (non-numeric tickers, wrong digit counts, unsupported prefixes) — caught at `catch (err) { throw new CliError('INVALID_ARGUMENT', ...)` at clis/eastmoney/holders.js:45.","commonSituations":"Passing 'AAPL' or '00700' (HK) instead of a mainland A-share code, using 'sh600519' textual prefixes, or a symbol with hidden whitespace/full-width digits from copy-paste.","solutions":["Supply a 6-digit A-share code (e.g. 600519) or prefixed form (1.600519 / 0.000001).","Normalize the symbol first: trim, strip 'sh'/'sz'/'bj' prefixes, convert full-width digits.","Validate with /((^\\d\\.)|^)\\d{6}$/ before invoking the CLI to fail early with your own message.","Branch on CliError code 'INVALID_ARGUMENT' in your wrapper to print usage help instead of a stack trace.","If you need textual prefixes or HK/US symbols, extend toSecucode — the shipped version doesn't accept them."],"exampleFix":"// caller-side validation before the CLI/library call\n// before\nconst holders = await fetchHolders({ symbol: args.symbol });\n// after\nconst symbol = String(args.symbol ?? '').trim().replace(/^(sh|sz|bj)/i, '');\nif (!/^\\d{6}$/.test(symbol)) throw new CliError('INVALID_ARGUMENT', `--symbol must be a 6-digit A-share code, got: ${args.symbol}`);\nconst holders = await fetchHolders({ symbol });","handlingStrategy":"validation","validationCode":"const symbol = String(args.symbol ?? '').trim().replace(/^(sh|sz|bj)/i, '');\nif (!/^\\d{6}$/.test(symbol)) {\n  throw new CliError('INVALID_ARGUMENT', `--symbol must be a 6-digit A-share code, got: ${args.symbol}`);\n}","typeGuard":"/**\n * @param {unknown} v\n * @returns {v is string}\n */\nfunction isParseableSymbol(v) {\n  if (typeof v !== 'string') return false;\n  const s = v.trim();\n  return /^\\d+\\.\\d{6}$/.test(s) || /^\\d{6}$/.test(s);\n}","tryCatchPattern":"try {\n  const holders = await fetchHolders({ symbol: '600519', limit: 10 });\n} catch (err) {\n  if (err instanceof CliError && err.code === 'INVALID_ARGUMENT') {\n    console.error(`Bad --symbol: ${err.message}. Example: holders --symbol 600519`);\n    return;\n  }\n  throw err;\n}","preventionTips":["Validate symbols with a regex at your CLI boundary before invoking the library.","Normalize input: trim, strip exchange prefixes, reject empty strings.","Show a usage example (e.g. holders --symbol 600519) whenever INVALID_ARGUMENT fires.","Branch on CliError.code === 'INVALID_ARGUMENT' to separate input bugs from network faults.","Reject non-A-share tickers early with a clear message instead of relying on the library error."],"tags":["validation","invalid-argument","input-parsing","eastmoney"],"backgroundTag":"invalid-symbol-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}