{"record":{"id":"14c3671d80c2bde4","repo":"jackwener/OpenCLI","slug":"currency-must-be-a-3-letter-iso-code-e-g-usd-jp","errorCode":null,"errorMessage":"currency must be a 3-letter ISO code (e.g. USD, JPY, CNY), got ${JSON.stringify(value)}","messagePattern":"currency must be a 3-letter ISO code \\(e\\.g\\. USD, JPY, CNY\\), got (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/booking/search.js","lineNumber":59,"sourceCode":"  }\n  const [year, month, day] = v.split('-').map(Number);\n  const d = new Date(Date.UTC(year, month - 1, day));\n  if (\n    Number.isNaN(d.getTime()) ||\n    d.getUTCFullYear() !== year ||\n    d.getUTCMonth() !== month - 1 ||\n    d.getUTCDate() !== day\n  ) {\n    throw new ArgumentError(`${label} is not a valid calendar date: ${v}`);\n  }\n  return v;\n}\n\nfunction normalizeCurrency(value) {\n  if (value == null || value === '') return '';\n  const v = String(value).trim().toUpperCase();\n  if (!/^[A-Z]{3}$/.test(v)) {\n    throw new ArgumentError(`currency must be a 3-letter ISO code (e.g. USD, JPY, CNY), got ${JSON.stringify(value)}`);\n  }\n  return v;\n}\n\nconst ALLOWED_LANGS = new Set([\n  'en-us', 'en-gb', 'zh-cn', 'zh-tw', 'ja', 'ko', 'de', 'fr', 'es', 'it',\n  'pt-br', 'pt-pt', 'ru', 'th', 'vi', 'tr', 'pl', 'nl', 'ar',\n]);\n\nfunction normalizeLang(value) {\n  if (value == null || value === '') return '';\n  const v = String(value).trim().toLowerCase();\n  if (!ALLOWED_LANGS.has(v)) {\n    throw new ArgumentError(`lang must be one of: ${[...ALLOWED_LANGS].join(', ')}`);\n  }\n  return v;\n}\n","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/booking/search.js#L41-L77","documentation":"normalizeCurrency validates the optional currency parameter as a 3-letter A-Z code (uppercased and trimmed first). It throws ArgumentError when a non-empty currency value is not exactly three letters after normalization. It does not validate ISO-4217 membership — only the shape — so '' or null are allowed (meaning default) but 'US' or 'USDD' are rejected.","triggerScenarios":"Passing currency values like 'US', 'us-dollar', '$', 'USDollar', or numbers to the search command; a config value that contains a currency symbol or full currency name instead of the code.","commonSituations":"Using '$' or '€' from UI input; storing 'US Dollar' in a settings file; concatenating a numeric code (840) instead of the alphabetic code; trailing whitespace/full-width characters in config.","solutions":["Change the value to the 3-letter ISO 4217 code, e.g. 'USD', 'JPY', 'CNY'","Trim surrounding whitespace and remove symbols from the config/env value","Validate shape with /^[A-Za-z]{3}$/ before calling","Check the source system is not emitting numeric ISO codes and map 840 -> USD if so"],"exampleFix":"// before\nconst currency = '$';\nawait search(page, { destination: 'Tokyo', currency });\n// after\nconst currency = 'USD';\nawait search(page, { destination: 'Tokyo', currency });","handlingStrategy":"type-guard","validationCode":"function normalizeCurrencySafe(v) {\n  if (v == null || v === '') return '';\n  const s = String(v).trim().toUpperCase();\n  if (!/^[A-Z]{3}$/.test(s)) throw new Error(`bad currency: ${v}`);\n  return s;\n}","typeGuard":"function isCurrencyCode(v) {\n  return typeof v === 'string' && /^[A-Za-z]{3}$/.test(v.trim());\n}","tryCatchPattern":"try {\n  await search(page, { destination, currency });\n} catch (e) {\n  if (/3-letter ISO code/.test(e.message)) {\n    throw new Error(`Set currency to a 3-letter ISO code like USD; got: ${currency}`);\n  } else throw e;\n}","preventionTips":["Store alphabetic ISO 4217 codes ('USD'), never symbols or names","Uppercase and trim currency values at the config boundary","Map numeric ISO codes to alphabetic ones if your source uses them","Keep currency as an optional field only when defaults are acceptable"],"tags":["validation","argument-error","currency"],"backgroundTag":"invalid-currency-code","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}