{"record":{"id":"4c62626c972a4084","repo":"jackwener/OpenCLI","slug":"name-is-too-long-max-60-chars-json-strin","errorCode":null,"errorMessage":"--${name} is too long (max 60 chars): ${JSON.stringify(raw)}","messagePattern":"--(.+?) is too long \\(max 60 chars\\): (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/trip/utils.js","lineNumber":405,"sourceCode":"    };\n    const found = detect();\n    if (found) return resolve(found);\n    const observer = new MutationObserver(() => {\n      const result = detect();\n      if (result) { observer.disconnect(); resolve(result); }\n    });\n    observer.observe(document.documentElement, { childList: true, subtree: true });\n    setTimeout(() => { observer.disconnect(); resolve('timeout'); }, 12000);\n  })\n`;\n\nexport function parseKeyword(name, raw) {\n    if (raw === undefined || raw === null || String(raw).trim() === '') {\n        throw new ArgumentError(`--${name} is required (a destination or attraction keyword)`);\n    }\n    const value = String(raw).trim();\n    if (value.length > 60) {\n        throw new ArgumentError(`--${name} is too long (max 60 chars): ${JSON.stringify(raw)}`);\n    }\n    return value;\n}\n\nexport function buildAttractionSearchUrl(keyword) {\n    const params = new URLSearchParams({ keyword, locale: 'en_US', curr: 'USD' });\n    return `https://www.trip.com/things-to-do/list?${params.toString()}`;\n}\n\n/**\n * Browser-context IIFE that extracts attraction / experience rows from Trip.com's\n * things-to-do results. The product cards use hashed CSS-module class names, so\n * this anchors on the one stable handle each card exposes, the\n * `things-to-do/detail/<id>` link (name is its text, `url` its href), and reads\n * rating / reviews / booked / price from the card's text by data-format pattern\n * rather than by hashed class. The price excludes the \"$N off\" promo tag and\n * takes the current (lowest non-promo) fare. Cards without a name or id are\n * dropped rather than surfaced with blanks.","sourceCodeStart":387,"sourceCodeEnd":423,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/utils.js#L387-L423","documentation":"parseKeyword caps keyword length at 60 characters because Trip.com search endpoints reject or mis-handle longer terms. Values longer than 60 chars (after trimming) throw ArgumentError showing the full raw value. This guards both URL length and upstream API constraints.","triggerScenarios":"Passing a long natural-language query like --query \"best cheap family friendly hotels near the airport with free breakfast and pool view\"; passing multi-word addresses or entire sentences as --city/--country; accidental duplication of a keyword string.","commonSituations":"Developers paste full search sentences instead of short attraction names; concatenating city + country into one flag; log or headline strings reused as keywords; localization strings longer than the limit.","solutions":["Shorten the keyword to the essential attraction/destination name (<=60 chars), e.g. \"Tokyo Tower\" instead of a full sentence","Split long queries: use --city for the destination and --query for the attraction separately","Trim redundant words (adjectives, marketing copy) before passing the value","Check String(keyword).trim().length <= 60 in your wrapper before invoking"],"exampleFix":"// before\n--query \"cheapest 5 star luxury hotel near Tokyo station with breakfast spa and city view\"\n// after\n--query \"Tokyo Station\"","handlingStrategy":"validation","validationCode":"const kw = String(keyword ?? '').trim();\nif (kw.length === 0) throw new Error('keyword is required');\nif (kw.length > 60) throw new Error(`keyword too long (${kw.length} > 60): use a short attraction/destination name`);","typeGuard":"const isValidKeyword = (v) => typeof v === 'string' && v.trim().length > 0 && v.trim().length <= 60;","tryCatchPattern":"try {\n  await runCli(['attractions-search', '--query', keyword]);\n} catch (e) {\n  if (/is too long \\(max 60 chars\\)/.test(e.message)) {\n    keyword = String(keyword).trim().slice(0, 60);\n    return runCli(['attractions-search', '--query', keyword]);\n  }\n  throw e;\n}","preventionTips":["Keep search terms short and noun-like, not full sentences","Trim whitespace and strip marketing/adjective text before passing","Enforce a 60-char cap in upstream forms/UIs that feed the CLI","Split destination vs attraction into --city/--query instead of one long string"],"tags":["cli","argument-validation","input-length"],"backgroundTag":"input-too-long","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}