{"record":{"id":"6d7ea33e5ec29028","repo":"jackwener/OpenCLI","slug":"train-no-must-not-be-empty","errorCode":null,"errorMessage":"<train-no> must not be empty","messagePattern":"<train-no> must not be empty","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/12306/price.js","lineNumber":127,"sourceCode":"cli({\n    site: '12306',\n    name: 'price',\n    access: 'read',\n    description: 'Look up 12306 ticket prices by seat class for one train on a given date and segment (anonymous, no login required)',\n    domain: 'kyfw.12306.cn',\n    strategy: Strategy.PUBLIC,\n    browser: false,\n    args: [\n        { name: 'train-no', positional: true, required: true, help: 'Internal train_no from `12306 trains` (e.g. 24000000G10L)' },\n        { name: 'from', required: true, help: 'Origin station (Chinese name, telecode, or pinyin) - must be a stop of this train' },\n        { name: 'to', required: true, help: 'Destination station - must be a stop of this train' },\n        { name: 'date', required: true, help: 'Departure date in YYYY-MM-DD' },\n        { name: 'seat-types', default: 'OM9PA1A3A4FWZ', help: 'Seat-type letters to query (default covers the common classes). Examples: OM9 (二等/一等/商务), A1A3A4 (硬座/硬卧/软卧).' },\n    ],\n    columns: ['seat_code', 'seat_name', 'price', 'currency'],\n    func: async (kwargs) => {\n        const trainNo = String(kwargs['train-no'] ?? '').trim();\n        if (!trainNo) throw new ArgumentError('<train-no> must not be empty');\n        if (!TRAIN_NO_RE.test(trainNo)) {\n            throw new ArgumentError(\n                `<train-no> \"${trainNo}\" does not look like a 12306 internal train_no`,\n                'Use the train_no field from `12306 trains` output (e.g. 24000000G10L), not the public code (G1).',\n            );\n        }\n        const fromArg = String(kwargs.from ?? '').trim();\n        const toArg = String(kwargs.to ?? '').trim();\n        if (!fromArg) throw new ArgumentError('--from station must not be empty');\n        if (!toArg) throw new ArgumentError('--to station must not be empty');\n        const date = validateDate(kwargs.date);\n        const seatTypes = String(kwargs['seat-types'] ?? '').trim() || 'OM9PA1A3A4FWZ';\n        if (!SEAT_TYPES_RE.test(seatTypes)) {\n            throw new ArgumentError('--seat-types must contain only 12306 seat letters/digits (A-Z, 0-9)');\n        }\n\n        const stations = await fetchStationBundle();\n        const fromStation = resolveStation(stations, fromArg);","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/12306/price.js#L109-L145","documentation":"The `12306 price` command throws this ArgumentError during argument validation when the positional <train-no> argument is missing or resolves to an empty/whitespace-only string. It fails fast before any network calls because the internal train_no is required for every downstream API call.","triggerScenarios":"Calling `12306 price` with no positional argument (`12306 price --from X --to Y --date D`), passing an empty string, or a value that is only whitespace; programmatically passing undefined/null kwargs['train-no'].","commonSituations":"Script-built command lines where a variable is empty because an upstream `12306 trains` lookup failed; shell quoting mistakes dropping the argument; copying only the public code after deleting the train_no field; template expansion leaving the positional blank.","solutions":["Pass the internal train_no positional argument, e.g. `12306 price 24000000G10L --from 北京南 --to 上海虹桥 --date 2026-09-01`.","Get the value from the train_no column of `12306 trains` output, not the public code (G1).","Check that the variable feeding the argument is non-empty in your script (guard before invoking).","Ensure proper quoting so the shell does not drop the token.","Distinguish this empty-argument error from the companion format error (non-empty but not matching TRAIN_NO_RE) — fix whichever validation you hit."],"exampleFix":"// before\nconst trainNo = rows[0].public_code; // e.g. \"G1\", or empty if row missing\nawait priceCmd({ 'train-no': trainNo, from: '北京南', to: '上海虹桥', date });\n// after\nconst trainNo = rows[0]?.train_no ?? '';\nif (!trainNo.trim()) throw new Error('run `12306 trains` first; train_no is required');\nawait priceCmd({ 'train-no': trainNo, from: '北京南', to: '上海虹桥', date });","handlingStrategy":"validation","validationCode":"const trainNo = String(kwargs['train-no'] ?? '').trim();\nif (!trainNo) throw new Error('12306 price requires a positional <train-no>; get it from `12306 trains` (train_no column)');\nif (!/^[0-9A-Za-z]{8,18}$/.test(trainNo)) throw new Error(\"'\" + trainNo + \"' is not an internal train_no (public codes like G1 are rejected)\");","typeGuard":"function isValidTrainNo(v) {\n  return typeof v === 'string' && /^[0-9A-Za-z]{8,18}$/.test(v.trim());\n}","tryCatchPattern":"try {\n  rows = await run12306Price({ 'train-no': trainNo, from, to, date });\n} catch (err) {\n  if (err instanceof ArgumentError && /must not be empty/.test(err.message)) {\n    console.error('Usage: 12306 price <train-no> --from <station> --to <station> --date YYYY-MM-DD');\n    process.exitCode = 2;\n    return;\n  }\n  throw err;\n}","preventionTips":["Always pass train_no positionally: `12306 price 24000000G10L --from ... --to ... --date ...`.","Source train_no from the `12306 trains` output's train_no column, not the public code.","Guard upstream variables in scripts so an empty lookup result cannot silently drop the argument.","Quote shell arguments to prevent tokens being swallowed by expansion."],"tags":["argument-validation","cli-usage","missing-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}