{"record":{"id":"8d113985edb108e5","repo":"jackwener/OpenCLI","slug":"label-must-be-one-of-object-keys-choices-jo","errorCode":null,"errorMessage":"${label} must be one of: ${Object.keys(choices).join(', ')}","messagePattern":"(.+?) must be one of: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/hltv/utils.js","lineNumber":82,"sourceCode":"  const raw = value ?? defaultValue;\n  const n = Number(raw);\n  if (!Number.isInteger(n) || n <= 0) throw new ArgumentError(`${label} must be a positive integer`);\n  if (n > maxValue) throw new ArgumentError(`${label} must be <= ${maxValue}`);\n  return n;\n}\n\nexport function normalizeOffset(value, defaultValue = 0) {\n  const raw = value ?? defaultValue;\n  const n = Number(raw);\n  if (!Number.isInteger(n) || n < 0) throw new ArgumentError('offset must be a non-negative integer');\n  if (n % 100 !== 0) throw new ArgumentError('offset must be a multiple of 100');\n  return n;\n}\n\nexport function normalizeChoice(value, defaultValue, choices, label) {\n  const raw = String(value ?? defaultValue);\n  if (!Object.prototype.hasOwnProperty.call(choices, raw)) {\n    throw new ArgumentError(`${label} must be one of: ${Object.keys(choices).join(', ')}`);\n  }\n  return raw;\n}\n\nexport function parseNumber(value) {\n  const raw = String(value ?? '').replace(/\\s+/g, ' ').trim();\n  if (!raw || raw === '-' || raw.toLowerCase() === 'n/a') return null;\n  const match = raw.replace(/,/g, '').match(/-?\\d+(?:\\.\\d+)?/);\n  if (!match) return null;\n  const n = Number(match[0]);\n  return Number.isFinite(n) ? n : null;\n}\n\nexport function parseMoneyUsd(value) {\n  return parseNumber(String(value ?? '').replace(/\\$/g, ''));\n}\n\nexport function absolutizeUrl(value) {","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/hltv/utils.js#L64-L100","documentation":"normalizeChoice validates an option against a fixed set of allowed keys (used for eventType, ranking, map, version options). It throws when the stringified value is not one of the accepted choices, listing all valid keys in the message.","triggerScenarios":"Passing an unknown value like --map cache or --event-type lan when the mapping only defines specific keys; typos or casing differences; renamed options after a library update.","commonSituations":"Old scripts using choice keys removed in a newer version; guessing slug-style names instead of the exact keys; copy-pasting values from HLTV's UI rather than this CLI's list.","solutions":["Use one of the exact keys printed in the error message","Run the command with --help to see the accepted choices for that option","Check the library's CHANGELOG in case the option key was renamed between versions"],"exampleFix":"// before\nhltv maps --map de_dust2\n// after\nhltv maps --map dust2  # use a key from the allowed list","handlingStrategy":"validation","validationCode":"const MAPS = ['mirage','inferno','nuke','overpass','vertigo','ancient','anubis','dust2','train'];\nif (opts.map && !MAPS.includes(String(opts.map).toLowerCase())) throw new Error(`map must be one of: ${MAPS.join(', ')}`);","typeGuard":"const isChoice = (v, choices) => Object.prototype.hasOwnProperty.call(choices, String(v));","tryCatchPattern":"try {\n  await hltv.stats({ map: opts.map });\n} catch (e) {\n  if (e.name === 'ArgumentError' && e.message.startsWith('map must be one of')) {\n    console.error(e.message); // message lists valid keys\n    process.exitCode = 2;\n  } else throw e;\n}","preventionTips":["Build CLI choice menus from the same key list the library exports","Lowercase/trim user input before comparing to choices","Pin the library version and re-check choices after upgrades"],"tags":["cli","validation","enum","argument-error"],"backgroundTag":"invalid-enum-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}